From d9404337367df922bcbf4e2771e182288ea6717f Mon Sep 17 00:00:00 2001 From: Daniel-Davies <33356828+Daniel-Davies@users.noreply.github.com> Date: Fri, 19 Dec 2025 17:59:56 +0000 Subject: [PATCH] [feat] Add config option to override Azure AD endpoint (#20267) Co-authored-by: Trevor Whitney --- docs/sources/shared/configuration.md | 5 ++ .../chunk/client/azure/blob_storage_client.go | 66 +++++++++++-------- .../client/azure/blob_storage_client_test.go | 46 +++++++++++++ 3 files changed, 88 insertions(+), 29 deletions(-) diff --git a/docs/sources/shared/configuration.md b/docs/sources/shared/configuration.md index a854c77dc1..d26ca88e5f 100644 --- a/docs/sources/shared/configuration.md +++ b/docs/sources/shared/configuration.md @@ -2097,6 +2097,11 @@ The `azure_storage_config` block configures the connection to Azure object stora # CLI flag: -.azure.container-name [container_name: | default = "loki"] +# Azure active directory endpoint override. Use when the Azure SDK does not +# support your environment. +# CLI flag: -.azure.active-directory-endpoint +[active_directory_endpoint: | default = ""] + # Azure storage endpoint suffix without schema. The storage account name will be # prefixed to this value to create the FQDN. # CLI flag: -.azure.endpoint-suffix diff --git a/pkg/storage/chunk/client/azure/blob_storage_client.go b/pkg/storage/chunk/client/azure/blob_storage_client.go index 486fa9cd00..973763989b 100644 --- a/pkg/storage/chunk/client/azure/blob_storage_client.go +++ b/pkg/storage/chunk/client/azure/blob_storage_client.go @@ -84,27 +84,28 @@ var ( // BlobStorageConfig defines the configurable flags that can be defined when using azure blob storage. type BlobStorageConfig struct { - Environment string `yaml:"environment"` - StorageAccountName string `yaml:"account_name"` - StorageAccountKey flagext.Secret `yaml:"account_key"` - ConnectionString string `yaml:"connection_string"` - ContainerName string `yaml:"container_name"` - EndpointSuffix string `yaml:"endpoint_suffix"` - UseManagedIdentity bool `yaml:"use_managed_identity"` - UseFederatedToken bool `yaml:"use_federated_token"` - UserAssignedID string `yaml:"user_assigned_id"` - UseServicePrincipal bool `yaml:"use_service_principal"` - ClientID string `yaml:"client_id"` - ClientSecret flagext.Secret `yaml:"client_secret"` - TenantID string `yaml:"tenant_id"` - ChunkDelimiter string `yaml:"chunk_delimiter"` - DownloadBufferSize int `yaml:"download_buffer_size"` - UploadBufferSize int `yaml:"upload_buffer_size"` - UploadBufferCount int `yaml:"upload_buffer_count"` - RequestTimeout time.Duration `yaml:"request_timeout"` - MaxRetries int `yaml:"max_retries"` - MinRetryDelay time.Duration `yaml:"min_retry_delay"` - MaxRetryDelay time.Duration `yaml:"max_retry_delay"` + Environment string `yaml:"environment"` + StorageAccountName string `yaml:"account_name"` + StorageAccountKey flagext.Secret `yaml:"account_key"` + ConnectionString string `yaml:"connection_string"` + ContainerName string `yaml:"container_name"` + ActiveDirectoryEndpoint string `yaml:"active_directory_endpoint"` + EndpointSuffix string `yaml:"endpoint_suffix"` + UseManagedIdentity bool `yaml:"use_managed_identity"` + UseFederatedToken bool `yaml:"use_federated_token"` + UserAssignedID string `yaml:"user_assigned_id"` + UseServicePrincipal bool `yaml:"use_service_principal"` + ClientID string `yaml:"client_id"` + ClientSecret flagext.Secret `yaml:"client_secret"` + TenantID string `yaml:"tenant_id"` + ChunkDelimiter string `yaml:"chunk_delimiter"` + DownloadBufferSize int `yaml:"download_buffer_size"` + UploadBufferSize int `yaml:"upload_buffer_size"` + UploadBufferCount int `yaml:"upload_buffer_count"` + RequestTimeout time.Duration `yaml:"request_timeout"` + MaxRetries int `yaml:"max_retries"` + MinRetryDelay time.Duration `yaml:"min_retry_delay"` + MaxRetryDelay time.Duration `yaml:"max_retry_delay"` } type authFunctions struct { @@ -125,6 +126,7 @@ func (c *BlobStorageConfig) RegisterFlagsWithPrefix(prefix string, f *flag.FlagS f.StringVar(&c.ConnectionString, prefix+"azure.connection-string", "", "If `connection-string` is set, the values of `account-name` and `endpoint-suffix` values will not be used. Use this method over `account-key` if you need to authenticate via a SAS token. Or if you use the Azurite emulator.") f.StringVar(&c.ContainerName, prefix+"azure.container-name", constants.Loki, "Name of the storage account blob container used to store chunks. This container must be created before running cortex.") f.StringVar(&c.EndpointSuffix, prefix+"azure.endpoint-suffix", "", "Azure storage endpoint suffix without schema. The storage account name will be prefixed to this value to create the FQDN.") + f.StringVar(&c.ActiveDirectoryEndpoint, prefix+"azure.active-directory-endpoint", "", "Azure active directory endpoint override. Use when the Azure SDK does not support your environment.") f.BoolVar(&c.UseManagedIdentity, prefix+"azure.use-managed-identity", false, "Use Managed Identity to authenticate to the Azure storage account.") f.BoolVar(&c.UseFederatedToken, prefix+"azure.use-federated-token", false, "Use Federated Token to authenticate to the Azure storage account.") f.StringVar(&c.UserAssignedID, prefix+"azure.user-assigned-id", "", "User assigned identity ID to authenticate to the Azure storage account.") @@ -516,14 +518,20 @@ func (b *BlobStorage) getServicePrincipalToken(authFunctions authFunctions) (*ad } func (b *BlobStorage) servicePrincipalTokenFromFederatedToken(resource string, newOAuthConfigFunc func(activeDirectoryEndpoint, tenantID string) (*adal.OAuthConfig, error), newServicePrincipalTokenFromFederatedTokenFunc func(oauthConfig adal.OAuthConfig, clientID string, jwt string, resource string, callbacks ...adal.TokenRefreshCallback) (*adal.ServicePrincipalToken, error)) (*adal.ServicePrincipalToken, error) { - environmentName := azurePublicCloud - if b.cfg.Environment != azureGlobal { - environmentName = b.cfg.Environment - } + activeDirectoryEndpoint := b.cfg.ActiveDirectoryEndpoint - env, err := azure.EnvironmentFromName(environmentName) - if err != nil { - return nil, err + if activeDirectoryEndpoint == "" { + environmentName := azurePublicCloud + if b.cfg.Environment != azureGlobal { + environmentName = b.cfg.Environment + } + + // Azure SDK does NOT ship with endpoints for certain environments, e.g. IL6 + env, err := azure.EnvironmentFromName(environmentName) + if err != nil { + return nil, err + } + activeDirectoryEndpoint = env.ActiveDirectoryEndpoint } azClientID := os.Getenv("AZURE_CLIENT_ID") @@ -536,7 +544,7 @@ func (b *BlobStorage) servicePrincipalTokenFromFederatedToken(resource string, n jwt := string(jwtBytes) - oauthConfig, err := newOAuthConfigFunc(env.ActiveDirectoryEndpoint, azTenantID) + oauthConfig, err := newOAuthConfigFunc(activeDirectoryEndpoint, azTenantID) if err != nil { return nil, err } diff --git a/pkg/storage/chunk/client/azure/blob_storage_client_test.go b/pkg/storage/chunk/client/azure/blob_storage_client_test.go index 2fefb6c795..ab7376cc1f 100644 --- a/pkg/storage/chunk/client/azure/blob_storage_client_test.go +++ b/pkg/storage/chunk/client/azure/blob_storage_client_test.go @@ -174,6 +174,52 @@ func Test_Hedging(t *testing.T) { } } +func (suite *FederatedTokenTestSuite) Test_ServicePrincipalTokenFromFederatedToken_ActiveDirectoryEndpoint_Default() { + suite.config.cfg.ActiveDirectoryEndpoint = "" + suite.config.cfg.Environment = azureGlobal + + newOAuthConfigFunc := func(activeDirectoryEndpoint, _ string) (*adal.OAuthConfig, error) { + require.Equal(suite.T(), azure.PublicCloud.ActiveDirectoryEndpoint, activeDirectoryEndpoint) + return suite.mockOAuthConfig, nil + } + + servicePrincipalTokenFromFederatedTokenFunc := func(_ adal.OAuthConfig, _, _, _ string, _ ...adal.TokenRefreshCallback) (*adal.ServicePrincipalToken, error) { + return suite.mockedServicePrincipalToken, nil + } + + _, err := suite.config.servicePrincipalTokenFromFederatedToken( + "https://test.blob.core.windows.net", + newOAuthConfigFunc, + servicePrincipalTokenFromFederatedTokenFunc, + ) + + require.NoError(suite.T(), err) +} + +func (suite *FederatedTokenTestSuite) Test_ServicePrincipalTokenFromFederatedToken_ActiveDirectoryEndpoint_Override() { + testAdEndpoint := "https://login.microsoftonline.test" + + suite.config.cfg.ActiveDirectoryEndpoint = testAdEndpoint + suite.config.cfg.Environment = azureGlobal + + newOAuthConfigFunc := func(activeDirectoryEndpoint, _ string) (*adal.OAuthConfig, error) { + require.Equal(suite.T(), testAdEndpoint, activeDirectoryEndpoint) + return suite.mockOAuthConfig, nil + } + + servicePrincipalTokenFromFederatedTokenFunc := func(_ adal.OAuthConfig, _, _, _ string, _ ...adal.TokenRefreshCallback) (*adal.ServicePrincipalToken, error) { + return suite.mockedServicePrincipalToken, nil + } + + _, err := suite.config.servicePrincipalTokenFromFederatedToken( + "https://test.blob.core.windows.net", + newOAuthConfigFunc, + servicePrincipalTokenFromFederatedTokenFunc, + ) + + require.NoError(suite.T(), err) +} + func Test_DefaultContainerURL(t *testing.T) { c, err := NewBlobStorage(&BlobStorageConfig{ ContainerName: "foo",