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 1 commit
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
17 changes: 17 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"
)

// SharedKeyCredential contains an account's name and its primary or secondary key.
Expand All @@ -19,6 +20,19 @@ func NewSharedKeyCredential(accountName, accountKey string) (*SharedKeyCredentia
return exported.NewSharedKeyCredential(accountName, accountKey)
}

// UserDelegationCredntial 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)
}

type UserDelegationKey = generated.UserDelegationKey

// StorageAccountCredential is a wrapper interface for SharedKeyCredential and UserDelegationCredential
type StorageAccountCredential = exported.StorageAccountCredential

// IPEndpointStyleInfo is used for IP endpoint style URL when working with Azure storage emulator.
// Ex: "https://10.132.141.33/accountname/containername"
type IPEndpointStyleInfo = exported.IPEndpointStyleInfo
Expand Down Expand Up @@ -46,3 +60,6 @@ type SASProtocol = exported.SASProtocol

// IPRange represents a SAS IP range's start IP and (optionally) end IP.
type IPRange = exported.IPRange

// KeyInfo - Key Information
type KeyInfo = generated.KeyInfo
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
29 changes: 27 additions & 2 deletions sdk/storage/azblob/internal/base/clients.go
Expand Up @@ -13,8 +13,9 @@ import (
)

type Client[T any] struct {
inner *T
sharedKey *exported.SharedKeyCredential
inner *T
sharedKey *exported.SharedKeyCredential
userDelegationKey *exported.UserDelegationKey
}

func InnerClient[T any](client *Client[T]) *T {
Expand Down Expand Up @@ -50,6 +51,30 @@ func NewBlobClient(blobURL string, pipeline runtime.Pipeline, sharedKey *exporte
}
}

func NewServiceClientUDK(containerURL string, pipeline runtime.Pipeline, sharedKey *exported.SharedKeyCredential, udk *exported.UserDelegationKey) *Client[generated.ServiceClient] {
siminsavani-msft marked this conversation as resolved.
Show resolved Hide resolved
return &Client[generated.ServiceClient]{
inner: generated.NewServiceClient(containerURL, pipeline),
sharedKey: sharedKey,
userDelegationKey: udk,
}
}

func NewContainerClientUDK(containerURL string, pipeline runtime.Pipeline, sharedKey *exported.SharedKeyCredential, udk *exported.UserDelegationKey) *Client[generated.ContainerClient] {
return &Client[generated.ContainerClient]{
inner: generated.NewContainerClient(containerURL, pipeline),
sharedKey: sharedKey,
userDelegationKey: udk,
}
}

func NewBlobClientUDK(blobURL string, pipeline runtime.Pipeline, sharedKey *exported.SharedKeyCredential, udk *exported.UserDelegationKey) *Client[generated.BlobClient] {
return &Client[generated.BlobClient]{
inner: generated.NewBlobClient(blobURL, pipeline),
sharedKey: sharedKey,
userDelegationKey: udk,
}
}

type CompositeClient[T, U any] struct {
innerT *T
innerU *U
Expand Down
2 changes: 1 addition & 1 deletion sdk/storage/azblob/internal/exported/account_sas.go
Expand Up @@ -58,7 +58,7 @@ func (v AccountSASSignatureValues) Sign(sharedKeyCredential *SharedKeyCredential
""}, // That is right, the account SAS requires a terminating extra newline
"\n")

