Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding UDK/UDC to service client #19141

Merged
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
8d9f52f
Adding UDK/UDC to service client
siminsavani-msft Sep 16, 2022
548a3e0
Adding GetUDKSASURL
siminsavani-msft Sep 20, 2022
bb9abb7
Merge siminsavani/userdelegationsas with main branch
siminsavani-msft Sep 21, 2022
361c25a
Removing changes as requested
siminsavani-msft Sep 21, 2022
3e8904c
Additional changes to remove
siminsavani-msft Sep 21, 2022
1a62866
Addressing comments, cleaning up code, adding wip test
siminsavani-msft Sep 21, 2022
dfca65d
Modifications to test
siminsavani-msft Sep 22, 2022
4f6ad3f
Clean up
siminsavani-msft Sep 22, 2022
aef324f
Addressing comments in PR
siminsavani-msft Sep 22, 2022
632381d
Adding helper methods to only allow SDK calls
siminsavani-msft Sep 22, 2022
a35f7a6
Adding to example_test and making udc immutable
siminsavani-msft Sep 22, 2022
a9a1508
Adding helper method for GetAccountName
siminsavani-msft Sep 22, 2022
95f0746
Additional clean up
siminsavani-msft Sep 22, 2022
4da22d1
Merge branch 'main' into siminsavani/userdelegationsas
siminsavani-msft Sep 22, 2022
46287c7
Minor changes & commenting out test
siminsavani-msft Sep 22, 2022
17310e5
Renaming of opts
siminsavani-msft Sep 22, 2022
cdd17c5
Changing option name for GetUserDelegationCredential
siminsavani-msft Sep 22, 2022
73bb439
Minor type renaming
siminsavani-msft Sep 22, 2022
1c8a6c3
Putting GetUserDelegationCredential on service.client
siminsavani-msft Sep 22, 2022
b8d57dd
Adding doc comment
siminsavani-msft Sep 22, 2022
f1d110a
Updating doc comment for KeyInfo
siminsavani-msft Sep 22, 2022
0a046e5
Fixing example test with handle error
siminsavani-msft Sep 23, 2022
07c039f
Fixing GetUserDelegationCredential and examples - Service.Client
siminsavani-msft Sep 23, 2022
702a7d0
Removing NewUserDelegationCredential from models.go
siminsavani-msft Sep 23, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
72 changes: 71 additions & 1 deletion sdk/messaging/azeventhubs/internal/blob/zc_sas_account.go
Expand Up @@ -27,7 +27,7 @@ type AccountSASSignatureValues struct {
ResourceTypes string `param:"srt"` // Create by initializing AccountSASResourceTypes and then call String()
}

