153 lines
		
	
	
		
			4.2 KiB
		
	
	
	
		
			Go
		
	
	
			
		
		
	
	
			153 lines
		
	
	
		
			4.2 KiB
		
	
	
	
		
			Go
		
	
	
| package azure
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"sync"
 | |
| 	"time"
 | |
| 
 | |
| 	"github.com/Azure/azure-sdk-for-go/sdk/azcore"
 | |
| 	"github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
 | |
| 	"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
 | |
| 	"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob"
 | |
| 	"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/container"
 | |
| 	"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/sas"
 | |
| 	"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/service"
 | |
| )
 | |
| 
 | |
| const (
 | |
| 	UDCGracePeriod = 30.0 * time.Minute
 | |
| 	UDCExpiryTime  = 48.0 * time.Hour
 | |
| )
 | |
| 
 | |
| // signer abstracts the specifics of a blob SAS and is specialized
 | |
| // for the different authentication credentials
 | |
| type signer interface {
 | |
| 	Sign(context.Context, *sas.BlobSignatureValues) (sas.QueryParameters, error)
 | |
| }
 | |
| 
 | |
| type sharedKeySigner struct {
 | |
| 	cred *azblob.SharedKeyCredential
 | |
| }
 | |
| 
 | |
| type clientTokenSigner struct {
 | |
| 	client    *azblob.Client
 | |
| 	cred      azcore.TokenCredential
 | |
| 	udcMutex  sync.Mutex
 | |
| 	udc       *service.UserDelegationCredential
 | |
| 	udcExpiry time.Time
 | |
| }
 | |
| 
 | |
| // azureClient abstracts signing blob urls for a container since the
 | |
| // azure apis have completely different underlying authentication apis
 | |
| type azureClient struct {
 | |
| 	container string
 | |
| 	client    *azblob.Client
 | |
| 	signer    signer
 | |
| }
 | |
| 
 | |
| func newAzureClient(params *Parameters) (*azureClient, error) {
 | |
| 	if params.AccountKey != "" {
 | |
| 		cred, err := azblob.NewSharedKeyCredential(params.AccountName, params.AccountKey)
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 		client, err := azblob.NewClientWithSharedKeyCredential(params.ServiceURL, cred, nil)
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 		signer := &sharedKeySigner{
 | |
| 			cred: cred,
 | |
| 		}
 | |
| 		return &azureClient{
 | |
| 			container: params.Container,
 | |
| 			client:    client,
 | |
| 			signer:    signer,
 | |
| 		}, nil
 | |
| 	}
 | |
| 
 | |
| 	var cred azcore.TokenCredential
 | |
| 	var err error
 | |
| 	if params.Credentials.Type == "client_secret" {
 | |
| 		creds := ¶ms.Credentials
 | |
| 		if cred, err = azidentity.NewClientSecretCredential(creds.TenantID, creds.ClientID, creds.Secret, nil); err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 	} else if cred, err = azidentity.NewDefaultAzureCredential(nil); err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	client, err := azblob.NewClient(params.ServiceURL, cred, nil)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	signer := &clientTokenSigner{
 | |
| 		client: client,
 | |
| 		cred:   cred,
 | |
| 	}
 | |
| 	return &azureClient{
 | |
| 		container: params.Container,
 | |
| 		client:    client,
 | |
| 		signer:    signer,
 | |
| 	}, nil
 | |
| }
 | |
| 
 | |
| func (a *azureClient) ContainerClient() *container.Client {
 | |
| 	return a.client.ServiceClient().NewContainerClient(a.container)
 | |
| }
 | |
| 
 | |
| func (a *azureClient) SignBlobURL(ctx context.Context, blobURL string, expires time.Time) (string, error) {
 | |
| 	urlParts, err := sas.ParseURL(blobURL)
 | |
| 	if err != nil {
 | |
| 		return "", err
 | |
| 	}
 | |
| 	perms := sas.BlobPermissions{Read: true}
 | |
| 	signatureValues := sas.BlobSignatureValues{
 | |
| 		Protocol:      sas.ProtocolHTTPS,
 | |
| 		StartTime:     time.Now().UTC().Add(-10 * time.Second),
 | |
| 		ExpiryTime:    expires,
 | |
| 		Permissions:   perms.String(),
 | |
| 		ContainerName: urlParts.ContainerName,
 | |
| 		BlobName:      urlParts.BlobName,
 | |
| 	}
 | |
| 	urlParts.SAS, err = a.signer.Sign(ctx, &signatureValues)
 | |
| 	if err != nil {
 | |
| 		return "", err
 | |
| 	}
 | |
| 	return urlParts.String(), nil
 | |
| }
 | |
| 
 | |
| func (s *sharedKeySigner) Sign(ctx context.Context, signatureValues *sas.BlobSignatureValues) (sas.QueryParameters, error) {
 | |
| 	return signatureValues.SignWithSharedKey(s.cred)
 | |
| }
 | |
| 
 | |
| func (s *clientTokenSigner) refreshUDC(ctx context.Context) (*service.UserDelegationCredential, error) {
 | |
| 	s.udcMutex.Lock()
 | |
| 	defer s.udcMutex.Unlock()
 | |
| 
 | |
| 	now := time.Now().UTC()
 | |
| 	if s.udc == nil || s.udcExpiry.Sub(now) < UDCGracePeriod {
 | |
| 		// reissue user delegation credential
 | |
| 		startTime := now.Add(-10 * time.Second)
 | |
| 		expiryTime := startTime.Add(UDCExpiryTime)
 | |
| 		info := service.KeyInfo{
 | |
| 			Start:  to.Ptr(startTime.UTC().Format(sas.TimeFormat)),
 | |
| 			Expiry: to.Ptr(expiryTime.UTC().Format(sas.TimeFormat)),
 | |
| 		}
 | |
| 		udc, err := s.client.ServiceClient().GetUserDelegationCredential(ctx, info, nil)
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 		s.udc = udc
 | |
| 		s.udcExpiry = expiryTime
 | |
| 	}
 | |
| 	return s.udc, nil
 | |
| }
 | |
| 
 | |
| func (s *clientTokenSigner) Sign(ctx context.Context, signatureValues *sas.BlobSignatureValues) (sas.QueryParameters, error) {
 | |
| 	udc, err := s.refreshUDC(ctx)
 | |
| 	if err != nil {
 | |
| 		return sas.QueryParameters{}, err
 | |
| 	}
 | |
| 	return signatureValues.SignWithUserDelegation(udc)
 | |
| }
 |