From 3c79cffe525b369ec41af70e28f3eca62159d992 Mon Sep 17 00:00:00 2001 From: Nathan Papapietro Date: Sat, 11 Jul 2026 13:21:14 -0500 Subject: [PATCH 1/2] Support for Azure Active directory Override Signed-off-by: Nathan Papapietro --- README.md | 2 ++ providers/azure/azure.go | 13 ++++++++++- providers/azure/azure_test.go | 42 +++++++++++++++++++++++++++++++++++ providers/azure/helpers.go | 22 ++++++++++++++++-- 4 files changed, 76 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 047af9f0e0..d76962455b 100644 --- a/README.md +++ b/README.md @@ -522,6 +522,8 @@ If `user_assigned_id` is used, authentication is done via user-assigned managed If `storage_connection_string` is set, the values of `storage_account` and `endpoint` values will not be used. Use this method over `storage_account_key` if you need to authenticate via a SAS token. +If `use_workload_identity` is set, `az_tenant_id` and `client_id` may be configured together or supplied through the standard Azure environment variables; `client_secret` must not be set. Set `active_directory_endpoint` to the Microsoft Entra authority host when using a sovereign cloud, for example `https://login.microsoftonline.us/` for Azure Government. + The generic `max_retries` will be used as value for the `pipeline_config`'s `max_tries` and `reader_config`'s `max_retry_requests`. For more control, `max_retries` could be ignored (0) and one could set specific retry values. ##### OpenStack Swift diff --git a/providers/azure/azure.go b/providers/azure/azure.go index 871e7734bd..f0b1b0ff4c 100644 --- a/providers/azure/azure.go +++ b/providers/azure/azure.go @@ -61,6 +61,8 @@ type Config struct { ReaderConfig ReaderConfig `yaml:"reader_config"` PipelineConfig PipelineConfig `yaml:"pipeline_config"` HTTPConfig exthttp.HTTPConfig `yaml:"http_config"` + ActiveDirectoryEndpoint string `yaml:"active_directory_endpoint"` + UseWorkloadIdentity bool `yaml:"use_workload_identity"` // Deprecated: Is automatically set by the Azure SDK. MSIResource string `yaml:"msi_resource"` @@ -92,10 +94,19 @@ func (conf *Config) validate() error { errMsg = append(errMsg, "user_assigned_id cannot be set when using client_id authentication") } - if (conf.AzTenantID != "" || conf.ClientSecret != "" || conf.ClientID != "") && (conf.AzTenantID == "" || conf.ClientSecret == "" || conf.ClientID == "") { + if !conf.UseWorkloadIdentity && (conf.AzTenantID != "" || conf.ClientSecret != "" || conf.ClientID != "") && (conf.AzTenantID == "" || conf.ClientSecret == "" || conf.ClientID == "") { errMsg = append(errMsg, "az_tenant_id, client_id, and client_secret must be set together") } + if conf.UseWorkloadIdentity { + if (conf.AzTenantID == "") != (conf.ClientID == "") { + errMsg = append(errMsg, "az_tenant_id and client_id must be set together when using workload identity authentication") + } + if conf.ClientSecret != "" { + errMsg = append(errMsg, "client_secret cannot be set when using workload identity authentication") + } + } + if conf.StorageAccountKey != "" && conf.StorageConnectionString != "" { errMsg = append(errMsg, "storage_account_key and storage_connection_string cannot both be set") } diff --git a/providers/azure/azure_test.go b/providers/azure/azure_test.go index bee65d26cc..0b3d942b37 100644 --- a/providers/azure/azure_test.go +++ b/providers/azure/azure_test.go @@ -4,6 +4,7 @@ package azure import ( + "strings" "testing" "time" @@ -165,6 +166,47 @@ container: "MyContainer"`), wantFailParse: false, wantFailValidate: true, }, + { + name: "ClientSecret without TenantID or ClientID", + config: []byte(`storage_account: "myAccount" +client_secret: "1234-56578678-655" +container: "MyContainer"`), + wantFailParse: false, + wantFailValidate: true, + }, + { + name: "Valid workload identity", + config: []byte(`storage_account: "myAccount" +az_tenant_id: "1234-56578678-655" +client_id: "1234-56578678-655" +use_workload_identity: true +active_directory_endpoint: "https://login.microsoftonline.us/" +container: "MyContainer"`), + wantFailParse: false, + wantFailValidate: false, + }, + { + name: "Workload identity with client secret", + config: []byte(`storage_account: "myAccount" +az_tenant_id: "1234-56578678-655" +client_id: "1234-56578678-655" +client_secret: "1234-56578678-655" +use_workload_identity: true +container: "MyContainer"`), + wantFailParse: false, + wantFailValidate: true, + }, +} + +func TestGetTokenCredential_CustomActiveDirectoryEndpoint(t *testing.T) { + _, err := getTokenCredential(Config{ + AzTenantID: "tenant", + ClientID: "client", + ClientSecret: "secret", + ActiveDirectoryEndpoint: "http://login.example.com", + }) + testutil.NotOk(t, err) + testutil.Assert(t, strings.Contains(err.Error(), "https"), "expected HTTPS validation error, got %v", err) } func TestConfig_validate(t *testing.T) { diff --git a/providers/azure/helpers.go b/providers/azure/helpers.go index 0b76ddb3fa..bbc880321b 100644 --- a/providers/azure/helpers.go +++ b/providers/azure/helpers.go @@ -9,6 +9,7 @@ import ( "time" "github.com/Azure/azure-sdk-for-go/sdk/azcore" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud" "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" "github.com/Azure/azure-sdk-for-go/sdk/azidentity" "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/container" @@ -86,12 +87,29 @@ func getContainerClient(conf Config, wrapRoundtripper func(http.RoundTripper) ht } func getTokenCredential(conf Config) (azcore.TokenCredential, error) { + clientOptions := azcore.ClientOptions{} + if conf.ActiveDirectoryEndpoint != "" { + clientOptions.Cloud = cloud.Configuration{ActiveDirectoryAuthorityHost: conf.ActiveDirectoryEndpoint} + } + + if conf.UseWorkloadIdentity { + return azidentity.NewWorkloadIdentityCredential(&azidentity.WorkloadIdentityCredentialOptions{ + ClientOptions: clientOptions, + TenantID: conf.AzTenantID, + ClientID: conf.ClientID, + }) + } + if conf.ClientSecret != "" && conf.AzTenantID != "" && conf.ClientID != "" { - return azidentity.NewClientSecretCredential(conf.AzTenantID, conf.ClientID, conf.ClientSecret, &azidentity.ClientSecretCredentialOptions{}) + return azidentity.NewClientSecretCredential(conf.AzTenantID, conf.ClientID, conf.ClientSecret, &azidentity.ClientSecretCredentialOptions{ + ClientOptions: clientOptions, + }) } if conf.UserAssignedID == "" { - return azidentity.NewDefaultAzureCredential(nil) + return azidentity.NewDefaultAzureCredential(&azidentity.DefaultAzureCredentialOptions{ + ClientOptions: clientOptions, + }) } msiOpt := &azidentity.ManagedIdentityCredentialOptions{} From 2476a6220f63e3f93822465bc58b3bc9a95d9fab Mon Sep 17 00:00:00 2001 From: Nathan Papapietro Date: Sat, 11 Jul 2026 13:25:29 -0500 Subject: [PATCH 2/2] Changelog Signed-off-by: Nathan Papapietro --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a0ca252c68..3eaa67ebd0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ NOTE: As semantic versioning states all 0.y.z releases can contain breaking chan We use *breaking :warning:* to mark changes that are not backward compatible (relates only to v0.y.z releases.) ## Unreleased +- [#263](https://github.com/thanos-io/objstore/pull/263) Azure: Support for Azure Active directory Override - [#165](https://github.com/thanos-io/objstore/pull/165) GCS: Upgrade cloud.google.com/go/storage version to `v1.50.0`. - [#38](https://github.com/thanos-io/objstore/pull/38) GCS: Upgrade cloud.google.com/go/storage version to `v1.43.0`. - [#145](https://github.com/thanos-io/objstore/pull/145) Include content length in the response of Get and GetRange.