github.com/openshift/installer@v1.4.17/pkg/infrastructure/azure/storage.go (about)

     1  package azure
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"fmt"
     7  	"strings"
     8  	"sync"
     9  	"time"
    10  
    11  	"github.com/Azure/azure-sdk-for-go/sdk/azcore"
    12  	"github.com/Azure/azure-sdk-for-go/sdk/azcore/arm"
    13  	"github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud"
    14  	"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
    15  	"github.com/Azure/azure-sdk-for-go/sdk/azcore/streaming"
    16  	"github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
    17  	"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/keyvault/armkeyvault"
    18  	"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage"
    19  	"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob"
    20  	"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blob"
    21  	"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blockblob"
    22  	"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/pageblob"
    23  	"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/sas"
    24  	"github.com/sirupsen/logrus"
    25  
    26  	aztypes "github.com/openshift/installer/pkg/types/azure"
    27  )
    28  
    29  var (
    30  	vaultsClient *armkeyvault.VaultsClient
    31  	keysClient   *armkeyvault.KeysClient
    32  )
    33  
    34  // CreateStorageAccountInput contains the input parameters for creating a
    35  // storage account.
    36  type CreateStorageAccountInput struct {
    37  	SubscriptionID     string
    38  	ResourceGroupName  string
    39  	StorageAccountName string
    40  	Region             string
    41  	Tags               map[string]*string
    42  	CustomerManagedKey *aztypes.CustomerManagedKey
    43  	CloudName          aztypes.CloudEnvironment
    44  	TokenCredential    azcore.TokenCredential
    45  	CloudConfiguration cloud.Configuration
    46  }
    47  
    48  // CreateStorageAccountOutput contains the return values after creating a
    49  // storage account.
    50  type CreateStorageAccountOutput struct {
    51  	StorageAccount        *armstorage.Account
    52  	StorageAccountsClient *armstorage.AccountsClient
    53  	StorageClientFactory  *armstorage.ClientFactory
    54  	StorageAccountKeys    []armstorage.AccountKey
    55  }
    56  
    57  // CreateStorageAccount creates a new storage account.
    58  func CreateStorageAccount(ctx context.Context, in *CreateStorageAccountInput) (*CreateStorageAccountOutput, error) {
    59  	minimumTLSVersion := armstorage.MinimumTLSVersionTLS10
    60  	cloudConfiguration := in.CloudConfiguration
    61  
    62  	/* XXX: Do we support other clouds? */
    63  	switch in.CloudName {
    64  	case aztypes.PublicCloud:
    65  		minimumTLSVersion = armstorage.MinimumTLSVersionTLS12
    66  	case aztypes.USGovernmentCloud:
    67  		minimumTLSVersion = armstorage.MinimumTLSVersionTLS12
    68  	}
    69  
    70  	storageClientFactory, err := armstorage.NewClientFactory(
    71  		in.SubscriptionID,
    72  		in.TokenCredential,
    73  		&arm.ClientOptions{
    74  			ClientOptions: policy.ClientOptions{
    75  				Cloud: cloudConfiguration,
    76  				//Transport: ...,
    77  			},
    78  		},
    79  	)
    80  	if err != nil {
    81  		return nil, fmt.Errorf("failed to get storage account factory %w", err)
    82  	}
    83  
    84  	sku := armstorage.SKU{
    85  		Name: to.Ptr(armstorage.SKUNameStandardLRS),
    86  	}
    87  	accountCreateParameters := armstorage.AccountCreateParameters{
    88  		Identity: nil,
    89  		Kind:     to.Ptr(armstorage.KindStorageV2),
    90  		Location: to.Ptr(in.Region),
    91  		SKU:      &sku,
    92  		Properties: &armstorage.AccountPropertiesCreateParameters{
    93  			AllowBlobPublicAccess: to.Ptr(true),
    94  			AllowSharedKeyAccess:  to.Ptr(true),
    95  			IsLocalUserEnabled:    to.Ptr(true),
    96  			LargeFileSharesState:  to.Ptr(armstorage.LargeFileSharesStateEnabled),
    97  			PublicNetworkAccess:   to.Ptr(armstorage.PublicNetworkAccessEnabled),
    98  			MinimumTLSVersion:     &minimumTLSVersion,
    99  		},
   100  		Tags: in.Tags,
   101  	}
   102  
   103  	if in.CustomerManagedKey != nil && in.CustomerManagedKey.KeyVault.Name != "" {
   104  		// When encryption is enabled, Ignition is is stored as a page blob
   105  		// (and not a block blob). To support this case, `Kind` can continue to be
   106  		// `StorageV2` and yhe `SKU` needs to be `Premium_LRS`.
   107  		//https://learn.microsoft.com/en-us/azure/storage/common/storage-account-create?tabs=azure-portal
   108  		sku = armstorage.SKU{
   109  			Name: to.Ptr(armstorage.SKUNamePremiumLRS),
   110  		}
   111  		identity := armstorage.Identity{
   112  			Type: to.Ptr(armstorage.IdentityTypeUserAssigned),
   113  			UserAssignedIdentities: map[string]*armstorage.UserAssignedIdentity{
   114  				fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.ManagedIdentity/userAssignedIdentities/%s",
   115  					in.SubscriptionID,
   116  					in.CustomerManagedKey.KeyVault.ResourceGroup,
   117  					in.CustomerManagedKey.UserAssignedIdentityKey,
   118  				): {},
   119  			},
   120  		}
   121  		logrus.Debugf("Generating Encrytption for Storage Account using Customer Managed Key")
   122  		encryption, err := GenerateStorageAccountEncryption(
   123  			ctx,
   124  			&CustomerManagedKeyInput{
   125  				SubscriptionID:     in.SubscriptionID,
   126  				ResourceGroupName:  in.ResourceGroupName,
   127  				CustomerManagedKey: in.CustomerManagedKey,
   128  				TokenCredential:    in.TokenCredential,
   129  			},
   130  		)
   131  		if err != nil {
   132  			return nil, fmt.Errorf("error generating encryption information for provided customer managed key: %w", err)
   133  		}
   134  		accountCreateParameters.Identity = &identity
   135  		accountCreateParameters.SKU = &sku
   136  		accountCreateParameters.Properties.Encryption = encryption
   137  	}
   138  
   139  	logrus.Debugf("Creating storage account")
   140  	accountsClient := storageClientFactory.NewAccountsClient()
   141  	pollerResponse, err := accountsClient.BeginCreate(
   142  		ctx,
   143  		in.ResourceGroupName,
   144  		in.StorageAccountName,
   145  		accountCreateParameters,
   146  		nil,
   147  	)
   148  	if err != nil {
   149  		return nil, fmt.Errorf("error creating storage account %s: %w", in.StorageAccountName, err)
   150  	}
   151  
   152  	pollDoneResponse, err := pollerResponse.PollUntilDone(ctx, nil)
   153  	if err != nil {
   154  		return nil, fmt.Errorf("error waiting for creation of storage account %s: %w", in.StorageAccountName, err)
   155  	}
   156  
   157  	logrus.Debugf("Getting storage keys")
   158  	listKeysResponse, err := accountsClient.ListKeys(ctx, in.ResourceGroupName, in.StorageAccountName, nil)
   159  	if err != nil {
   160  		return nil, fmt.Errorf("failed to retrieve storage account keys for %s: %w", in.StorageAccountName, err)
   161  	}
   162  
   163  	out := &CreateStorageAccountOutput{
   164  		StorageAccount:        to.Ptr(pollDoneResponse.Account),
   165  		StorageAccountsClient: accountsClient,
   166  		StorageClientFactory:  storageClientFactory,
   167  	}
   168  
   169  	for _, key := range listKeysResponse.Keys {
   170  		out.StorageAccountKeys = append(out.StorageAccountKeys, *key)
   171  	}
   172  
   173  	return out, nil
   174  }
   175  
   176  // CreateBlobContainerInput contains the input parameters used for creating a
   177  // blob storage container.
   178  type CreateBlobContainerInput struct {
   179  	SubscriptionID       string
   180  	ResourceGroupName    string
   181  	StorageAccountName   string
   182  	ContainerName        string
   183  	PublicAccess         *armstorage.PublicAccess
   184  	StorageClientFactory *armstorage.ClientFactory
   185  }
   186  
   187  // CreateBlobContainerOutput contains the return values after creating a blob
   188  // storage container.
   189  type CreateBlobContainerOutput struct {
   190  	BlobContainer *armstorage.BlobContainer
   191  }
   192  
   193  // CreateBlobContainer creates a blob container in a storage account.
   194  func CreateBlobContainer(ctx context.Context, in *CreateBlobContainerInput) (*CreateBlobContainerOutput, error) {
   195  	blobContainersClient := in.StorageClientFactory.NewBlobContainersClient()
   196  
   197  	logrus.Debugf("Creating blob container")
   198  	blobContainerResponse, err := blobContainersClient.Create(
   199  		ctx,
   200  		in.ResourceGroupName,
   201  		in.StorageAccountName,
   202  		in.ContainerName,
   203  		armstorage.BlobContainer{
   204  			ContainerProperties: &armstorage.ContainerProperties{
   205  				PublicAccess: in.PublicAccess,
   206  			},
   207  		},
   208  		nil,
   209  	)
   210  	if err != nil {
   211  		return nil, fmt.Errorf("failed to create blob container %s: %w", in.ContainerName, err)
   212  	}
   213  
   214  	return &CreateBlobContainerOutput{
   215  		BlobContainer: to.Ptr(blobContainerResponse.BlobContainer),
   216  	}, nil
   217  }
   218  
   219  // CreatePageBlobInput containers the input parameters used for creating a page
   220  // blob.
   221  type CreatePageBlobInput struct {
   222  	StorageURL         string
   223  	BlobURL            string
   224  	ImageURL           string
   225  	StorageAccountName string
   226  	BootstrapIgnData   []byte
   227  	ImageLength        int64
   228  	StorageAccountKeys []armstorage.AccountKey
   229  	CloudConfiguration cloud.Configuration
   230  }
   231  
   232  // CreatePageBlobOutput contains the return values after creating a page blob.
   233  type CreatePageBlobOutput struct {
   234  	PageBlobClient      *pageblob.Client
   235  	SharedKeyCredential *azblob.SharedKeyCredential
   236  }
   237  
   238  // CreatePageBlob creates a blob and uploads a file from a URL to it.
   239  func CreatePageBlob(ctx context.Context, in *CreatePageBlobInput) (string, error) {
   240  	logrus.Debugf("Getting page blob credentials")
   241  
   242  	// XXX: Should try all of them until one is successful
   243  	sharedKeyCredential, err := azblob.NewSharedKeyCredential(in.StorageAccountName, *in.StorageAccountKeys[0].Value)
   244  	if err != nil {
   245  		return "", fmt.Errorf("failed to get shared credentials for storage account: %w", err)
   246  	}
   247  
   248  	logrus.Debugf("Getting page blob client")
   249  	pageBlobClient, err := pageblob.NewClientWithSharedKeyCredential(
   250  		in.BlobURL,
   251  		sharedKeyCredential,
   252  		&pageblob.ClientOptions{
   253  			ClientOptions: azcore.ClientOptions{
   254  				Cloud: in.CloudConfiguration,
   255  			},
   256  		},
   257  	)
   258  	if err != nil {
   259  		return "", fmt.Errorf("failed to get page blob client: %w", err)
   260  	}
   261  
   262  	logrus.Debugf("Creating Page blob and uploading image to it")
   263  	if in.ImageURL == "" {
   264  		_, err = pageBlobClient.Create(ctx, in.ImageLength, nil)
   265  		if err != nil {
   266  			return "", fmt.Errorf("failed to create page blob with image contents: %w", err)
   267  		}
   268  		// This image (example: ignition shim) needs to be uploaded from a local file.
   269  		err = doUploadPages(ctx, pageBlobClient, in.BootstrapIgnData, in.ImageLength)
   270  		if err != nil {
   271  			return "", fmt.Errorf("failed to upload page blob image contents: %w", err)
   272  		}
   273  	} else {
   274  		// This is used in terraform, not sure if it matters
   275  		metadata := map[string]*string{
   276  			"source_uri": to.Ptr(in.ImageURL),
   277  		}
   278  
   279  		_, err = pageBlobClient.Create(ctx, in.ImageLength, &pageblob.CreateOptions{
   280  			Metadata: metadata,
   281  		})
   282  		if err != nil {
   283  			return "", fmt.Errorf("failed to create page blob with image URL: %w", err)
   284  		}
   285  
   286  		err = doUploadPagesFromURL(ctx, pageBlobClient, in.ImageURL, in.ImageLength)
   287  		if err != nil {
   288  			return "", fmt.Errorf("failed to upload page blob image from URL %s: %w", in.ImageURL, err)
   289  		}
   290  	}
   291  
   292  	// Is this addition OK for when CreatePageBlob() is called from InfraReady()
   293  	sasURL, err := pageBlobClient.GetSASURL(sas.BlobPermissions{Read: true}, time.Now().Add(time.Minute*60), &blob.GetSASURLOptions{})
   294  	if err != nil {
   295  		return "", fmt.Errorf("failed to get Page Blob SAS URL: %w", err)
   296  	}
   297  	return sasURL, nil
   298  }
   299  
   300  func doUploadPages(ctx context.Context, pageBlobClient *pageblob.Client, imageData []byte, imageLength int64) error {
   301  	logrus.Debugf("Uploading to Page Blob with Image of length :%d", imageLength)
   302  
   303  	// Page blobs file size must be a multiple of 512, hence a little padding is needed to push the file.
   304  	// imageLength has already been adjusted to the next highest size divisible by 512.
   305  	// So, here we are padding the image to match this size.
   306  	// Bootstrap Ignition is a json file. For parsing of this file to succeed with the padding, the
   307  	// file needs to end with a }.
   308  	logrus.Debugf("Original Image length: %d", int64(len(imageData)))
   309  	padding := imageLength - int64(len(imageData))
   310  	paddingString := strings.Repeat(" ", int(padding)) + string(imageData[len(imageData)-1])
   311  	imageData = append(imageData[0:len(imageData)-1], paddingString...)
   312  	logrus.Debugf("New Image length (after padding): %d", int64(len(imageData)))
   313  
   314  	pageSize := int64(1024 * 1024 * 4)
   315  	newOffset := int64(0)
   316  	remainingImageLength := imageLength
   317  
   318  	for remainingImageLength > 0 {
   319  		if remainingImageLength < pageSize {
   320  			pageSize = remainingImageLength
   321  		}
   322  
   323  		logrus.Debugf("Uploading pages with Offset :%d and Count :%d", newOffset, pageSize)
   324  
   325  		_, err := pageBlobClient.UploadPages(
   326  			ctx,
   327  			streaming.NopCloser(bytes.NewReader(imageData)),
   328  			blob.HTTPRange{
   329  				Offset: newOffset,
   330  				Count:  pageSize,
   331  			},
   332  			nil)
   333  		if err != nil {
   334  			return fmt.Errorf("failed uploading Image to page blob: %w", err)
   335  		}
   336  		newOffset += pageSize
   337  		remainingImageLength -= pageSize
   338  		logrus.Debugf("newOffset :%d and remainingImageLength :%d", newOffset, remainingImageLength)
   339  	}
   340  	return nil
   341  }
   342  
   343  func doUploadPagesFromURL(ctx context.Context, pageBlobClient *pageblob.Client, imageURL string, imageLength int64) error {
   344  	// Azure only allows 4MB chunks, See
   345  	// https://docs.microsoft.com/rest/api/storageservices/put-page-from-url
   346  	pageSize := int64(1024 * 1024 * 4)
   347  	leftOverBytes := imageLength % pageSize
   348  	offset := int64(0)
   349  	pages := int64(0)
   350  
   351  	if imageLength > pageSize {
   352  		pages = imageLength / pageSize
   353  		if imageLength%pageSize > 0 {
   354  			pages++
   355  		}
   356  	} else {
   357  		pageSize = imageLength
   358  		pages = 1
   359  	}
   360  
   361  	threadsPerGroup := int64(64)
   362  	if pages < threadsPerGroup {
   363  		threadsPerGroup = pages
   364  	}
   365  
   366  	threadGroups := pages / threadsPerGroup
   367  	if pages%threadsPerGroup > 0 {
   368  		threadGroups++
   369  	}
   370  
   371  	var wg sync.WaitGroup
   372  	var threadError error
   373  	var res error
   374  
   375  	pagesLeft := pages
   376  	for threadGroup := int64(0); threadGroup < threadGroups; threadGroup++ {
   377  		if pagesLeft < threadsPerGroup {
   378  			threadsPerGroup = pagesLeft
   379  		}
   380  
   381  		errors := make(chan error, 1)
   382  		defer close(errors)
   383  
   384  		results := make(chan int64, threadsPerGroup)
   385  		defer close(results)
   386  
   387  		for thread := int64(0); thread < threadsPerGroup; thread++ {
   388  			if offset+pageSize >= imageLength && leftOverBytes > 0 {
   389  				pageSize = leftOverBytes
   390  				leftOverBytes = 0
   391  			} else if offset > imageLength {
   392  				break
   393  			}
   394  
   395  			wg.Add(1)
   396  			go func(ctx context.Context, source string, thread, sourceOffset, destOffset, count int64, wg *sync.WaitGroup) {
   397  				defer wg.Done()
   398  				var err error
   399  				nretries := 3
   400  				for i := 0; i < nretries; i++ {
   401  					_, err = pageBlobClient.UploadPagesFromURL(ctx, imageURL, sourceOffset, destOffset, count, nil)
   402  					if err == nil {
   403  						break
   404  					}
   405  				}
   406  				errors <- err
   407  				results <- thread
   408  			}(ctx, imageURL, thread, offset, offset, pageSize, &wg)
   409  
   410  			offset += pageSize
   411  		}
   412  		pagesLeft -= threadsPerGroup
   413  		for thread := int64(0); thread < threadsPerGroup; thread++ {
   414  			threadError = <-errors
   415  
   416  			// XXX: Save first error only. Should we care about the
   417  			// rest?
   418  			if threadError != nil && res == nil {
   419  				res = threadError
   420  			}
   421  			<-results
   422  		}
   423  		wg.Wait()
   424  		if res != nil {
   425  			logrus.Debug("Failed to upload rhcos image")
   426  			break
   427  		}
   428  
   429  		logrus.Debugf("%d out of %d pages uploaded", pages-pagesLeft, pages)
   430  	}
   431  
   432  	logrus.Debugf("Done uploading")
   433  	return res
   434  }
   435  
   436  // CreateBlockBlobInput containers the input parameters used for creating a
   437  // block blob.
   438  type CreateBlockBlobInput struct {
   439  	StorageURL         string
   440  	BlobURL            string
   441  	StorageAccountName string
   442  	BootstrapIgnData   []byte
   443  	StorageAccountKeys []armstorage.AccountKey
   444  	CloudConfiguration cloud.Configuration
   445  }
   446  
   447  // CreateBlockBlobOutput contains the return values after creating a block
   448  // blob.
   449  type CreateBlockBlobOutput struct {
   450  	PageBlobClient      *pageblob.Client
   451  	SharedKeyCredential *azblob.SharedKeyCredential
   452  }
   453  
   454  // CreateBlockBlob creates a block blob and uploads a file from a URL to it.
   455  func CreateBlockBlob(ctx context.Context, in *CreateBlockBlobInput) (string, error) {
   456  	logrus.Debugf("Getting block blob credentials")
   457  
   458  	// XXX: Should try all of them until one is successful
   459  	sharedKeyCredential, err := azblob.NewSharedKeyCredential(in.StorageAccountName, *in.StorageAccountKeys[0].Value)
   460  	if err != nil {
   461  		return "", fmt.Errorf("failed to get shared crdentials for storage account: %w", err)
   462  	}
   463  
   464  	logrus.Debugf("Getting block blob client")
   465  	blockBlobClient, err := blockblob.NewClientWithSharedKeyCredential(
   466  		in.BlobURL,
   467  		sharedKeyCredential,
   468  		&blockblob.ClientOptions{
   469  			ClientOptions: azcore.ClientOptions{
   470  				Cloud: in.CloudConfiguration,
   471  			},
   472  		},
   473  	)
   474  	if err != nil {
   475  		return "", fmt.Errorf("failed to get page blob client: %w", err)
   476  	}
   477  
   478  	logrus.Debugf("Creating block blob")
   479  
   480  	accessTier := blob.AccessTierHot
   481  	_, err = blockBlobClient.Upload(ctx, streaming.NopCloser(bytes.NewReader(in.BootstrapIgnData)), &blockblob.UploadOptions{
   482  		Tier: &accessTier,
   483  	})
   484  	if err != nil {
   485  		return "", fmt.Errorf("failed to create block blob: %w", err)
   486  	}
   487  
   488  	sasURL, err := blockBlobClient.GetSASURL(sas.BlobPermissions{Read: true}, time.Now().Add(time.Minute*60), &blob.GetSASURLOptions{})
   489  	if err != nil {
   490  		return "", fmt.Errorf("failed to get SAS URL: %w", err)
   491  	}
   492  
   493  	return sasURL, nil
   494  }
   495  
   496  // CustomerManagedKeyInput contains the input parameters for creating the
   497  // customer managed key and identity.
   498  type CustomerManagedKeyInput struct {
   499  	SubscriptionID     string
   500  	ResourceGroupName  string
   501  	CustomerManagedKey *aztypes.CustomerManagedKey
   502  	TokenCredential    azcore.TokenCredential
   503  }
   504  
   505  // GenerateStorageAccountEncryption generates all the Encryption information for the Storage Account
   506  // using the Customer Managed Key.
   507  func GenerateStorageAccountEncryption(ctx context.Context, in *CustomerManagedKeyInput) (*armstorage.Encryption, error) {
   508  	logrus.Debugf("Generating Encryption for Storage Account")
   509  
   510  	if in.CustomerManagedKey == nil {
   511  		logrus.Debugf("No Customer Managed Key provided. So, Encryption not enabled on storage account.")
   512  		return &armstorage.Encryption{}, nil
   513  	}
   514  
   515  	keyvaultClientFactory, err := armkeyvault.NewClientFactory(
   516  		in.SubscriptionID,
   517  		in.TokenCredential,
   518  		nil)
   519  	if err != nil {
   520  		return nil, fmt.Errorf("failed to get key vault client factory %w", err)
   521  	}
   522  
   523  	keysClient = keyvaultClientFactory.NewKeysClient()
   524  
   525  	_, err = keysClient.Get(
   526  		ctx,
   527  		in.CustomerManagedKey.KeyVault.ResourceGroup,
   528  		in.CustomerManagedKey.KeyVault.Name,
   529  		in.CustomerManagedKey.KeyVault.KeyName,
   530  		&armkeyvault.KeysClientGetOptions{})
   531  	if err != nil {
   532  		return nil, fmt.Errorf("failed to get customer managed key %s from key vault %s: %w", in.CustomerManagedKey.KeyVault.KeyName, in.CustomerManagedKey.KeyVault.Name, err)
   533  	}
   534  
   535  	vaultsClient = keyvaultClientFactory.NewVaultsClient()
   536  
   537  	keyVault, err := vaultsClient.Get(
   538  		ctx,
   539  		in.CustomerManagedKey.KeyVault.ResourceGroup,
   540  		in.CustomerManagedKey.KeyVault.Name,
   541  		&armkeyvault.VaultsClientGetOptions{})
   542  	if err != nil {
   543  		return nil, fmt.Errorf("failed to get key vault %s which contains customer managed key: %w", in.CustomerManagedKey.KeyVault.Name, err)
   544  	}
   545  
   546  	encryption := &armstorage.Encryption{
   547  		Services: &armstorage.EncryptionServices{
   548  			Blob: &armstorage.EncryptionService{
   549  				Enabled: to.Ptr(true),
   550  				KeyType: to.Ptr(armstorage.KeyTypeAccount),
   551  			},
   552  			File: &armstorage.EncryptionService{
   553  				Enabled: to.Ptr(true),
   554  				KeyType: to.Ptr(armstorage.KeyTypeAccount),
   555  			},
   556  		},
   557  		EncryptionIdentity: &armstorage.EncryptionIdentity{
   558  			EncryptionUserAssignedIdentity: to.Ptr(fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.ManagedIdentity/userAssignedIdentities/%s",
   559  				in.SubscriptionID,
   560  				in.CustomerManagedKey.KeyVault.ResourceGroup,
   561  				in.CustomerManagedKey.UserAssignedIdentityKey,
   562  			)),
   563  		},
   564  		KeySource: to.Ptr(armstorage.KeySourceMicrosoftKeyvault),
   565  		KeyVaultProperties: &armstorage.KeyVaultProperties{
   566  			KeyName:     to.Ptr(in.CustomerManagedKey.KeyVault.KeyName),
   567  			KeyVersion:  to.Ptr(""),
   568  			KeyVaultURI: keyVault.Properties.VaultURI,
   569  		},
   570  	}
   571  
   572  	return encryption, nil
   573  }