From 0c8d4e6a0a392579f0c60db47a691093c34745ec Mon Sep 17 00:00:00 2001 From: John Hixson Date: Wed, 4 Sep 2024 19:12:55 -0700 Subject: [PATCH 1/4] pkg/infrastructure/azure: don't use shared key auth - When using a managed identity, don't use shared key credentials - SAS urls are not supported in this case, so just return the blob URL https://issues.redhat.com/browse/OCPBUGS-37587 --- pkg/infrastructure/azure/azure.go | 6 ++ pkg/infrastructure/azure/storage.go | 115 ++++++++++++++++++++-------- 2 files changed, 91 insertions(+), 30 deletions(-) diff --git a/pkg/infrastructure/azure/azure.go b/pkg/infrastructure/azure/azure.go index d07f25d58df..9907d4274cf 100644 --- a/pkg/infrastructure/azure/azure.go +++ b/pkg/infrastructure/azure/azure.go @@ -280,6 +280,8 @@ func (p *Provider) InfraReady(ctx context.Context, in clusterapi.InfraReadyInput BlobURL: blobURL, ImageURL: imageURL, ImageLength: imageLength, + AuthType: session.AuthType, + TokenCredential: session.TokenCreds, StorageAccountName: storageAccountName, StorageAccountKeys: storageAccountKeys, ClientOpts: p.clientOptions, @@ -746,6 +748,8 @@ func (p Provider) Ignition(ctx context.Context, in clusterapi.IgnitionInput) ([] sasURL, err = CreateBlockBlob(ctx, &CreateBlockBlobInput{ StorageURL: p.StorageURL, BlobURL: blobURL, + AuthType: session.AuthType, + TokenCredential: session.TokenCreds, StorageAccountName: p.StorageAccountName, StorageAccountKeys: p.StorageAccountKeys, ClientOpts: p.clientOptions, @@ -774,6 +778,8 @@ func (p Provider) Ignition(ctx context.Context, in clusterapi.IgnitionInput) ([] StorageURL: p.StorageURL, BlobURL: blobURL, ImageURL: "", + AuthType: session.AuthType, + TokenCredential: session.TokenCreds, StorageAccountName: p.StorageAccountName, BootstrapIgnData: bootstrapIgnData, ImageLength: lengthBootstrapFile, diff --git a/pkg/infrastructure/azure/storage.go b/pkg/infrastructure/azure/storage.go index 3f048ebeeb8..31b88b94258 100644 --- a/pkg/infrastructure/azure/storage.go +++ b/pkg/infrastructure/azure/storage.go @@ -40,8 +40,8 @@ type CreateStorageAccountInput struct { ResourceGroupName string StorageAccountName string Region string - AuthType azic.AuthenticationType Tags map[string]*string + AuthType azic.AuthenticationType CustomerManagedKey *aztypes.CustomerManagedKey CloudName aztypes.CloudEnvironment TokenCredential azcore.TokenCredential @@ -62,7 +62,6 @@ func CreateStorageAccount(ctx context.Context, in *CreateStorageAccountInput) (* minimumTLSVersion := armstorage.MinimumTLSVersionTLS10 storageKind := to.Ptr(armstorage.KindStorageV2) - /* XXX: Do we support other clouds? */ switch in.CloudName { case aztypes.PublicCloud: minimumTLSVersion = armstorage.MinimumTLSVersionTLS12 @@ -94,6 +93,7 @@ func CreateStorageAccount(ctx context.Context, in *CreateStorageAccountInput) (* sku := armstorage.SKU{ Name: to.Ptr(armstorage.SKUNameStandardLRS), } + accountCreateParameters := armstorage.AccountCreateParameters{ Identity: nil, Kind: storageKind, @@ -237,6 +237,8 @@ type CreatePageBlobInput struct { StorageAccountName string BootstrapIgnData []byte ImageLength int64 + AuthType azic.AuthenticationType + TokenCredential azcore.TokenCredential StorageAccountKeys []armstorage.AccountKey ClientOpts *arm.ClientOptions } @@ -249,24 +251,9 @@ type CreatePageBlobOutput struct { // CreatePageBlob creates a blob and uploads a file from a URL to it. func CreatePageBlob(ctx context.Context, in *CreatePageBlobInput) (string, error) { - logrus.Debugf("Getting page blob credentials") - - // XXX: Should try all of them until one is successful - sharedKeyCredential, err := azblob.NewSharedKeyCredential(in.StorageAccountName, *in.StorageAccountKeys[0].Value) - if err != nil { - return "", fmt.Errorf("failed to get shared credentials for storage account: %w", err) - } - logrus.Debugf("Getting page blob client") - pageBlobClient, err := pageblob.NewClientWithSharedKeyCredential( - in.BlobURL, - sharedKeyCredential, - &pageblob.ClientOptions{ - ClientOptions: azcore.ClientOptions{ - Cloud: in.ClientOpts.Cloud, - }, - }, - ) + + pageBlobClient, err := getPageBlobClient(in) if err != nil { return "", fmt.Errorf("failed to get page blob client: %w", err) } @@ -301,6 +288,11 @@ func CreatePageBlob(ctx context.Context, in *CreatePageBlobInput) (string, error } } + // SAS not supported when using managed identity. + if in.AuthType == azic.ManagedIdentityAuth { + return pageBlobClient.URL(), nil + } + // Is this addition OK for when CreatePageBlob() is called from InfraReady() sasURL, err := pageBlobClient.GetSASURL(sas.BlobPermissions{Read: true}, time.Now().Add(time.Minute*60), &blob.GetSASURLOptions{}) if err != nil { @@ -309,6 +301,39 @@ func CreatePageBlob(ctx context.Context, in *CreatePageBlobInput) (string, error return sasURL, nil } +func getPageBlobClient(in *CreatePageBlobInput) (*pageblob.Client, error) { + var pageBlobClient *pageblob.Client + var err error + + if in.AuthType == azic.ManagedIdentityAuth { + pageBlobClient, err = pageblob.NewClient( + in.BlobURL, + in.TokenCredential, + &pageblob.ClientOptions{ + ClientOptions: azcore.ClientOptions{ + Cloud: in.ClientOpts.Cloud, + }, + }, + ) + } else { + sharedKeyCredential, credErr := azblob.NewSharedKeyCredential(in.StorageAccountName, *in.StorageAccountKeys[0].Value) + if err != nil { + return nil, credErr + } + pageBlobClient, err = pageblob.NewClientWithSharedKeyCredential( + in.BlobURL, + sharedKeyCredential, + &pageblob.ClientOptions{ + ClientOptions: azcore.ClientOptions{ + Cloud: in.ClientOpts.Cloud, + }, + }, + ) + } + + return pageBlobClient, err +} + func doUploadPages(ctx context.Context, pageBlobClient *pageblob.Client, imageData []byte, imageLength int64) error { logrus.Debugf("Uploading to Page Blob with Image of length :%d", imageLength) @@ -452,6 +477,8 @@ type CreateBlockBlobInput struct { BlobURL string StorageAccountName string BootstrapIgnData []byte + AuthType azic.AuthenticationType + TokenCredential azcore.TokenCredential StorageAccountKeys []armstorage.AccountKey ClientOpts *arm.ClientOptions CloudEnvironment aztypes.CloudEnvironment @@ -488,17 +515,9 @@ func CreateBlockBlob(ctx context.Context, in *CreateBlockBlobInput) (string, err func createBlockBlob(ctx context.Context, in *CreateBlockBlobInput, sharedKeyCredential *azblob.SharedKeyCredential) (string, error) { logrus.Debugf("Getting block blob client") - blockBlobClient, err := blockblob.NewClientWithSharedKeyCredential( - in.BlobURL, - sharedKeyCredential, - &blockblob.ClientOptions{ - ClientOptions: azcore.ClientOptions{ - Cloud: in.ClientOpts.Cloud, - }, - }, - ) + blockBlobClient, err := getBlockBlobClient(in) if err != nil { - return "", fmt.Errorf("failed to get page blob client: %w", err) + return "", fmt.Errorf("failed to get block blob client: %w", err) } logrus.Debugf("Creating block blob") @@ -511,11 +530,15 @@ func createBlockBlob(ctx context.Context, in *CreateBlockBlobInput, sharedKeyCre return "", fmt.Errorf("failed to create block blob: %w", err) } + // SAS not supported when using managed identity. + if in.AuthType == azic.ManagedIdentityAuth { + return blockBlobClient.URL(), nil + } + sasURL, err := blockBlobClient.GetSASURL(sas.BlobPermissions{Read: true}, time.Now().Add(time.Minute*60), &blob.GetSASURLOptions{}) if err != nil { return "", fmt.Errorf("failed to get SAS URL: %w", err) } - return sasURL, nil } @@ -597,6 +620,38 @@ func uploadBlockBlobOnStack(in *CreateBlockBlobInput, key string) (string, error } return sas, nil } +func getBlockBlobClient(in *CreateBlockBlobInput) (*blockblob.Client, error) { + var blockBlobClient *blockblob.Client + var err error + + if in.AuthType == azic.ManagedIdentityAuth { + blockBlobClient, err = blockblob.NewClient( + in.BlobURL, + in.TokenCredential, + &blockblob.ClientOptions{ + ClientOptions: azcore.ClientOptions{ + Cloud: in.ClientOpts.Cloud, + }, + }, + ) + } else { + sharedKeyCredential, credErr := azblob.NewSharedKeyCredential(in.StorageAccountName, *in.StorageAccountKeys[0].Value) + if err != nil { + return nil, credErr + } + blockBlobClient, err = blockblob.NewClientWithSharedKeyCredential( + in.BlobURL, + sharedKeyCredential, + &blockblob.ClientOptions{ + ClientOptions: azcore.ClientOptions{ + Cloud: in.ClientOpts.Cloud, + }, + }, + ) + } + + return blockBlobClient, err +} // CustomerManagedKeyInput contains the input parameters for creating the // customer managed key and identity. From 44377378cecea7ad6e635c7652e74aea036d12c2 Mon Sep 17 00:00:00 2001 From: Aditya Narayanaswamy Date: Wed, 30 Jul 2025 12:28:30 -0400 Subject: [PATCH 2/4] azure: Remove option to add shared key creds Removing the option to add shared key credentials to make way for better cred authentications. --- pkg/infrastructure/azure/storage.go | 92 ++++++----------------------- 1 file changed, 19 insertions(+), 73 deletions(-) diff --git a/pkg/infrastructure/azure/storage.go b/pkg/infrastructure/azure/storage.go index 31b88b94258..2518d5e34a0 100644 --- a/pkg/infrastructure/azure/storage.go +++ b/pkg/infrastructure/azure/storage.go @@ -80,11 +80,6 @@ func CreateStorageAccount(ctx context.Context, in *CreateStorageAccountInput) (* //APIVersion: "2019-06-01", }, } - allowSharedKeyAccess := true - if in.AuthType == azic.ManagedIdentityAuth { - allowSharedKeyAccess = false - } - storageClientFactory, err := armstorage.NewClientFactory(in.SubscriptionID, in.TokenCredential, opts) if err != nil { return nil, fmt.Errorf("failed to get storage account factory %w", err) @@ -101,7 +96,7 @@ func CreateStorageAccount(ctx context.Context, in *CreateStorageAccountInput) (* SKU: &sku, Properties: &armstorage.AccountPropertiesCreateParameters{ AllowBlobPublicAccess: to.Ptr(false), - AllowSharedKeyAccess: to.Ptr(allowSharedKeyAccess), + AllowSharedKeyAccess: to.Ptr(false), IsLocalUserEnabled: to.Ptr(true), LargeFileSharesState: to.Ptr(armstorage.LargeFileSharesStateEnabled), PublicNetworkAccess: to.Ptr(armstorage.PublicNetworkAccessEnabled), @@ -253,7 +248,15 @@ type CreatePageBlobOutput struct { func CreatePageBlob(ctx context.Context, in *CreatePageBlobInput) (string, error) { logrus.Debugf("Getting page blob client") - pageBlobClient, err := getPageBlobClient(in) + pageBlobClient, err := pageblob.NewClient( + in.BlobURL, + in.TokenCredential, + &pageblob.ClientOptions{ + ClientOptions: azcore.ClientOptions{ + Cloud: in.ClientOpts.Cloud, + }, + }, + ) if err != nil { return "", fmt.Errorf("failed to get page blob client: %w", err) } @@ -301,39 +304,6 @@ func CreatePageBlob(ctx context.Context, in *CreatePageBlobInput) (string, error return sasURL, nil } -func getPageBlobClient(in *CreatePageBlobInput) (*pageblob.Client, error) { - var pageBlobClient *pageblob.Client - var err error - - if in.AuthType == azic.ManagedIdentityAuth { - pageBlobClient, err = pageblob.NewClient( - in.BlobURL, - in.TokenCredential, - &pageblob.ClientOptions{ - ClientOptions: azcore.ClientOptions{ - Cloud: in.ClientOpts.Cloud, - }, - }, - ) - } else { - sharedKeyCredential, credErr := azblob.NewSharedKeyCredential(in.StorageAccountName, *in.StorageAccountKeys[0].Value) - if err != nil { - return nil, credErr - } - pageBlobClient, err = pageblob.NewClientWithSharedKeyCredential( - in.BlobURL, - sharedKeyCredential, - &pageblob.ClientOptions{ - ClientOptions: azcore.ClientOptions{ - Cloud: in.ClientOpts.Cloud, - }, - }, - ) - } - - return pageBlobClient, err -} - func doUploadPages(ctx context.Context, pageBlobClient *pageblob.Client, imageData []byte, imageLength int64) error { logrus.Debugf("Uploading to Page Blob with Image of length :%d", imageLength) @@ -515,7 +485,15 @@ func CreateBlockBlob(ctx context.Context, in *CreateBlockBlobInput) (string, err func createBlockBlob(ctx context.Context, in *CreateBlockBlobInput, sharedKeyCredential *azblob.SharedKeyCredential) (string, error) { logrus.Debugf("Getting block blob client") - blockBlobClient, err := getBlockBlobClient(in) + blockBlobClient, err := blockblob.NewClient( + in.BlobURL, + in.TokenCredential, + &blockblob.ClientOptions{ + ClientOptions: azcore.ClientOptions{ + Cloud: in.ClientOpts.Cloud, + }, + }, + ) if err != nil { return "", fmt.Errorf("failed to get block blob client: %w", err) } @@ -620,38 +598,6 @@ func uploadBlockBlobOnStack(in *CreateBlockBlobInput, key string) (string, error } return sas, nil } -func getBlockBlobClient(in *CreateBlockBlobInput) (*blockblob.Client, error) { - var blockBlobClient *blockblob.Client - var err error - - if in.AuthType == azic.ManagedIdentityAuth { - blockBlobClient, err = blockblob.NewClient( - in.BlobURL, - in.TokenCredential, - &blockblob.ClientOptions{ - ClientOptions: azcore.ClientOptions{ - Cloud: in.ClientOpts.Cloud, - }, - }, - ) - } else { - sharedKeyCredential, credErr := azblob.NewSharedKeyCredential(in.StorageAccountName, *in.StorageAccountKeys[0].Value) - if err != nil { - return nil, credErr - } - blockBlobClient, err = blockblob.NewClientWithSharedKeyCredential( - in.BlobURL, - sharedKeyCredential, - &blockblob.ClientOptions{ - ClientOptions: azcore.ClientOptions{ - Cloud: in.ClientOpts.Cloud, - }, - }, - ) - } - - return blockBlobClient, err -} // CustomerManagedKeyInput contains the input parameters for creating the // customer managed key and identity. From 6735d8f628cf6acecf573251ac7771be3a9c40b5 Mon Sep 17 00:00:00 2001 From: Aditya Narayanaswamy Date: Wed, 30 Jul 2025 15:25:23 -0400 Subject: [PATCH 3/4] azure: Create SAS for blobs Creating SAS url for ignition blobs using user delegated credentials. --- pkg/infrastructure/azure/azure.go | 43 +++++++++++++++++++++++++++-- pkg/infrastructure/azure/storage.go | 43 ++++++++++++----------------- 2 files changed, 57 insertions(+), 29 deletions(-) diff --git a/pkg/infrastructure/azure/azure.go b/pkg/infrastructure/azure/azure.go index 9907d4274cf..eae43f58870 100644 --- a/pkg/infrastructure/azure/azure.go +++ b/pkg/infrastructure/azure/azure.go @@ -17,6 +17,8 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v4" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v2" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage" + "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/sas" + "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/service" "github.com/coreos/stream-metadata-go/arch" "github.com/sirupsen/logrus" corev1 "k8s.io/api/core/v1" @@ -280,7 +282,7 @@ func (p *Provider) InfraReady(ctx context.Context, in clusterapi.InfraReadyInput BlobURL: blobURL, ImageURL: imageURL, ImageLength: imageLength, - AuthType: session.AuthType, + CloudEnvironment: in.InstallConfig.Azure.CloudName, TokenCredential: session.TokenCreds, StorageAccountName: storageAccountName, StorageAccountKeys: storageAccountKeys, @@ -429,7 +431,6 @@ func (p *Provider) InfraReady(ctx context.Context, in clusterapi.InfraReadyInput lbClient: lbClient, tags: p.Tags, } - intLoadBalancer, err := updateInternalLoadBalancer(ctx, lbInput) if err != nil { return fmt.Errorf("failed to update internal load balancer: %w", err) @@ -742,6 +743,28 @@ func (p Provider) Ignition(ctx context.Context, in clusterapi.IgnitionInput) ([] } sasURL := "" + now := time.Now().UTC().Add(-10 * time.Second) + expiry := now.Add(1 * time.Hour) + info := service.KeyInfo{ + Start: to.Ptr(now.UTC().Format(sas.TimeFormat)), + Expiry: to.Ptr(expiry.UTC().Format(sas.TimeFormat)), + } + + serviceClient, err := service.NewClient(fmt.Sprintf("https://%s.blob.%s/", p.StorageAccountName, session.Environment.StorageEndpointSuffix), + session.TokenCreds, + &service.ClientOptions{ + ClientOptions: azcore.ClientOptions{ + Cloud: p.CloudConfiguration, + }, + }, + ) + if err != nil { + return nil, fmt.Errorf("failed to create service client: %w", err) + } + udc, err := serviceClient.GetUserDelegationCredential(context.Background(), info, nil) + if err != nil { + return nil, fmt.Errorf("failed to create user delegation credentials: %w", err) + } if in.InstallConfig.Config.Azure.CustomerManagedKey == nil { logrus.Debugf("Creating a Block Blob for ignition shim") @@ -778,7 +801,7 @@ func (p Provider) Ignition(ctx context.Context, in clusterapi.IgnitionInput) ([] StorageURL: p.StorageURL, BlobURL: blobURL, ImageURL: "", - AuthType: session.AuthType, + CloudEnvironment: in.InstallConfig.Azure.CloudName, TokenCredential: session.TokenCreds, StorageAccountName: p.StorageAccountName, BootstrapIgnData: bootstrapIgnData, @@ -790,6 +813,20 @@ func (p Provider) Ignition(ctx context.Context, in clusterapi.IgnitionInput) ([] return nil, fmt.Errorf("failed to create PageBlob for ignition shim: %w", err) } } + if sasURL == "" { + sasQueryParams, err := sas.BlobSignatureValues{ + Protocol: sas.ProtocolHTTPS, + StartTime: time.Now().UTC().Add(time.Second * -10), + ExpiryTime: time.Now().UTC().Add(2 * time.Hour), + Permissions: to.Ptr(sas.ContainerPermissions{Read: true}).String(), + ContainerName: "ignition", + BlobName: blobName, + }.SignWithUserDelegation(udc) + if err != nil { + return nil, fmt.Errorf("failed to sign blob %s: %w", blobURL, err) + } + sasURL = fmt.Sprintf("https://%s.blob.%s/ignition/%s?%s", p.StorageAccountName, session.Environment.StorageEndpointSuffix, blobName, sasQueryParams.Encode()) + } ignShim, err := bootstrap.GenerateIgnitionShimWithCertBundleAndProxy(sasURL, in.InstallConfig.Config.AdditionalTrustBundle, in.InstallConfig.Config.Proxy) if err != nil { return nil, fmt.Errorf("failed to create ignition shim: %w", err) diff --git a/pkg/infrastructure/azure/storage.go b/pkg/infrastructure/azure/storage.go index 2518d5e34a0..cf036094b54 100644 --- a/pkg/infrastructure/azure/storage.go +++ b/pkg/infrastructure/azure/storage.go @@ -21,6 +21,7 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blockblob" "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/pageblob" "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/sas" + "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/service" "github.com/Azure/azure-sdk-for-go/storage" "github.com/sirupsen/logrus" @@ -96,7 +97,7 @@ func CreateStorageAccount(ctx context.Context, in *CreateStorageAccountInput) (* SKU: &sku, Properties: &armstorage.AccountPropertiesCreateParameters{ AllowBlobPublicAccess: to.Ptr(false), - AllowSharedKeyAccess: to.Ptr(false), + AllowSharedKeyAccess: to.Ptr(in.CloudName == aztypes.StackCloud), IsLocalUserEnabled: to.Ptr(true), LargeFileSharesState: to.Ptr(armstorage.LargeFileSharesStateEnabled), PublicNetworkAccess: to.Ptr(armstorage.PublicNetworkAccessEnabled), @@ -230,9 +231,10 @@ type CreatePageBlobInput struct { BlobURL string ImageURL string StorageAccountName string + CloudEnvironment aztypes.CloudEnvironment BootstrapIgnData []byte + UserDelegatedSAS *sas.UserDelegationCredential ImageLength int64 - AuthType azic.AuthenticationType TokenCredential azcore.TokenCredential StorageAccountKeys []armstorage.AccountKey ClientOpts *arm.ClientOptions @@ -290,18 +292,15 @@ func CreatePageBlob(ctx context.Context, in *CreatePageBlobInput) (string, error return "", fmt.Errorf("failed to upload page blob image from URL %s: %w", in.ImageURL, err) } } - - // SAS not supported when using managed identity. - if in.AuthType == azic.ManagedIdentityAuth { - return pageBlobClient.URL(), nil - } - - // Is this addition OK for when CreatePageBlob() is called from InfraReady() - sasURL, err := pageBlobClient.GetSASURL(sas.BlobPermissions{Read: true}, time.Now().Add(time.Minute*60), &blob.GetSASURLOptions{}) - if err != nil { - return "", fmt.Errorf("failed to get Page Blob SAS URL: %w", err) + if in.CloudEnvironment == aztypes.StackCloud { + // Is this addition OK for when CreatePageBlob() is called from InfraReady() + sasURL, err := pageBlobClient.GetSASURL(sas.BlobPermissions{Read: true}, time.Now().Add(time.Minute*60), &blob.GetSASURLOptions{}) + if err != nil { + return "", fmt.Errorf("failed to get Page Blob SAS URL: %w", err) + } + return sasURL, nil } - return sasURL, nil + return "", nil } func doUploadPages(ctx context.Context, pageBlobClient *pageblob.Client, imageData []byte, imageLength int64) error { @@ -454,12 +453,14 @@ type CreateBlockBlobInput struct { CloudEnvironment aztypes.CloudEnvironment ContainerName string BlobName string + UserDelegatedSAS *sas.UserDelegationCredential StorageSuffix string ARMEndpoint string Region string ResourceGroupName string Session *azic.Session Tags map[string]*string + UserDelegatedCreds *service.UserDelegationCredential } // CreateBlockBlobOutput contains the return values after creating a block @@ -475,15 +476,10 @@ func CreateBlockBlob(ctx context.Context, in *CreateBlockBlobInput) (string, err return createBlockBlobOnStack(ctx, in) } logrus.Debugf("Getting block blob credentials") - // XXX: Should try all of them until one is successful - sharedKeyCredential, err := azblob.NewSharedKeyCredential(in.StorageAccountName, *in.StorageAccountKeys[0].Value) - if err != nil { - return "", fmt.Errorf("failed to get shared crdentials for storage account: %w", err) - } - return createBlockBlob(ctx, in, sharedKeyCredential) + return createBlockBlob(ctx, in) } -func createBlockBlob(ctx context.Context, in *CreateBlockBlobInput, sharedKeyCredential *azblob.SharedKeyCredential) (string, error) { +func createBlockBlob(ctx context.Context, in *CreateBlockBlobInput) (string, error) { logrus.Debugf("Getting block blob client") blockBlobClient, err := blockblob.NewClient( in.BlobURL, @@ -512,12 +508,7 @@ func createBlockBlob(ctx context.Context, in *CreateBlockBlobInput, sharedKeyCre if in.AuthType == azic.ManagedIdentityAuth { return blockBlobClient.URL(), nil } - - sasURL, err := blockBlobClient.GetSASURL(sas.BlobPermissions{Read: true}, time.Now().Add(time.Minute*60), &blob.GetSASURLOptions{}) - if err != nil { - return "", fmt.Errorf("failed to get SAS URL: %w", err) - } - return sasURL, nil + return "", nil } func createBlockBlobOnStack(ctx context.Context, in *CreateBlockBlobInput) (string, error) { From 6136231183be27ce37dc5f7f0a1879b026beb6df Mon Sep 17 00:00:00 2001 From: Aditya Narayanaswamy Date: Wed, 7 Jan 2026 01:06:55 -0500 Subject: [PATCH 4/4] azure: Add field in installconfig to disallow shared access key Since this needs to be backported to earlier versions, adding a field to disallow shared access key if necessary. Made the field negative type field to have the default be enabled for shared key access for earlier versions. --- .../install.openshift.io_installconfigs.yaml | 6 + pkg/explain/printer_test.go | 5 + pkg/infrastructure/azure/azure.go | 136 +++++++------ pkg/infrastructure/azure/storage.go | 190 +++++++++++------- pkg/types/azure/platform.go | 7 + pkg/types/azure/validation/platform.go | 3 + 6 files changed, 217 insertions(+), 130 deletions(-) diff --git a/data/data/install.openshift.io_installconfigs.yaml b/data/data/install.openshift.io_installconfigs.yaml index 7a4808024ec..dda5847d353 100644 --- a/data/data/install.openshift.io_installconfigs.yaml +++ b/data/data/install.openshift.io_installconfigs.yaml @@ -4801,6 +4801,12 @@ spec: azure: description: Azure is the configuration used when installing on Azure. properties: + allowSharedKeyAccess: + description: |- + AllowSharedKeyAccess specifies if shared access key should be enabled for the storage account. + Default value is true. + Disabling this will require a new permission "Storage Blob Data Contributor" in azure. + type: boolean armEndpoint: description: ARMEndpoint is the endpoint for the Azure API when installing on Azure Stack. diff --git a/pkg/explain/printer_test.go b/pkg/explain/printer_test.go index bb784d9f794..3205d34c990 100644 --- a/pkg/explain/printer_test.go +++ b/pkg/explain/printer_test.go @@ -293,6 +293,11 @@ cluster itself may not include these tags. }, { path: []string{"platform", "azure"}, desc: `FIELDS: + allowSharedKeyAccess + AllowSharedKeyAccess specifies if shared access key should be enabled for the storage account. +Default value is true. +Disabling this will require a new permission "Storage Blob Data Contributor" in azure. + armEndpoint ARMEndpoint is the endpoint for the Azure API when installing on Azure Stack. diff --git a/pkg/infrastructure/azure/azure.go b/pkg/infrastructure/azure/azure.go index eae43f58870..379e75874b2 100644 --- a/pkg/infrastructure/azure/azure.go +++ b/pkg/infrastructure/azure/azure.go @@ -107,8 +107,8 @@ func (p *Provider) InfraReady(ctx context.Context, in clusterapi.InfraReadyInput installConfig := in.InstallConfig.Config platform := installConfig.Platform.Azure subscriptionID := session.Credentials.SubscriptionID - cloudConfiguration := session.CloudConfig - tokenCredential := session.TokenCreds + p.CloudConfiguration = session.CloudConfig + p.TokenCredential = session.TokenCreds p.ResourceGroupName = platform.ClusterResourceGroupName(in.InfraID) userTags := platform.UserTags @@ -121,7 +121,7 @@ func (p *Provider) InfraReady(ctx context.Context, in clusterapi.InfraReadyInput opts := &arm.ClientOptions{ ClientOptions: policy.ClientOptions{ - Cloud: cloudConfiguration, + Cloud: p.CloudConfiguration, }, } computeClientOpts := opts @@ -129,7 +129,7 @@ func (p *Provider) InfraReady(ctx context.Context, in clusterapi.InfraReadyInput opts.APIVersion = stackAPIVersion computeClientOpts = &arm.ClientOptions{ ClientOptions: policy.ClientOptions{ - Cloud: cloudConfiguration, + Cloud: p.CloudConfiguration, APIVersion: stackComputeAPIVersion, }, } @@ -142,7 +142,7 @@ func (p *Provider) InfraReady(ctx context.Context, in clusterapi.InfraReadyInput region: platform.Region, resourceGroupName: p.ResourceGroupName, subscriptionID: subscriptionID, - tokenCredential: tokenCredential, + tokenCredential: p.TokenCredential, infraID: in.InfraID, clientOpts: p.clientOptions, tags: p.Tags, @@ -156,7 +156,7 @@ func (p *Provider) InfraReady(ctx context.Context, in clusterapi.InfraReadyInput // Creating a dummy nsg for existing vnets installation to appease the ingress operator. if in.InstallConfig.Config.Azure.VirtualNetwork != "" { - networkClientFactory, err := armnetwork.NewClientFactory(subscriptionID, tokenCredential, p.clientOptions) + networkClientFactory, err := armnetwork.NewClientFactory(subscriptionID, p.TokenCredential, p.clientOptions) if err != nil { return fmt.Errorf("failed to create azure network factory: %w", err) } @@ -232,20 +232,26 @@ func (p *Provider) InfraReady(ctx context.Context, in clusterapi.InfraReadyInput var storageClientFactory *armstorage.ClientFactory var storageAccountKeys []armstorage.AccountKey + sharedKey := true + if in.InstallConfig.Config.Azure.AllowSharedKeyAccess != nil { + sharedKey = *in.InstallConfig.Config.Azure.AllowSharedKeyAccess + } + var createStorageAccountOutput *CreateStorageAccountOutput if platform.CloudName != aztypes.StackCloud { // Create storage account createStorageAccountOutput, err = CreateStorageAccount(ctx, &CreateStorageAccountInput{ - SubscriptionID: subscriptionID, - ResourceGroupName: resourceGroupName, - StorageAccountName: storageAccountName, - CloudName: platform.CloudName, - Region: platform.Region, - AuthType: session.AuthType, - Tags: tags, - CustomerManagedKey: platform.CustomerManagedKey, - TokenCredential: tokenCredential, - ClientOpts: p.clientOptions, + SubscriptionID: subscriptionID, + ResourceGroupName: resourceGroupName, + StorageAccountName: storageAccountName, + CloudName: platform.CloudName, + Region: platform.Region, + AuthType: session.AuthType, + AllowSharedKeyAccess: sharedKey, + Tags: tags, + CustomerManagedKey: platform.CustomerManagedKey, + TokenCredential: p.TokenCredential, + ClientOpts: p.clientOptions, }) if err != nil { return err @@ -278,15 +284,16 @@ func (p *Provider) InfraReady(ctx context.Context, in clusterapi.InfraReadyInput logrus.Debugf("BlobContainer.ID=%s", *blobContainer.ID) _, err = CreatePageBlob(ctx, &CreatePageBlobInput{ - StorageURL: storageURL, - BlobURL: blobURL, - ImageURL: imageURL, - ImageLength: imageLength, - CloudEnvironment: in.InstallConfig.Azure.CloudName, - TokenCredential: session.TokenCreds, - StorageAccountName: storageAccountName, - StorageAccountKeys: storageAccountKeys, - ClientOpts: p.clientOptions, + StorageURL: storageURL, + BlobURL: blobURL, + ImageURL: imageURL, + ImageLength: imageLength, + CloudEnvironment: in.InstallConfig.Azure.CloudName, + AllowSharedKeyAccess: sharedKey, + TokenCredential: session.TokenCreds, + StorageAccountName: storageAccountName, + StorageAccountKeys: storageAccountKeys, + ClientOpts: p.clientOptions, }) if err != nil { return err @@ -299,7 +306,7 @@ func (p *Provider) InfraReady(ctx context.Context, in clusterapi.InfraReadyInput GalleryName: galleryName, Region: platform.Region, Tags: tags, - TokenCredential: tokenCredential, + TokenCredential: p.TokenCredential, ClientOpts: p.clientOptions, }) if err != nil { @@ -318,7 +325,7 @@ func (p *Provider) InfraReady(ctx context.Context, in clusterapi.InfraReadyInput Offer: "rhcos", SKU: "basic", Tags: tags, - TokenCredential: tokenCredential, + TokenCredential: p.TokenCredential, ClientOpts: p.clientOptions, Architecture: architecture, OSType: armcompute.OperatingSystemTypesLinux, @@ -347,7 +354,7 @@ func (p *Provider) InfraReady(ctx context.Context, in clusterapi.InfraReadyInput Offer: "rhcos-gen2", SKU: "gen2", Tags: tags, - TokenCredential: tokenCredential, + TokenCredential: p.TokenCredential, ClientOpts: p.clientOptions, Architecture: architecture, OSType: armcompute.OperatingSystemTypesLinux, @@ -393,7 +400,7 @@ func (p *Provider) InfraReady(ctx context.Context, in clusterapi.InfraReadyInput } if installConfig.Azure.CloudName == aztypes.StackCloud { - client, err := armcompute.NewImagesClient(subscriptionID, tokenCredential, p.computeClientOptions) + client, err := armcompute.NewImagesClient(subscriptionID, p.TokenCredential, p.computeClientOptions) if err != nil { return fmt.Errorf("error creating stack managed images client: %w", err) } @@ -761,31 +768,33 @@ func (p Provider) Ignition(ctx context.Context, in clusterapi.IgnitionInput) ([] if err != nil { return nil, fmt.Errorf("failed to create service client: %w", err) } - udc, err := serviceClient.GetUserDelegationCredential(context.Background(), info, nil) - if err != nil { - return nil, fmt.Errorf("failed to create user delegation credentials: %w", err) + + sharedKey := true + if in.InstallConfig.Config.Azure.AllowSharedKeyAccess != nil { + sharedKey = *in.InstallConfig.Config.Azure.AllowSharedKeyAccess } if in.InstallConfig.Config.Azure.CustomerManagedKey == nil { logrus.Debugf("Creating a Block Blob for ignition shim") sasURL, err = CreateBlockBlob(ctx, &CreateBlockBlobInput{ - StorageURL: p.StorageURL, - BlobURL: blobURL, - AuthType: session.AuthType, - TokenCredential: session.TokenCreds, - StorageAccountName: p.StorageAccountName, - StorageAccountKeys: p.StorageAccountKeys, - ClientOpts: p.clientOptions, - BootstrapIgnData: bootstrapIgnData, - CloudEnvironment: in.InstallConfig.Azure.CloudName, - ContainerName: ignitionContainerName, - BlobName: blobName, - StorageSuffix: session.Environment.StorageEndpointSuffix, - ARMEndpoint: in.InstallConfig.Azure.ARMEndpoint, - Session: session, - Region: in.InstallConfig.Config.Azure.Region, - Tags: p.Tags, - ResourceGroupName: p.ResourceGroupName, + StorageURL: p.StorageURL, + BlobURL: blobURL, + AuthType: session.AuthType, + TokenCredential: session.TokenCreds, + StorageAccountName: p.StorageAccountName, + StorageAccountKeys: p.StorageAccountKeys, + AllowSharedKeyAccess: sharedKey, + ClientOpts: p.clientOptions, + BootstrapIgnData: bootstrapIgnData, + CloudEnvironment: in.InstallConfig.Azure.CloudName, + ContainerName: ignitionContainerName, + BlobName: blobName, + StorageSuffix: session.Environment.StorageEndpointSuffix, + ARMEndpoint: in.InstallConfig.Azure.ARMEndpoint, + Session: session, + Region: in.InstallConfig.Config.Azure.Region, + Tags: p.Tags, + ResourceGroupName: p.ResourceGroupName, }) if err != nil { return nil, fmt.Errorf("failed to create BlockBlob for ignition shim: %w", err) @@ -798,26 +807,31 @@ func (p Provider) Ignition(ctx context.Context, in clusterapi.IgnitionInput) ([] } sasURL, err = CreatePageBlob(ctx, &CreatePageBlobInput{ - StorageURL: p.StorageURL, - BlobURL: blobURL, - ImageURL: "", - CloudEnvironment: in.InstallConfig.Azure.CloudName, - TokenCredential: session.TokenCreds, - StorageAccountName: p.StorageAccountName, - BootstrapIgnData: bootstrapIgnData, - ImageLength: lengthBootstrapFile, - StorageAccountKeys: p.StorageAccountKeys, - ClientOpts: p.clientOptions, + StorageURL: p.StorageURL, + BlobURL: blobURL, + ImageURL: "", + CloudEnvironment: in.InstallConfig.Azure.CloudName, + AllowSharedKeyAccess: sharedKey, + TokenCredential: session.TokenCreds, + StorageAccountName: p.StorageAccountName, + BootstrapIgnData: bootstrapIgnData, + ImageLength: lengthBootstrapFile, + StorageAccountKeys: p.StorageAccountKeys, + ClientOpts: p.clientOptions, }) if err != nil { return nil, fmt.Errorf("failed to create PageBlob for ignition shim: %w", err) } } - if sasURL == "" { + if sasURL == "" && !sharedKey { + udc, err := serviceClient.GetUserDelegationCredential(context.Background(), info, nil) + if err != nil { + return nil, fmt.Errorf("failed to create user delegation credentials: %w", err) + } sasQueryParams, err := sas.BlobSignatureValues{ Protocol: sas.ProtocolHTTPS, StartTime: time.Now().UTC().Add(time.Second * -10), - ExpiryTime: time.Now().UTC().Add(2 * time.Hour), + ExpiryTime: time.Now().UTC().Add(1 * time.Hour), Permissions: to.Ptr(sas.ContainerPermissions{Read: true}).String(), ContainerName: "ignition", BlobName: blobName, diff --git a/pkg/infrastructure/azure/storage.go b/pkg/infrastructure/azure/storage.go index cf036094b54..669bff0cebd 100644 --- a/pkg/infrastructure/azure/storage.go +++ b/pkg/infrastructure/azure/storage.go @@ -37,16 +37,17 @@ var ( // CreateStorageAccountInput contains the input parameters for creating a // storage account. type CreateStorageAccountInput struct { - SubscriptionID string - ResourceGroupName string - StorageAccountName string - Region string - Tags map[string]*string - AuthType azic.AuthenticationType - CustomerManagedKey *aztypes.CustomerManagedKey - CloudName aztypes.CloudEnvironment - TokenCredential azcore.TokenCredential - ClientOpts *arm.ClientOptions + SubscriptionID string + ResourceGroupName string + StorageAccountName string + Region string + Tags map[string]*string + AuthType azic.AuthenticationType + AllowSharedKeyAccess bool + CustomerManagedKey *aztypes.CustomerManagedKey + CloudName aztypes.CloudEnvironment + TokenCredential azcore.TokenCredential + ClientOpts *arm.ClientOptions } // CreateStorageAccountOutput contains the return values after creating a @@ -89,7 +90,7 @@ func CreateStorageAccount(ctx context.Context, in *CreateStorageAccountInput) (* sku := armstorage.SKU{ Name: to.Ptr(armstorage.SKUNameStandardLRS), } - + allowSharedKeyAccess := in.CloudName == aztypes.StackCloud || in.AllowSharedKeyAccess accountCreateParameters := armstorage.AccountCreateParameters{ Identity: nil, Kind: storageKind, @@ -97,7 +98,7 @@ func CreateStorageAccount(ctx context.Context, in *CreateStorageAccountInput) (* SKU: &sku, Properties: &armstorage.AccountPropertiesCreateParameters{ AllowBlobPublicAccess: to.Ptr(false), - AllowSharedKeyAccess: to.Ptr(in.CloudName == aztypes.StackCloud), + AllowSharedKeyAccess: to.Ptr(allowSharedKeyAccess), IsLocalUserEnabled: to.Ptr(true), LargeFileSharesState: to.Ptr(armstorage.LargeFileSharesStateEnabled), PublicNetworkAccess: to.Ptr(armstorage.PublicNetworkAccessEnabled), @@ -227,17 +228,18 @@ func CreateBlobContainer(ctx context.Context, in *CreateBlobContainerInput) (*Cr // CreatePageBlobInput containers the input parameters used for creating a page // blob. type CreatePageBlobInput struct { - StorageURL string - BlobURL string - ImageURL string - StorageAccountName string - CloudEnvironment aztypes.CloudEnvironment - BootstrapIgnData []byte - UserDelegatedSAS *sas.UserDelegationCredential - ImageLength int64 - TokenCredential azcore.TokenCredential - StorageAccountKeys []armstorage.AccountKey - ClientOpts *arm.ClientOptions + StorageURL string + BlobURL string + ImageURL string + StorageAccountName string + AllowSharedKeyAccess bool + CloudEnvironment aztypes.CloudEnvironment + BootstrapIgnData []byte + UserDelegatedSAS *sas.UserDelegationCredential + ImageLength int64 + TokenCredential azcore.TokenCredential + StorageAccountKeys []armstorage.AccountKey + ClientOpts *arm.ClientOptions } // CreatePageBlobOutput contains the return values after creating a page blob. @@ -250,17 +252,41 @@ type CreatePageBlobOutput struct { func CreatePageBlob(ctx context.Context, in *CreatePageBlobInput) (string, error) { logrus.Debugf("Getting page blob client") - pageBlobClient, err := pageblob.NewClient( - in.BlobURL, - in.TokenCredential, - &pageblob.ClientOptions{ - ClientOptions: azcore.ClientOptions{ - Cloud: in.ClientOpts.Cloud, + var pageBlobClient *pageblob.Client + var err error + if in.AllowSharedKeyAccess { + if len(in.StorageAccountKeys) == 0 || in.StorageAccountKeys[0].Value == nil { + return "", fmt.Errorf("missing storage account key for shared-key page blob upload") + } + sharedKeyCredential, err := azblob.NewSharedKeyCredential(in.StorageAccountName, *in.StorageAccountKeys[0].Value) + if err != nil { + return "", fmt.Errorf("failed to get shared credentials for storage account: %w", err) + } + pageBlobClient, err = pageblob.NewClientWithSharedKeyCredential( + in.BlobURL, + sharedKeyCredential, + &pageblob.ClientOptions{ + ClientOptions: azcore.ClientOptions{ + Cloud: in.ClientOpts.Cloud, + }, }, - }, - ) - if err != nil { - return "", fmt.Errorf("failed to get page blob client: %w", err) + ) + if err != nil { + return "", fmt.Errorf("failed to get page blob client: %w", err) + } + } else { + pageBlobClient, err = pageblob.NewClient( + in.BlobURL, + in.TokenCredential, + &pageblob.ClientOptions{ + ClientOptions: azcore.ClientOptions{ + Cloud: in.ClientOpts.Cloud, + }, + }, + ) + if err != nil { + return "", fmt.Errorf("failed to get page blob client: %w", err) + } } logrus.Debugf("Creating Page blob and uploading image to it") @@ -292,8 +318,7 @@ func CreatePageBlob(ctx context.Context, in *CreatePageBlobInput) (string, error return "", fmt.Errorf("failed to upload page blob image from URL %s: %w", in.ImageURL, err) } } - if in.CloudEnvironment == aztypes.StackCloud { - // Is this addition OK for when CreatePageBlob() is called from InfraReady() + if in.CloudEnvironment == aztypes.StackCloud || in.AllowSharedKeyAccess { sasURL, err := pageBlobClient.GetSASURL(sas.BlobPermissions{Read: true}, time.Now().Add(time.Minute*60), &blob.GetSASURLOptions{}) if err != nil { return "", fmt.Errorf("failed to get Page Blob SAS URL: %w", err) @@ -442,25 +467,26 @@ func doUploadPagesFromURL(ctx context.Context, pageBlobClient *pageblob.Client, // CreateBlockBlobInput containers the input parameters used for creating a // block blob. type CreateBlockBlobInput struct { - StorageURL string - BlobURL string - StorageAccountName string - BootstrapIgnData []byte - AuthType azic.AuthenticationType - TokenCredential azcore.TokenCredential - StorageAccountKeys []armstorage.AccountKey - ClientOpts *arm.ClientOptions - CloudEnvironment aztypes.CloudEnvironment - ContainerName string - BlobName string - UserDelegatedSAS *sas.UserDelegationCredential - StorageSuffix string - ARMEndpoint string - Region string - ResourceGroupName string - Session *azic.Session - Tags map[string]*string - UserDelegatedCreds *service.UserDelegationCredential + StorageURL string + BlobURL string + StorageAccountName string + AllowSharedKeyAccess bool + BootstrapIgnData []byte + AuthType azic.AuthenticationType + TokenCredential azcore.TokenCredential + StorageAccountKeys []armstorage.AccountKey + ClientOpts *arm.ClientOptions + CloudEnvironment aztypes.CloudEnvironment + ContainerName string + BlobName string + UserDelegatedSAS *sas.UserDelegationCredential + StorageSuffix string + ARMEndpoint string + Region string + ResourceGroupName string + Session *azic.Session + Tags map[string]*string + UserDelegatedCreds *service.UserDelegationCredential } // CreateBlockBlobOutput contains the return values after creating a block @@ -475,23 +501,46 @@ func CreateBlockBlob(ctx context.Context, in *CreateBlockBlobInput) (string, err if in.CloudEnvironment == aztypes.StackCloud { return createBlockBlobOnStack(ctx, in) } - logrus.Debugf("Getting block blob credentials") return createBlockBlob(ctx, in) } func createBlockBlob(ctx context.Context, in *CreateBlockBlobInput) (string, error) { logrus.Debugf("Getting block blob client") - blockBlobClient, err := blockblob.NewClient( - in.BlobURL, - in.TokenCredential, - &blockblob.ClientOptions{ - ClientOptions: azcore.ClientOptions{ - Cloud: in.ClientOpts.Cloud, + var blockBlobClient *blockblob.Client + var err error + if in.AllowSharedKeyAccess { + if len(in.StorageAccountKeys) == 0 || in.StorageAccountKeys[0].Value == nil { + return "", fmt.Errorf("missing storage account key for shared-key block blob upload") + } + sharedKeyCredential, err := azblob.NewSharedKeyCredential(in.StorageAccountName, *in.StorageAccountKeys[0].Value) + if err != nil { + return "", fmt.Errorf("failed to get shared credentials for storage account: %w", err) + } + blockBlobClient, err = blockblob.NewClientWithSharedKeyCredential( + in.BlobURL, + sharedKeyCredential, + &blockblob.ClientOptions{ + ClientOptions: azcore.ClientOptions{ + Cloud: in.ClientOpts.Cloud, + }, }, - }, - ) - if err != nil { - return "", fmt.Errorf("failed to get block blob client: %w", err) + ) + if err != nil { + return "", fmt.Errorf("failed to get block blob client: %w", err) + } + } else { + blockBlobClient, err = blockblob.NewClient( + in.BlobURL, + in.TokenCredential, + &blockblob.ClientOptions{ + ClientOptions: azcore.ClientOptions{ + Cloud: in.ClientOpts.Cloud, + }, + }, + ) + if err != nil { + return "", fmt.Errorf("failed to get block blob client: %w", err) + } } logrus.Debugf("Creating block blob") @@ -503,11 +552,14 @@ func createBlockBlob(ctx context.Context, in *CreateBlockBlobInput) (string, err if err != nil { return "", fmt.Errorf("failed to create block blob: %w", err) } - - // SAS not supported when using managed identity. - if in.AuthType == azic.ManagedIdentityAuth { - return blockBlobClient.URL(), nil + if in.AllowSharedKeyAccess { + sasURL, err := blockBlobClient.GetSASURL(sas.BlobPermissions{Read: true}, time.Now().Add(time.Minute*60), &blob.GetSASURLOptions{}) + if err != nil { + return "", fmt.Errorf("failed to get SAS URL: %w", err) + } + return sasURL, nil } + return "", nil } diff --git a/pkg/types/azure/platform.go b/pkg/types/azure/platform.go index d1ab5665ef5..1b7bcf69e56 100644 --- a/pkg/types/azure/platform.go +++ b/pkg/types/azure/platform.go @@ -45,6 +45,13 @@ type Platform struct { // +optional BaseDomainResourceGroupName string `json:"baseDomainResourceGroupName,omitempty"` + // AllowSharedKeyAccess specifies if shared access key should be enabled for the storage account. + // Default value is true. + // Disabling this will require a new permission "Storage Blob Data Contributor" in azure. + // + // +optional + AllowSharedKeyAccess *bool `json:"allowSharedKeyAccess,omitempty"` + // DefaultMachinePlatform is the default configuration used when // installing on Azure for machine pools which do not define their own // platform configuration. diff --git a/pkg/types/azure/validation/platform.go b/pkg/types/azure/validation/platform.go index f9d2297abd5..9ab22ebfa39 100644 --- a/pkg/types/azure/validation/platform.go +++ b/pkg/types/azure/validation/platform.go @@ -128,6 +128,9 @@ func ValidatePlatform(p *azure.Platform, publish types.PublishingStrategy, fldPa } } + if p.CloudName == azure.StackCloud && p.AllowSharedKeyAccess != nil && !*p.AllowSharedKeyAccess { + allErrs = append(allErrs, field.Invalid(fldPath.Child("allowSharedKeyAccess"), p.AllowSharedKeyAccess, "disabling shared access key creation is unsupported in Azure stack hub")) + } return allErrs }