diff --git a/sdk/storage/azblob/blob/client.go b/sdk/storage/azblob/blob/client.go index 054a48063e50..90e101806490 100644 --- a/sdk/storage/azblob/blob/client.go +++ b/sdk/storage/azblob/blob/client.go @@ -255,7 +255,7 @@ func (b *Client) GetSASURL(permissions sas.BlobPermissions, start time.Time, exp StartTime: start.UTC(), ExpiryTime: expiry.UTC(), - }.Sign(b.sharedKey()) + }.SignWithSharedKey(b.sharedKey()) if err != nil { return "", err diff --git a/sdk/storage/azblob/blob/client_test.go b/sdk/storage/azblob/blob/client_test.go index c5f6b36acb88..e6fb13f135ec 100644 --- a/sdk/storage/azblob/blob/client_test.go +++ b/sdk/storage/azblob/blob/client_test.go @@ -114,7 +114,7 @@ func (s *BlobUnrecordedTestsSuite) TestCreateBlobClientWithSnapshotAndSAS() { Permissions: to.Ptr(sas.AccountPermissions{Read: true, List: true}).String(), Services: to.Ptr(sas.AccountServices{Blob: true}).String(), ResourceTypes: to.Ptr(sas.AccountResourceTypes{Container: true, Object: true}).String(), - }.Sign(credential) + }.SignWithSharedKey(credential) _require.Nil(err) parts, err := blob.ParseURL(bbClient.URL()) @@ -156,7 +156,7 @@ func (s *BlobUnrecordedTestsSuite) TestCreateBlobClientWithSnapshotAndSASUsingCo Permissions: to.Ptr(sas.AccountPermissions{Read: true, List: true}).String(), Services: to.Ptr(sas.AccountServices{Blob: true}).String(), ResourceTypes: to.Ptr(sas.AccountResourceTypes{Container: true, Object: true}).String(), - }.Sign(credential) + }.SignWithSharedKey(credential) _require.Nil(err) parts, err := blob.ParseURL(bbClient.URL()) diff --git a/sdk/storage/azblob/blockblob/client_test.go b/sdk/storage/azblob/blockblob/client_test.go index ec355d53c5c6..057ea116fb98 100644 --- a/sdk/storage/azblob/blockblob/client_test.go +++ b/sdk/storage/azblob/blockblob/client_test.go @@ -1169,7 +1169,7 @@ func (s *BlockBlobUnrecordedTestsSuite) TestSetTierOnCopyBlockBlobFromURL() { Permissions: to.Ptr(sas.AccountPermissions{Read: true, List: true}).String(), Services: to.Ptr(sas.AccountServices{Blob: true}).String(), ResourceTypes: to.Ptr(sas.AccountResourceTypes{Container: true, Object: true}).String(), - }.Sign(credential) + }.SignWithSharedKey(credential) _require.Nil(err) srcBlobParts, _ := blob.ParseURL(srcBlob.URL()) diff --git a/sdk/storage/azblob/container/client.go b/sdk/storage/azblob/container/client.go index aa58acdce1b8..97339976709c 100644 --- a/sdk/storage/azblob/container/client.go +++ b/sdk/storage/azblob/container/client.go @@ -312,7 +312,7 @@ func (c *Client) GetSASURL(permissions sas.ContainerPermissions, start time.Time Permissions: permissions.String(), StartTime: start.UTC(), ExpiryTime: expiry.UTC(), - }.Sign(c.sharedKey()) + }.SignWithSharedKey(c.sharedKey()) if err != nil { return "", err } diff --git a/sdk/storage/azblob/internal/exported/user_delegation_credential.go b/sdk/storage/azblob/internal/exported/user_delegation_credential.go new file mode 100644 index 000000000000..b0aca86eea63 --- /dev/null +++ b/sdk/storage/azblob/internal/exported/user_delegation_credential.go @@ -0,0 +1,64 @@ +//go:build go1.18 +// +build go1.18 + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package exported + +import ( + "crypto/hmac" + "crypto/sha256" + "encoding/base64" + "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/internal/generated" +) + +// NewUserDelegationCredential creates a new UserDelegationCredential using a Storage account's Name and a user delegation Key from it +func NewUserDelegationCredential(accountName string, udk UserDelegationKey) *UserDelegationCredential { + return &UserDelegationCredential{ + accountName: accountName, + userDelegationKey: udk, + } +} + +// UserDelegationKey contains UserDelegationKey. +type UserDelegationKey = generated.UserDelegationKey + +// UserDelegationCredential contains an account's name and its user delegation key. +type UserDelegationCredential struct { + accountName string + userDelegationKey UserDelegationKey +} + +// AccountName returns the Storage account's Name +func (f *UserDelegationCredential) getAccountName() string { + return f.accountName +} + +// GetUDKParams is a helper method for accessing the user delegation key parameters outside of this package. +func GetAccountName(udc *UserDelegationCredential) string { + return udc.getAccountName() +} + +// computeHMACSHA256 generates a hash signature for an HTTP request or for a SAS. +func (f *UserDelegationCredential) computeHMACSHA256(message string) (string, error) { + bytes, _ := base64.StdEncoding.DecodeString(*f.userDelegationKey.Value) + h := hmac.New(sha256.New, bytes) + _, err := h.Write([]byte(message)) + return base64.StdEncoding.EncodeToString(h.Sum(nil)), err +} + +// ComputeUDCHMACSHA256 is a helper method for computing the signed string outside of this package. +func ComputeUDCHMACSHA256(udc *UserDelegationCredential, message string) (string, error) { + return udc.computeHMACSHA256(message) +} + +// GetUDKParams returns UserDelegationKey +func (f *UserDelegationCredential) getUDKParams() *UserDelegationKey { + return &f.userDelegationKey +} + +// GetUDKParams is a helper method for accessing the user delegation key parameters outside of this package. +func GetUDKParams(udc *UserDelegationCredential) *UserDelegationKey { + return udc.getUDKParams() +} diff --git a/sdk/storage/azblob/sas/account.go b/sdk/storage/azblob/sas/account.go index dd2dd0d915b7..fa76ed95c416 100644 --- a/sdk/storage/azblob/sas/account.go +++ b/sdk/storage/azblob/sas/account.go @@ -19,6 +19,9 @@ import ( // SharedKeyCredential contains an account's name and its primary or secondary key. type SharedKeyCredential = exported.SharedKeyCredential +// UserDelegationCredential contains an account's name and its user delegation key. +type UserDelegationCredential = exported.UserDelegationCredential + // AccountSignatureValues is used to generate a Shared Access Signature (SAS) for an Azure Storage account. // For more information, see https://docs.microsoft.com/rest/api/storageservices/constructing-an-account-sas type AccountSignatureValues struct { @@ -32,9 +35,9 @@ type AccountSignatureValues 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 +// SignWithSharedKey uses an account's shared key credential to sign this signature values to produce // the proper SAS query parameters. -func (v AccountSignatureValues) Sign(sharedKeyCredential *SharedKeyCredential) (QueryParameters, error) { +func (v AccountSignatureValues) SignWithSharedKey(sharedKeyCredential *SharedKeyCredential) (QueryParameters, error) { // https://docs.microsoft.com/en-us/rest/api/storageservices/Constructing-an-Account-SAS if v.ExpiryTime.IsZero() || v.Permissions == "" || v.ResourceTypes == "" || v.Services == "" { return QueryParameters{}, errors.New("account SAS is missing at least one of these: ExpiryTime, Permissions, Service, or ResourceType") @@ -87,6 +90,71 @@ func (v AccountSignatureValues) Sign(sharedKeyCredential *SharedKeyCredential) ( return p, nil } +// SignWithUserDelegation uses an account's UserDelegationKey to sign this signature values to produce the proper SAS query parameters. +func (v AccountSignatureValues) SignWithUserDelegation(userDelegationCredential *UserDelegationCredential) (QueryParameters, error) { + // https://docs.microsoft.com/en-us/rest/api/storageservices/Constructing-an-Account-SAS + if v.ExpiryTime.IsZero() || v.Permissions == "" || v.ResourceTypes == "" || v.Services == "" { + return QueryParameters{}, errors.New("account SAS is missing at least one of these: ExpiryTime, Permissions, Service, or ResourceType") + } + if v.Version == "" { + v.Version = Version + } + + perms, err := parseAccountPermissions(v.Permissions) + if err != nil { + return QueryParameters{}, err + } + v.Permissions = perms.String() + + startTime, expiryTime, _ := formatTimesForSigning(v.StartTime, v.ExpiryTime, time.Time{}) + + stringToSign := strings.Join([]string{ + exported.GetAccountName(userDelegationCredential), + 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 := exported.ComputeUDCHMACSHA256(userDelegationCredential, stringToSign) + if err != nil { + return QueryParameters{}, err + } + p := QueryParameters{ + // 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 := exported.GetUDKParams(userDelegationCredential) + + //User delegation SAS specific parameters + 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 +} + // AccountPermissions 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 AccountPermissions struct { diff --git a/sdk/storage/azblob/sas/service.go b/sdk/storage/azblob/sas/service.go index b6dd2090ff44..98d853d4e9f8 100644 --- a/sdk/storage/azblob/sas/service.go +++ b/sdk/storage/azblob/sas/service.go @@ -47,9 +47,8 @@ func getDirectoryDepth(path string) string { return fmt.Sprint(strings.Count(path, "/") + 1) } -// Sign 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 BlobSignatureValues) Sign(sharedKeyCredential *SharedKeyCredential) (QueryParameters, error) { +// SignWithSharedKey uses an account's SharedKeyCredential to sign this signature values to produce the proper SAS query parameters. +func (v BlobSignatureValues) SignWithSharedKey(sharedKeyCredential *SharedKeyCredential) (QueryParameters, error) { if sharedKeyCredential == nil { return QueryParameters{}, fmt.Errorf("cannot sign SAS query without Shared Key Credential") } @@ -82,25 +81,6 @@ func (v BlobSignatureValues) Sign(sharedKeyCredential *SharedKeyCredential) (Que 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, @@ -151,15 +131,115 @@ func (v BlobSignatureValues) Sign(sharedKeyCredential *SharedKeyCredential) (Que 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 +} + +// SignWithUserDelegation uses an account's UserDelegationCredential to sign this signature values to produce the proper SAS query parameters. +func (v BlobSignatureValues) SignWithUserDelegation(userDelegationCredential *UserDelegationCredential) (QueryParameters, error) { + if userDelegationCredential == nil { + return QueryParameters{}, fmt.Errorf("cannot sign SAS query without User Delegation Key") + } + + //Make sure the permission characters are in the correct order + perms, err := parseBlobPermissions(v.Permissions) + if err != nil { + return QueryParameters{}, err + } + v.Permissions = perms.String() + + resource := "c" + if !v.SnapshotTime.IsZero() { + resource = "bs" + } else if v.BlobVersion != "" { + resource = "bv" + } else if v.Directory != "" { + resource = "d" + v.BlobName = "" + } else if v.BlobName == "" { + // do nothing + } else { + resource = "b" + } + + if v.Version == "" { + v.Version = Version + } + startTime, expiryTime, snapshotTime := formatTimesForSigning(v.StartTime, v.ExpiryTime, v.SnapshotTime) + + udk := exported.GetUDKParams(userDelegationCredential) + + udkStart, udkExpiry, _ := formatTimesForSigning(*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, + startTime, + expiryTime, + getCanonicalName(exported.GetAccountName(userDelegationCredential), 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, err := exported.ComputeUDCHMACSHA256(userDelegationCredential, stringToSign) + if err != nil { + return QueryParameters{}, err + } + + p := QueryParameters{ + // 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 + 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 } diff --git a/sdk/storage/azblob/service/client.go b/sdk/storage/azblob/service/client.go index 69740e47680d..d206f0341076 100644 --- a/sdk/storage/azblob/service/client.go +++ b/sdk/storage/azblob/service/client.go @@ -9,6 +9,7 @@ package service import ( "context" "errors" + "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blob" "net/http" "strings" "time" @@ -82,6 +83,23 @@ func NewClientFromConnectionString(connectionString string, options *ClientOptio return NewClientWithNoCredential(parsed.ServiceURL, options) } +// GetUserDelegationCredential obtains a UserDelegationKey object using the base ServiceURL object. +// OAuth is required for this call, as well as any role that can delegate access to the storage account. +func (s *Client) GetUserDelegationCredential(ctx context.Context, info KeyInfo, o *GetUserDelegationCredentialOptions) (*UserDelegationCredential, error) { + url, err := blob.ParseURL(s.URL()) + if err != nil { + return nil, err + } + + getUserDelegationKeyOptions := o.format() + udk, err := s.generated().GetUserDelegationKey(ctx, info, getUserDelegationKeyOptions) + if err != nil { + return nil, err + } + + return exported.NewUserDelegationCredential(strings.Split(url.Host, ".")[0], udk.UserDelegationKey), nil +} + func (s *Client) generated() *generated.ServiceClient { return base.InnerClient((*base.Client[generated.ServiceClient])(s)) } @@ -235,7 +253,7 @@ func (s *Client) GetSASURL(resources sas.AccountResourceTypes, permissions sas.A ResourceTypes: resources.String(), StartTime: start.UTC(), ExpiryTime: expiry.UTC(), - }.Sign(s.sharedKey()) + }.SignWithSharedKey(s.sharedKey()) if err != nil { return "", err } diff --git a/sdk/storage/azblob/service/client_test.go b/sdk/storage/azblob/service/client_test.go index 758bc9180cf6..491dd3048478 100644 --- a/sdk/storage/azblob/service/client_test.go +++ b/sdk/storage/azblob/service/client_test.go @@ -600,6 +600,57 @@ func (s *ServiceUnrecordedTestsSuite) TestSASContainerClient2() { //_require.Nil(err) } +/*func (s *ServiceRecordedTestsSuite) TestUserDelegationSAS() { + _require := require.New(s.T()) + testName := s.T().Name() + accountName := os.Getenv("AZURE_STORAGE_ACCOUNT_NAME") + + optsClientID := azidentity.ManagedIdentityCredentialOptions{ID: azidentity.ClientID("cf482fac-3a0b-11ed-a261-0242ac120002")} + cred, err := azidentity.NewManagedIdentityCredential(&optsClientID) + _require.Nil(err) + + svcClient, err := azblob.NewClient(fmt.Sprintf("https://%s.blob.core.windows.net/", accountName), cred, &azblob.ClientOptions{}) + _require.Nil(err) + + // Set current and past time, create KeyInfo + currentTime := time.Now().UTC().Add(-10 * time.Second) + pastTime := currentTime.Add(48 * time.Hour) + _require.Nil(err) + info := generated.KeyInfo{ + Start: to.Ptr(currentTime.UTC().Format(sas.TimeFormat)), + Expiry: to.Ptr(pastTime.UTC().Format(sas.TimeFormat)), + } + + // Get UserDelegationCredential + udc, err := svcClient.GetUserDelegationCredential(context.Background(), info, nil) + _require.Nil(err) + + csas, err := sas.AccountSignatureValues{ + Protocol: sas.ProtocolHTTPS, + ExpiryTime: pastTime.UTC(), + Permissions: to.Ptr(sas.AccountPermissions{Read: true, List: true}).String(), + Services: to.Ptr(sas.AccountServices{Blob: true}).String(), + ResourceTypes: to.Ptr(sas.AccountResourceTypes{Container: true, Object: true}).String(), + }.SignWithUserDelegation(udc) + _require.Nil(err) + + sasURL := svcClient.URL() + if !strings.HasSuffix(sasURL, "/") { + sasURL += "/" + } + sasURL += "?" + csas.Encode() + + containerName := testcommon.GenerateContainerName(testName) + sc, err := service.NewClientWithNoCredential(sasURL, nil) + _require.Nil(err) + + _, err = sc.CreateContainer(context.Background(), containerName+"002", nil) + _require.Nil(err) + + _, err = sc.DeleteContainer(context.Background(), containerName+"002", nil) + _require.Nil(err) +}*/ + // make sure that container soft delete is enabled // TODO: convert this test to recorded func (s *ServiceUnrecordedTestsSuite) TestContainerRestore() { diff --git a/sdk/storage/azblob/service/examples_test.go b/sdk/storage/azblob/service/examples_test.go index a552de0660cc..e7ce4250555e 100644 --- a/sdk/storage/azblob/service/examples_test.go +++ b/sdk/storage/azblob/service/examples_test.go @@ -9,6 +9,8 @@ package service_test import ( "context" "fmt" + "github.com/Azure/azure-sdk-for-go/sdk/azcore" + "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/internal/generated" "log" "os" "time" @@ -267,7 +269,7 @@ func Example_service_SASSignatureValues_Sign() { Permissions: to.Ptr(sas.AccountPermissions{Read: true, List: true}).String(), Services: to.Ptr(sas.AccountServices{Blob: true}).String(), ResourceTypes: to.Ptr(sas.AccountResourceTypes{Container: true, Object: true}).String(), - }.Sign(credential) + }.SignWithSharedKey(credential) handleError(err) sasURL := fmt.Sprintf("https://%s.blob.core.windows.net/?%s", accountName, sasQueryParams.Encode()) @@ -280,3 +282,85 @@ func Example_service_SASSignatureValues_Sign() { blobURLParts, _ := blob.ParseURL(serviceClient.URL()) fmt.Printf("SAS expiry time = %s\n", blobURLParts.SAS.ExpiryTime()) } + +func Example_service_Client_NewClientWithUserDelegationCredential() { + accountName, ok := os.LookupEnv("AZURE_STORAGE_ACCOUNT_NAME") + if !ok { + panic("AZURE_STORAGE_ACCOUNT_NAME could not be found") + } + // Create Managed Identity (OAuth) Credentials using Client ID + clientOptions := azcore.ClientOptions{} // Fill clientOptions as needed + optsClientID := azidentity.ManagedIdentityCredentialOptions{ClientOptions: clientOptions, ID: azidentity.ClientID("7cf7db0d-...")} + cred, err := azidentity.NewManagedIdentityCredential(&optsClientID) + handleError(err) + clientOptionsService := service.ClientOptions{} // Same as azcore.ClientOptions using service instead + + svcClient, err := service.NewClient(fmt.Sprintf("https://%s.blob.core.windows.net/", accountName), cred, &clientOptionsService) + handleError(err) + + // Set current and past time and create key + currentTime := time.Now().UTC().Add(-10 * time.Second) + pastTime := currentTime.Add(48 * time.Hour) + info := generated.KeyInfo{ + Start: to.Ptr(currentTime.UTC().Format(sas.TimeFormat)), + Expiry: to.Ptr(pastTime.UTC().Format(sas.TimeFormat)), + } + + udc, err := svcClient.GetUserDelegationCredential(context.Background(), info, nil) + handleError(err) + + fmt.Println("User Delegation Key has been created for ", accountName) + + // Create Account Signature Values with desired permissions and sign with user delegation credential + sasQueryParams, err := sas.AccountSignatureValues{ + Protocol: sas.ProtocolHTTPS, + ExpiryTime: time.Now().UTC().Add(48 * time.Hour), + Permissions: to.Ptr(sas.AccountPermissions{Read: true, List: true}).String(), + Services: to.Ptr(sas.AccountServices{Blob: true}).String(), + ResourceTypes: to.Ptr(sas.AccountResourceTypes{Container: true, Object: true}).String(), + }.SignWithUserDelegation(udc) + handleError(err) + + sasURL := fmt.Sprintf("https://%s.blob.core.windows.net/?%s", accountName, sasQueryParams.Encode()) + + // This URL can be used to authenticate requests now + serviceClient, err := service.NewClientWithNoCredential(sasURL, nil) + handleError(err) + + // You can also break a blob URL up into it's constituent parts + blobURLParts, _ := blob.ParseURL(serviceClient.URL()) + fmt.Printf("SAS expiry time = %s\n", blobURLParts.SAS.ExpiryTime()) + + // Create Managed Identity (OAuth) Credentials using Resource ID + optsResourceID := azidentity.ManagedIdentityCredentialOptions{ClientOptions: clientOptions, ID: azidentity.ResourceID("/subscriptions/...")} + cred, err = azidentity.NewManagedIdentityCredential(&optsResourceID) + handleError(err) + + svcClient, err = service.NewClient("svcURL", cred, &clientOptionsService) + handleError(err) + + udc, err = svcClient.GetUserDelegationCredential(context.Background(), info, nil) + handleError(err) + fmt.Println("User Delegation Key has been created for ", accountName) + + // Create Account Signature Values with desired permissions and sign with user delegation credential + sasQueryParams, err = sas.AccountSignatureValues{ + Protocol: sas.ProtocolHTTPS, + ExpiryTime: time.Now().UTC().Add(48 * time.Hour), + Permissions: to.Ptr(sas.AccountPermissions{Read: true, List: true}).String(), + Services: to.Ptr(sas.AccountServices{Blob: true}).String(), + ResourceTypes: to.Ptr(sas.AccountResourceTypes{Container: true, Object: true}).String(), + }.SignWithUserDelegation(udc) + handleError(err) + + sasURL = fmt.Sprintf("https://%s.blob.core.windows.net/?%s", accountName, sasQueryParams.Encode()) + + // This URL can be used to authenticate requests now + serviceClient, err = service.NewClientWithNoCredential(sasURL, nil) + handleError(err) + + // You can also break a blob URL up into it's constituent parts + blobURLParts, _ = blob.ParseURL(serviceClient.URL()) + fmt.Printf("SAS expiry time = %s\n", blobURLParts.SAS.ExpiryTime()) + +} diff --git a/sdk/storage/azblob/service/models.go b/sdk/storage/azblob/service/models.go index 5c3c0f1dc7f0..b0354cd90e3e 100644 --- a/sdk/storage/azblob/service/models.go +++ b/sdk/storage/azblob/service/models.go @@ -21,6 +21,24 @@ 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 + +// UserDelegationKey contains UserDelegationKey. +type UserDelegationKey = generated.UserDelegationKey + +// KeyInfo contains KeyInfo struct. +type KeyInfo = generated.KeyInfo + +// GetUserDelegationCredentialOptions contains optional parameters for Service.GetUserDelegationKey method +type GetUserDelegationCredentialOptions struct { + // placeholder for future options +} + +func (o *GetUserDelegationCredentialOptions) format() *generated.ServiceClientGetUserDelegationKeyOptions { + return nil +} + // AccessConditions identifies container-specific access conditions which you optionally set. type AccessConditions = exported.ContainerAccessConditions diff --git a/sdk/storage/azblob/service/responses.go b/sdk/storage/azblob/service/responses.go index 26e3c085bd90..788514cca372 100644 --- a/sdk/storage/azblob/service/responses.go +++ b/sdk/storage/azblob/service/responses.go @@ -36,3 +36,6 @@ type GetStatisticsResponse = generated.ServiceClientGetStatisticsResponse // FilterBlobsResponse contains the response from method Client.FilterBlobs. type FilterBlobsResponse = generated.ServiceClientFilterBlobsResponse + +// GetUserDelegationKeyResponse contains the response from method ServiceClient.GetUserDelegationKey. +type GetUserDelegationKeyResponse = generated.ServiceClientGetUserDelegationKeyResponse