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 21 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
2 changes: 1 addition & 1 deletion sdk/storage/azblob/blob/client.go
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions sdk/storage/azblob/blob/client_test.go
Expand Up @@ -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())
Expand Down Expand Up @@ -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())
Expand Down
2 changes: 1 addition & 1 deletion sdk/storage/azblob/blockblob/client_test.go
Expand Up @@ -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())
Expand Down
2 changes: 1 addition & 1 deletion sdk/storage/azblob/container/client.go
Expand Up @@ -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
}
Expand Down
@@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment isn't right

func (f *UserDelegationCredential) getAccountName() string {
return f.accountName
jhendrixMSFT marked this conversation as resolved.
Show resolved Hide resolved
}

// GetUDKParams is a helper method for accessing the user delegation key parameters outside of this package.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment isn't right

func GetAccountName(udc *UserDelegationCredential) string {
jhendrixMSFT marked this conversation as resolved.
Show resolved Hide resolved
return udc.getAccountName()
}

// computeHMACSHA256 generates a hash signature for an HTTP request or for a SAS.
func (f *UserDelegationCredential) computeHMACSHA256(message string) (string, error) {
jhendrixMSFT marked this conversation as resolved.
Show resolved Hide resolved
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) {
jhendrixMSFT marked this conversation as resolved.
Show resolved Hide resolved
return udc.computeHMACSHA256(message)
jhendrixMSFT marked this conversation as resolved.
Show resolved Hide resolved
}

// GetUDKParams returns UserDelegationKey
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment isn't right

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()
jhendrixMSFT marked this conversation as resolved.
Show resolved Hide resolved
}
72 changes: 70 additions & 2 deletions sdk/storage/azblob/sas/account.go
Expand Up @@ -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 {
Expand All @@ -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")
Expand Down Expand Up @@ -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 {
Expand Down
144 changes: 113 additions & 31 deletions sdk/storage/azblob/sas/service.go
Expand Up @@ -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")
}
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -151,15 +131,117 @@ 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)

signedIdentifier := v.Identifier

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
}
Expand Down