// Sign uses an account's shared key credential to sign this signature values to produce
// Sign uses an account's SharedKeyCredential to sign this signature values to produce
siminsavani-msft marked this conversation as resolved.
Show resolved Hide resolved
// the proper SAS query parameters.
func (v AccountSASSignatureValues) Sign(sharedKeyCredential *SharedKeyCredential) (SASQueryParameters, error) {
// https://docs.microsoft.com/en-us/rest/api/storageservices/Constructing-an-Account-SAS
Expand Down Expand Up @@ -82,6 +82,76 @@ func (v AccountSASSignatureValues) Sign(sharedKeyCredential *SharedKeyCredential
return p, nil
}

// SignUDK uses an account's UserDelegationKey to sign this signature values to produce the proper SAS query parameters.
func (v AccountSASSignatureValues) SignUDK(userDelegationCredential *UserDelegationCredential) (SASQueryParameters, error) {
if userDelegationCredential == nil {
return SASQueryParameters{}, fmt.Errorf("cannot sign SAS query without UserDelegationKey")
}

// https://docs.microsoft.com/en-us/rest/api/storageservices/Constructing-an-Account-SAS
if v.ExpiryTime.IsZero() || v.Permissions == "" || v.ResourceTypes == "" || v.Services == "" {
return SASQueryParameters{}, errors.New("account SAS is missing at least one of these: ExpiryTime, Permissions, Service, or ResourceType")
}
if v.Version == "" {
v.Version = SASVersion
}
perms := &AccountSASPermissions{}
if err := perms.Parse(v.Permissions); err != nil {
return SASQueryParameters{}, err
}
v.Permissions = perms.String()

startTime, expiryTime, _ := FormatTimesForSASSigning(v.StartTime, v.ExpiryTime, time.Time{})

stringToSign := strings.Join([]string{
userDelegationCredential.AccountName(),
v.Permissions,
v.Services,
v.ResourceTypes,
startTime,
expiryTime,
v.IPRange.String(),
string(v.Protocol),
v.Version,
""}, // That is right, the account SAS requires a terminating extra newline
"\n")

signature, err := userDelegationCredential.ComputeHMACSHA256(stringToSign)
if err != nil {
return SASQueryParameters{}, err
}
p := SASQueryParameters{
// Common SAS parameters
version: v.Version,
protocol: v.Protocol,
startTime: v.StartTime,
expiryTime: v.ExpiryTime,
permissions: v.Permissions,
ipRange: v.IPRange,

// Account-specific SAS parameters
services: v.Services,
resourceTypes: v.ResourceTypes,

// Calculated SAS signature
signature: signature,
}

udk := userDelegationCredential.getUDKParams()

//User delegation SAS specific parameters
if udk != nil {
p.signedOID = *udk.SignedOID
p.signedTID = *udk.SignedTID
p.signedStart = *udk.SignedStart
p.signedExpiry = *udk.SignedExpiry
p.signedService = *udk.SignedService
p.signedVersion = *udk.SignedVersion
}

return p, nil
}

// AccountSASPermissions type simplifies creating the permissions string for an Azure Storage Account SAS.
// Initialize an instance of this type and then call its String method to set AccountSASSignatureValues's Permissions field.
type AccountSASPermissions struct {
Expand Down
168 changes: 136 additions & 32 deletions sdk/messaging/azeventhubs/internal/blob/zc_sas_service.go
Expand Up @@ -45,10 +45,8 @@ func getDirectoryDepth(path string) string {
return fmt.Sprint(strings.Count(path, "/") + 1)
}

// NewSASQueryParameters uses an account's StorageAccountCredential to sign this signature values to produce
// the proper SAS query parameters.
// See: StorageAccountCredential. Compatible with both UserDelegationCredential and SharedKeyCredential
func (v BlobSASSignatureValues) NewSASQueryParameters(sharedKeyCredential *SharedKeyCredential) (SASQueryParameters, error) {
// Sign uses an account's SharedKeyCredential to sign this signature values to produce the proper SAS query parameters.
func (v BlobSASSignatureValues) Sign(sharedKeyCredential *SharedKeyCredential) (SASQueryParameters, error) {
resource := "c"
if sharedKeyCredential == nil {
return SASQueryParameters{}, fmt.Errorf("cannot sign SAS query without Shared Key Credential")
Expand Down Expand Up @@ -101,25 +99,6 @@ func (v BlobSASSignatureValues) NewSASQueryParameters(sharedKeyCredential *Share

signedIdentifier := v.Identifier

//udk := sharedKeyCredential.getUDKParams()
//
//if udk != nil {
// udkStart, udkExpiry, _ := FormatTimesForSASSigning(udk.SignedStart, udk.SignedExpiry, time.Time{})
// //I don't like this answer to combining the functions
// //But because signedIdentifier and the user delegation key strings share a place, this is an _OK_ way to do it.
// signedIdentifier = strings.Join([]string{
// udk.SignedOid,
// udk.SignedTid,
// udkStart,
// udkExpiry,
// udk.SignedService,
// udk.SignedVersion,
// v.PreauthorizedAgentObjectId,
// v.AgentObjectId,
// v.CorrelationId,
// }, "\n")
//}

// String to sign: http://msdn.microsoft.com/en-us/library/azure/dn140255.aspx
stringToSign := strings.Join([]string{
v.Permissions,
Expand Down Expand Up @@ -171,15 +150,140 @@ func (v BlobSASSignatureValues) NewSASQueryParameters(sharedKeyCredential *Share
signature: signature,
}

////User delegation SAS specific parameters
//if udk != nil {
// p.signedOid = udk.SignedOid
// p.signedTid = udk.SignedTid
// p.signedStart = udk.SignedStart
// p.signedExpiry = udk.SignedExpiry
// p.signedService = udk.SignedService
// p.signedVersion = udk.SignedVersion
//}
return p, nil
}

// SignUDK uses an account's UserDelegationKey to sign this signature values to produce the proper SAS query parameters.
func (v BlobSASSignatureValues) SignUDK(userDelegationCredential *UserDelegationCredential) (SASQueryParameters, error) {
resource := "c"
if userDelegationCredential == nil {
return SASQueryParameters{}, fmt.Errorf("cannot sign SAS query without UserDelegationKey")
}

if !v.SnapshotTime.IsZero() {
resource = "bs"
//Make sure the permission characters are in the correct order
perms := &BlobSASPermissions{}
if err := perms.Parse(v.Permissions); err != nil {
return SASQueryParameters{}, err
}
v.Permissions = perms.String()
} else if v.BlobVersion != "" {
resource = "bv"
//Make sure the permission characters are in the correct order
perms := &BlobSASPermissions{}
if err := perms.Parse(v.Permissions); err != nil {
return SASQueryParameters{}, err
}
v.Permissions = perms.String()
} else if v.Directory != "" {
resource = "d"
v.BlobName = ""
perms := &BlobSASPermissions{}
if err := perms.Parse(v.Permissions); err != nil {
return SASQueryParameters{}, err
}
v.Permissions = perms.String()
} else if v.BlobName == "" {
// Make sure the permission characters are in the correct order
perms := &ContainerSASPermissions{}
if err := perms.Parse(v.Permissions); err != nil {
return SASQueryParameters{}, err
}
v.Permissions = perms.String()
} else {
resource = "b"
// Make sure the permission characters are in the correct order
perms := &BlobSASPermissions{}
if err := perms.Parse(v.Permissions); err != nil {
return SASQueryParameters{}, err
}
v.Permissions = perms.String()
}
if v.Version == "" {
v.Version = SASVersion
}
startTime, expiryTime, snapshotTime := FormatTimesForSASSigning(v.StartTime, v.ExpiryTime, v.SnapshotTime)

signedIdentifier := v.Identifier

udk := userDelegationCredential.getUDKParams()

if udk != nil {
udkStart, udkExpiry, _ := FormatTimesForSASSigning(*udk.SignedStart, *udk.SignedExpiry, time.Time{})
signedIdentifier = strings.Join([]string{
*udk.SignedOID,
*udk.SignedTID,
udkStart,
udkExpiry,
*udk.SignedService,
*udk.SignedVersion,
v.PreauthorizedAgentObjectId,
v.AgentObjectId,
v.CorrelationId,
}, "\n")
}

// String to sign: http://msdn.microsoft.com/en-us/library/azure/dn140255.aspx
stringToSign := strings.Join([]string{
v.Permissions,
startTime,
expiryTime,
getCanonicalName(userDelegationCredential.AccountName(), v.ContainerName, v.BlobName, v.Directory),
signedIdentifier,
v.IPRange.String(),
string(v.Protocol),
v.Version,
resource,
snapshotTime, // signed timestamp
v.CacheControl, // rscc
v.ContentDisposition, // rscd
v.ContentEncoding, // rsce
v.ContentLanguage, // rscl
v.ContentType}, // rsct
"\n")

signature := ""
signature, err := userDelegationCredential.ComputeHMACSHA256(stringToSign)
if err != nil {
return SASQueryParameters{}, err
}

p := SASQueryParameters{
// Common SAS parameters
version: v.Version,
protocol: v.Protocol,
startTime: v.StartTime,
expiryTime: v.ExpiryTime,
permissions: v.Permissions,
ipRange: v.IPRange,

// Container/Blob-specific SAS parameters
resource: resource,
identifier: v.Identifier,
cacheControl: v.CacheControl,
contentDisposition: v.ContentDisposition,
contentEncoding: v.ContentEncoding,
contentLanguage: v.ContentLanguage,
contentType: v.ContentType,
snapshotTime: v.SnapshotTime,
signedDirectoryDepth: getDirectoryDepth(v.Directory),
preauthorizedAgentObjectID: v.PreauthorizedAgentObjectId,
agentObjectID: v.AgentObjectId,
correlationID: v.CorrelationId,
// Calculated SAS signature
signature: signature,
}

//User delegation SAS specific parameters
if udk != nil {
p.signedOID = *udk.SignedOID
p.signedTID = *udk.SignedTID
p.signedStart = *udk.SignedStart
p.signedExpiry = *udk.SignedExpiry
p.signedService = *udk.SignedService
p.signedVersion = *udk.SignedVersion
}

return p, nil
}
Expand Down
3 changes: 3 additions & 0 deletions sdk/storage/azblob/blob/models.go
Expand Up @@ -23,6 +23,9 @@ func NewSharedKeyCredential(accountName, accountKey string) (*SharedKeyCredentia
return exported.NewSharedKeyCredential(accountName, accountKey)
}

// UserDelegationKey contains UserDelegationKey.
type UserDelegationKey = generated.UserDelegationKey
siminsavani-msft marked this conversation as resolved.
Show resolved Hide resolved

// Type Declarations ---------------------------------------------------------------------

// AccessConditions identifies blob-specific access conditions which you optionally set.
Expand Down
12 changes: 12 additions & 0 deletions sdk/storage/azblob/common.go
Expand Up @@ -8,6 +8,7 @@ package azblob

import (
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/internal/exported"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/internal/generated"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/sas"
)

Expand All @@ -20,6 +21,17 @@ func NewSharedKeyCredential(accountName, accountKey string) (*SharedKeyCredentia
return exported.NewSharedKeyCredential(accountName, accountKey)
}

// UserDelegationCredential contains an account's name and its user delegation key.
type UserDelegationCredential = exported.UserDelegationCredential
siminsavani-msft marked this conversation as resolved.
Show resolved Hide resolved

// NewUserDelegationCredential creates a new UserDelegationCredential using a Storage account's name and a user delegation key from it
func NewUserDelegationCredential(accountName string, key generated.UserDelegationKey) *UserDelegationCredential {
return exported.NewUserDelegationCredential(accountName, key)
}

// UserDelegationKey contains UserDelegationKey.
type UserDelegationKey = generated.UserDelegationKey

// URLParts object represents the components that make up an Azure Storage Container/Blob URL.
// NOTE: Changing any SAS-related field requires computing a new SAS signature.
type URLParts = sas.URLParts
Expand Down
3 changes: 3 additions & 0 deletions sdk/storage/azblob/constants.go
Expand Up @@ -7,6 +7,7 @@
package azblob

import (
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/internal/exported"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/internal/generated"
)

Expand Down Expand Up @@ -35,3 +36,5 @@ const (
func PossibleDeleteSnapshotsOptionTypeValues() []DeleteSnapshotsOptionType {
return generated.PossibleDeleteSnapshotsOptionTypeValues()
}

const SASTimeFormat = exported.SASTimeFormat // "2017-07-27T00:00:00Z" // ISO 8601
siminsavani-msft marked this conversation as resolved.
Show resolved Hide resolved
44 changes: 44 additions & 0 deletions sdk/storage/azblob/container/client.go
Expand Up @@ -9,6 +9,7 @@ package container
import (
"context"
"errors"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob"
"net/http"
"strings"
"time"
Expand Down Expand Up @@ -90,6 +91,10 @@ func (c *Client) sharedKey() *SharedKeyCredential {
return base.SharedKey((*base.Client[generated.ContainerClient])(c))
}

func (c *Client) userDelegationKey() *UserDelegationKey {
return base.UserDelegationKey((*base.Client[generated.ContainerClient])(c))
}

// URL returns the URL endpoint used by the Client object.
func (c *Client) URL() string {
return c.generated().Endpoint()
Expand Down Expand Up @@ -308,3 +313,42 @@ func (c *Client) GetSASURL(permissions sas.ContainerPermissions, start time.Time

return endpoint, nil
}

// GetUDKSASURL is a convenience method for generating a SAS token for the currently pointed at container.
// It can only be used if the credential supplied during creation was a UserDelegationKey.
func (c *Client) GetUDKSASURL(permissions sas.ContainerPermissions, start time.Time, expiry time.Time) (string, error) {
if c.userDelegationKey() == nil {
return "", errors.New("GetUDKSASURL can only be signed with a UserDelegationKey")
}

urlParts, err := blob.ParseURL(c.URL())
if err != nil {
return "", err
}

udc := azblob.UserDelegationCredential{
siminsavani-msft marked this conversation as resolved.
Show resolved Hide resolved
Name: urlParts.Host,
Key: *c.userDelegationKey(),
}

// Containers do not have snapshots, nor versions.
qps, err := sas.BlobSignatureValues{
Version: sas.Version,
Protocol: sas.ProtocolHTTPS,
ContainerName: urlParts.ContainerName,
Permissions: permissions.String(),
StartTime: start.UTC(),
ExpiryTime: expiry.UTC(),
}.SignUDK(&udc)
if err != nil {
return "", err
}

endpoint := c.URL()
if !strings.HasSuffix(endpoint, "/") {
endpoint += "/"
}
endpoint += "?" + qps.Encode()

return endpoint, nil
}
3 changes: 3 additions & 0 deletions sdk/storage/azblob/container/models.go
Expand Up @@ -22,6 +22,9 @@ func NewSharedKeyCredential(accountName, accountKey string) (*SharedKeyCredentia
return exported.NewSharedKeyCredential(accountName, accountKey)
}

// UserDelegationKey contains UserDelegationKey.
type UserDelegationKey = exported.UserDelegationKey
siminsavani-msft marked this conversation as resolved.
Show resolved Hide resolved

// Request Model Declaration -------------------------------------------------------------------------------------------

// CpkScopeInfo contains a group of parameters for the ContainerClient.Create method.
Expand Down