signature, err := sharedKeyCredential.computeHMACSHA256(stringToSign)
signature, err := sharedKeyCredential.ComputeHMACSHA256(stringToSign)
if err != nil {
return SASQueryParameters{}, err
}
Expand Down
60 changes: 29 additions & 31 deletions sdk/storage/azblob/internal/exported/service_sas.go
Expand Up @@ -47,9 +47,9 @@ func getDirectoryDepth(path string) string {

// 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 BlobSASSignatureValues) Sign(sharedKeyCredential *SharedKeyCredential) (SASQueryParameters, error) {
func (v BlobSASSignatureValues) Sign(credential StorageAccountCredential) (SASQueryParameters, error) {
resource := "c"
if sharedKeyCredential == nil {
if credential == nil {
return SASQueryParameters{}, fmt.Errorf("cannot sign SAS query without Shared Key Credential")
}

Expand Down Expand Up @@ -100,31 +100,29 @@ func (v BlobSASSignatureValues) Sign(sharedKeyCredential *SharedKeyCredential) (

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")
//}
udk := credential.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(sharedKeyCredential.AccountName(), v.ContainerName, v.BlobName, v.Directory),
getCanonicalName(credential.AccountName(), v.ContainerName, v.BlobName, v.Directory),
signedIdentifier,
v.IPRange.String(),
string(v.Protocol),
Expand All @@ -139,7 +137,7 @@ func (v BlobSASSignatureValues) Sign(sharedKeyCredential *SharedKeyCredential) (
"\n")

signature := ""
signature, err := sharedKeyCredential.computeHMACSHA256(stringToSign)
signature, err := credential.ComputeHMACSHA256(stringToSign)
if err != nil {
return SASQueryParameters{}, err
}
Expand Down Expand Up @@ -170,15 +168,15 @@ func (v BlobSASSignatureValues) Sign(sharedKeyCredential *SharedKeyCredential) (
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
//}
//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
10 changes: 8 additions & 2 deletions sdk/storage/azblob/internal/exported/shared_key_credential.go
Expand Up @@ -12,6 +12,7 @@ import (
"crypto/sha256"
"encoding/base64"
"fmt"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/internal/generated"
"net/http"
"net/url"
"sort"
Expand Down Expand Up @@ -58,7 +59,7 @@ func (c *SharedKeyCredential) SetAccountKey(accountKey string) error {
}

// ComputeHMACSHA256 generates a hash signature for an HTTP request or for a SAS.
func (c *SharedKeyCredential) computeHMACSHA256(message string) (string, error) {
func (c *SharedKeyCredential) ComputeHMACSHA256(message string) (string, error) {
h := hmac.New(sha256.New, c.accountKey.Load().([]byte))
_, err := h.Write([]byte(message))
return base64.StdEncoding.EncodeToString(h.Sum(nil)), err
Expand Down Expand Up @@ -178,6 +179,11 @@ func (c *SharedKeyCredential) buildCanonicalizedResource(u *url.URL) (string, er
return cr.String(), nil
}

// noop function to satisfy StorageAccountCredential interface
func (f *SharedKeyCredential) getUDKParams() *generated.UserDelegationKey {
return nil
}

// the following content isn't actually exported but must live
// next to SharedKeyCredential as it uses its unexported methods

Expand All @@ -197,7 +203,7 @@ func (s *SharedKeyCredPolicy) Do(req *policy.Request) (*http.Response, error) {
if err != nil {
return nil, err
}
signature, err := s.cred.computeHMACSHA256(stringToSign)
signature, err := s.cred.ComputeHMACSHA256(stringToSign)
if err != nil {
return nil, err
}
Expand Down
16 changes: 16 additions & 0 deletions sdk/storage/azblob/internal/exported/storage_account_credential.go
@@ -0,0 +1,16 @@
//go:build go1.18
// +build go1.18

// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

package exported

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

// StorageAccountCredential is a wrapper interface for SharedKeyCredential and UserDelegationCredential
type StorageAccountCredential interface {
siminsavani-msft marked this conversation as resolved.
Show resolved Hide resolved
AccountName() string
ComputeHMACSHA256(message string) (string, error)
getUDKParams() *generated.UserDelegationKey
}
@@ -0,0 +1,47 @@
//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, key generated.UserDelegationKey) *UserDelegationCredential {
return &UserDelegationCredential{
accountName: accountName,
accountKey: key,
}
}

type UserDelegationCredential struct {
accountName string
accountKey generated.UserDelegationKey
}

// AccountName returns the Storage account's name
func (f *UserDelegationCredential) AccountName() string {
siminsavani-msft marked this conversation as resolved.
Show resolved Hide resolved
return f.accountName
jhendrixMSFT marked this conversation as resolved.
Show resolved Hide resolved
}

// ComputeHMAC
func (f *UserDelegationCredential) ComputeHMACSHA256(message string) (string, error) {
siminsavani-msft marked this conversation as resolved.
Show resolved Hide resolved
bytes, _ := base64.StdEncoding.DecodeString(*f.accountKey.Value)
h := hmac.New(sha256.New, bytes)
_, err := h.Write([]byte(message))
return base64.StdEncoding.EncodeToString(h.Sum(nil)), err
}

// Private method to return important parameters for NewSASQueryParameters
func (f *UserDelegationCredential) getUDKParams() *generated.UserDelegationKey {
return &f.accountKey
}

type UserDelegationKey = generated.UserDelegationKey
26 changes: 26 additions & 0 deletions sdk/storage/azblob/service/client.go
Expand Up @@ -81,6 +81,32 @@ func NewClientFromConnectionString(connectionString string, options *ClientOptio
return NewClientWithNoCredential(parsed.ServiceURL, options)
}

// NewClientWithUserDelegationCredential 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 NewClientWithUserDelegationCredential(serviceURL string, ctx context.Context, info generated.KeyInfo, timeout *int32, requestID *string) (*Client, error) {
siminsavani-msft marked this conversation as resolved.
Show resolved Hide resolved
url, err := exported.ParseURL(serviceURL)
if err != nil {
return nil, err
}
pl := runtime.NewPipeline(exported.ModuleName, exported.ModuleVersion, runtime.PipelineOptions{}, nil)
sc := generated.NewServiceClient(serviceURL, pl)
opts := GetUserDelegationKeyOptions{
RequestID: requestID,
Timeout: timeout,
}
udk, err := sc.GetUserDelegationKey(ctx, info, &opts)
if err != nil {
return nil, err
}

return (*Client)(base.NewServiceClientUDK(url.Host, pl, nil, &udk.UserDelegationKey)), nil
}

func (s Client) GetUserDelegationKey(ctx context.Context, keyInfo generated.KeyInfo, options *GetUserDelegationKeyOptions) GetUserDelegationKeyResponse {
siminsavani-msft marked this conversation as resolved.
Show resolved Hide resolved
resp, _ := s.generated().GetUserDelegationKey(ctx, keyInfo, options)
return resp
}

func (s *Client) generated() *generated.ServiceClient {
return base.InnerClient((*base.Client[generated.ServiceClient])(s))
}
Expand Down