diff --git a/sdk/storage/azblob/CHANGELOG.md b/sdk/storage/azblob/CHANGELOG.md index ce00eedd9918..d3fd329c1f0c 100644 --- a/sdk/storage/azblob/CHANGELOG.md +++ b/sdk/storage/azblob/CHANGELOG.md @@ -6,7 +6,8 @@ * Added support for [Cold tier](https://learn.microsoft.com/azure/storage/blobs/access-tiers-overview?tabs=azure-portal). * Added `CopySourceTag` option for `UploadBlobFromURLOptions` * Added [FilterBlobs](https://learn.microsoft.com/rest/api/storageservices/find-blobs-by-tags-container) by tags API for container client. -* Added `System` option to `ListContainersInclude` to allow listing of system containers. +* Added `System` option to `ListContainersInclude` to allow listing of system containers. +* Updated the SAS Version to `2021-12-02` and added `Encryption Scope` to Account SAS, Service SAS, and User Delegation SAS ### Breaking Changes diff --git a/sdk/storage/azblob/appendblob/client_test.go b/sdk/storage/azblob/appendblob/client_test.go index 2d650e492244..9fd8a3ac90bf 100644 --- a/sdk/storage/azblob/appendblob/client_test.go +++ b/sdk/storage/azblob/appendblob/client_test.go @@ -12,6 +12,7 @@ import ( "crypto/md5" "encoding/binary" "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" + "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/service" "hash/crc64" "io" "math/rand" @@ -371,6 +372,149 @@ func (s *AppendBlobUnrecordedTestsSuite) TestAppendBlockFromURL() { _require.Equal(destBuffer, sourceData) } +func (s *AppendBlobUnrecordedTestsSuite) TestBlobEncryptionScopeSAS() { + _require := require.New(s.T()) + testName := s.T().Name() + svcClient, err := testcommon.GetServiceClient(s.T(), testcommon.TestAccountDefault, nil) + _require.NoError(err) + + containerName := testcommon.GenerateContainerName(testName) + containerClient := testcommon.CreateNewContainer(context.Background(), _require, containerName, svcClient) + defer testcommon.DeleteContainer(context.Background(), _require, containerClient) + + blobClient := containerClient.NewAppendBlobClient(testcommon.GenerateBlobName("appendsrc")) + + // Get source abClient URL with SAS for AppendBlockFromURL. + blobParts, _ := blob.ParseURL(blobClient.URL()) + + encryptionScope, err := testcommon.GetRequiredEnv(testcommon.EncryptionScopeEnvVar) + _require.Nil(err) + credential, err := testcommon.GetGenericSharedKeyCredential(testcommon.TestAccountDefault) + _require.Nil(err) + perms := sas.BlobPermissions{Read: true, Create: true, Write: true, Delete: true} + + blobParts.SAS, err = sas.BlobSignatureValues{ + Protocol: sas.ProtocolHTTPS, // Users MUST use HTTPS (not HTTP) + ExpiryTime: time.Now().UTC().Add(48 * time.Hour), // 48-hours before expiration + ContainerName: blobParts.ContainerName, + BlobName: blobParts.BlobName, + Permissions: perms.String(), + EncryptionScope: encryptionScope, + }.SignWithSharedKey(credential) + _require.NoError(err) + + blobURLWithSAS := blobParts.String() + + // create new client with sas url + blobClient, err = appendblob.NewClientWithNoCredential(blobURLWithSAS, nil) + _require.Nil(err) + + createResponse, err := blobClient.Create(context.Background(), nil) + _require.NoError(err) + _require.Equal(*createResponse.EncryptionScope, encryptionScope) +} + +func (s *AppendBlobUnrecordedTestsSuite) TestAccountEncryptionScopeSAS() { + _require := require.New(s.T()) + testName := s.T().Name() + svcClient, err := testcommon.GetServiceClient(s.T(), testcommon.TestAccountDefault, nil) + _require.NoError(err) + + containerName := testcommon.GenerateContainerName(testName) + containerClient := testcommon.CreateNewContainer(context.Background(), _require, containerName, svcClient) + defer testcommon.DeleteContainer(context.Background(), _require, containerClient) + + blobName := testcommon.GenerateBlobName("appendsrc") + blobClient := containerClient.NewAppendBlobClient(blobName) + + // Get blob URL with SAS for AppendBlockFromURL. + blobParts, _ := blob.ParseURL(blobClient.URL()) + + encryptionScope, err := testcommon.GetRequiredEnv(testcommon.EncryptionScopeEnvVar) + _require.Nil(err) + + credential, err := testcommon.GetGenericSharedKeyCredential(testcommon.TestAccountDefault) + _require.Nil(err) + + blobParts.SAS, err = sas.AccountSignatureValues{ + Protocol: sas.ProtocolHTTPS, // Users MUST use HTTPS (not HTTP) + ExpiryTime: time.Now().UTC().Add(48 * time.Hour), // 48-hours before expiration + Permissions: to.Ptr(sas.AccountPermissions{Read: true, Create: true, Write: true, Delete: true}).String(), + ResourceTypes: to.Ptr(sas.AccountResourceTypes{Service: true, Container: true, Object: true}).String(), + EncryptionScope: encryptionScope, + }.SignWithSharedKey(credential) + _require.NoError(err) + + blobURLWithSAS := blobParts.String() + blobClient, err = appendblob.NewClientWithNoCredential(blobURLWithSAS, nil) + _require.NoError(err) + + createResp, err := blobClient.Create(context.Background(), nil) + _require.NoError(err) + _require.NotNil(createResp) + _require.Equal(*createResp.EncryptionScope, encryptionScope) +} + +func (s *AppendBlobUnrecordedTestsSuite) TestGetUserDelegationEncryptionScopeSAS() { + _require := require.New(s.T()) + testName := s.T().Name() + accountName, _ := testcommon.GetGenericAccountInfo(testcommon.TestAccountDefault) + _require.Greater(len(accountName), 0) + + cred, err := testcommon.GetGenericTokenCredential() + _require.NoError(err) + + svcClient, err := service.NewClient("https://"+accountName+".blob.core.windows.net/", cred, nil) + _require.NoError(err) + + containerName := testcommon.GenerateContainerName(testName) + cntClientTokenCred := testcommon.CreateNewContainer(context.Background(), _require, containerName, svcClient) + defer testcommon.DeleteContainer(context.Background(), _require, cntClientTokenCred) + + blobName := testcommon.GenerateBlobName("appendsrc") + blobClient := cntClientTokenCred.NewAppendBlobClient(blobName) + + // Set current and past time and create key + now := time.Now().UTC().Add(-10 * time.Second) + expiry := now.Add(2 * time.Hour) + info := service.KeyInfo{ + Start: to.Ptr(now.UTC().Format(sas.TimeFormat)), + Expiry: to.Ptr(expiry.UTC().Format(sas.TimeFormat)), + } + + udc, err := svcClient.GetUserDelegationCredential(context.Background(), info, nil) + _require.NoError(err) + + // get permissions and details for sas + encryptionScope, err := testcommon.GetRequiredEnv(testcommon.EncryptionScopeEnvVar) + _require.Nil(err) + + permissions := sas.BlobPermissions{Read: true, Create: true, Write: true, List: true, Add: true, Delete: true} + + blobParts, _ := blob.ParseURL(blobClient.URL()) + + // Create Blob Signature Values with desired permissions and sign with user delegation credential + blobParts.SAS, err = sas.BlobSignatureValues{ + Protocol: sas.ProtocolHTTPS, + StartTime: time.Now().UTC().Add(time.Second * -10), + ExpiryTime: time.Now().UTC().Add(15 * time.Minute), + Permissions: permissions.String(), + ContainerName: containerName, + EncryptionScope: encryptionScope, + }.SignWithUserDelegation(udc) + _require.NoError(err) + + blobURLWithSAS := blobParts.String() + blobClient, err = appendblob.NewClientWithNoCredential(blobURLWithSAS, nil) + _require.NoError(err) + + createResp, err := blobClient.Create(context.Background(), nil) + _require.NoError(err) + _require.NotNil(createResp) + _require.Equal(*createResp.EncryptionScope, encryptionScope) + +} + func (s *AppendBlobUnrecordedTestsSuite) TestAppendBlockFromURLWithMD5() { _require := require.New(s.T()) testName := s.T().Name() diff --git a/sdk/storage/azblob/sas/account.go b/sdk/storage/azblob/sas/account.go index 454a08cb9e58..4069bb132023 100644 --- a/sdk/storage/azblob/sas/account.go +++ b/sdk/storage/azblob/sas/account.go @@ -25,13 +25,14 @@ 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 { - Version string `param:"sv"` // If not specified, this format to SASVersion - Protocol Protocol `param:"spr"` // See the SASProtocol* constants - StartTime time.Time `param:"st"` // Not specified if IsZero - ExpiryTime time.Time `param:"se"` // Not specified if IsZero - Permissions string `param:"sp"` // Create by initializing AccountPermissions and then call String() - IPRange IPRange `param:"sip"` - ResourceTypes string `param:"srt"` // Create by initializing AccountResourceTypes and then call String() + Version string `param:"sv"` // If not specified, this format to SASVersion + Protocol Protocol `param:"spr"` // See the SASProtocol* constants + StartTime time.Time `param:"st"` // Not specified if IsZero + ExpiryTime time.Time `param:"se"` // Not specified if IsZero + Permissions string `param:"sp"` // Create by initializing AccountPermissions and then call String() + IPRange IPRange `param:"sip"` + ResourceTypes string `param:"srt"` // Create by initializing AccountResourceTypes and then call String() + EncryptionScope string `param:"ses"` } // SignWithSharedKey uses an account's shared key credential to sign this signature values to produce @@ -68,6 +69,7 @@ func (v AccountSignatureValues) SignWithSharedKey(sharedKeyCredential *SharedKey v.IPRange.String(), string(v.Protocol), v.Version, + v.EncryptionScope, ""}, // That is right, the account SAS requires a terminating extra newline "\n") @@ -77,12 +79,13 @@ func (v AccountSignatureValues) SignWithSharedKey(sharedKeyCredential *SharedKey } p := QueryParameters{ // Common SAS parameters - version: v.Version, - protocol: v.Protocol, - startTime: v.StartTime, - expiryTime: v.ExpiryTime, - permissions: v.Permissions, - ipRange: v.IPRange, + version: v.Version, + protocol: v.Protocol, + startTime: v.StartTime, + expiryTime: v.ExpiryTime, + permissions: v.Permissions, + ipRange: v.IPRange, + encryptionScope: v.EncryptionScope, // Account-specific SAS parameters services: "b", // will always be "b" diff --git a/sdk/storage/azblob/sas/query_params.go b/sdk/storage/azblob/sas/query_params.go index 4d97372da6d0..4c23208e2e14 100644 --- a/sdk/storage/azblob/sas/query_params.go +++ b/sdk/storage/azblob/sas/query_params.go @@ -23,7 +23,7 @@ const ( var ( // Version is the default version encoded in the SAS token. - Version = "2020-02-10" + Version = "2021-12-02" ) // TimeFormats ISO 8601 format. @@ -143,6 +143,7 @@ type QueryParameters struct { authorizedObjectID string `param:"saoid"` unauthorizedObjectID string `param:"suoid"` correlationID string `param:"scid"` + encryptionScope string `param:"ses"` // private member used for startTime and expiryTime formatting. stTimeFormat string seTimeFormat string @@ -163,6 +164,11 @@ func (p *QueryParameters) SignedCorrelationID() string { return p.correlationID } +// EncryptionScope returns encryptionScope +func (p *QueryParameters) EncryptionScope() string { + return p.encryptionScope +} + // SignedOID returns signedOID. func (p *QueryParameters) SignedOID() string { return p.signedOID @@ -355,6 +361,9 @@ func (p *QueryParameters) Encode() string { if p.correlationID != "" { v.Add("scid", p.correlationID) } + if p.encryptionScope != "" { + v.Add("ses", p.encryptionScope) + } return v.Encode() } @@ -429,6 +438,8 @@ func NewQueryParameters(values url.Values, deleteSASParametersFromValues bool) Q p.unauthorizedObjectID = val case "scid": p.correlationID = val + case "ses": + p.encryptionScope = val default: isSASKey = false // We didn't recognize the query parameter } diff --git a/sdk/storage/azblob/sas/service.go b/sdk/storage/azblob/sas/service.go index 8f58dc76808a..45f730847d28 100644 --- a/sdk/storage/azblob/sas/service.go +++ b/sdk/storage/azblob/sas/service.go @@ -40,6 +40,7 @@ type BlobSignatureValues struct { AuthorizedObjectID string // saoid UnauthorizedObjectID string // suoid CorrelationID string // scid + EncryptionScope string `param:"ses"` } func getDirectoryDepth(path string) string { @@ -103,7 +104,8 @@ func (v BlobSignatureValues) SignWithSharedKey(sharedKeyCredential *SharedKeyCre string(v.Protocol), v.Version, resource, - snapshotTime, // signed timestamp + snapshotTime, // signed timestamp + v.EncryptionScope, v.CacheControl, // rscc v.ContentDisposition, // rscd v.ContentEncoding, // rsce @@ -118,12 +120,13 @@ func (v BlobSignatureValues) SignWithSharedKey(sharedKeyCredential *SharedKeyCre p := QueryParameters{ // Common SAS parameters - version: v.Version, - protocol: v.Protocol, - startTime: v.StartTime, - expiryTime: v.ExpiryTime, - permissions: v.Permissions, - ipRange: v.IPRange, + version: v.Version, + protocol: v.Protocol, + startTime: v.StartTime, + expiryTime: v.ExpiryTime, + permissions: v.Permissions, + ipRange: v.IPRange, + encryptionScope: v.EncryptionScope, // Container/Blob-specific SAS parameters resource: resource, @@ -211,7 +214,8 @@ func (v BlobSignatureValues) SignWithUserDelegation(userDelegationCredential *Us string(v.Protocol), v.Version, resource, - snapshotTime, // signed timestamp + snapshotTime, // signed timestamp + v.EncryptionScope, v.CacheControl, // rscc v.ContentDisposition, // rscd v.ContentEncoding, // rsce @@ -226,12 +230,13 @@ func (v BlobSignatureValues) SignWithUserDelegation(userDelegationCredential *Us p := QueryParameters{ // Common SAS parameters - version: v.Version, - protocol: v.Protocol, - startTime: v.StartTime, - expiryTime: v.ExpiryTime, - permissions: v.Permissions, - ipRange: v.IPRange, + version: v.Version, + protocol: v.Protocol, + startTime: v.StartTime, + expiryTime: v.ExpiryTime, + permissions: v.Permissions, + ipRange: v.IPRange, + encryptionScope: v.EncryptionScope, // Container/Blob-specific SAS parameters resource: resource,