Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions NEXT_CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@

### Bug Fixes

- Fixed Azure CLI authentication for federated token service principals in AKS
workload identity environments. The SDK now properly detects service principals
with GUID-like names as federated tokens and skips tenant ID parameters that
cause authentication failures.

### Documentation

### Internal Changes
Expand Down
13 changes: 11 additions & 2 deletions config/auth_azure_cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ func (ts *azureCliTokenSource) Token() (*oauth2.Token, error) {
func (ts *azureCliTokenSource) getTokenBytes() ([]byte, error) {
// When fetching an access token from the CLI with a managed identity, the tenant ID should not be specified.
// https://github.com/hashicorp/go-azure-sdk/pull/910/files demonstrates how to detect whether the CLI is authenticated
// using a managed identity.
// using a managed identity or federated token.
accountRaw, err := runCommand(ts.ctx, "az", []string{"account", "show", "--output", "json"})
if err != nil {
return nil, fmt.Errorf("cannot get account info: %w", err)
Expand All @@ -176,9 +176,18 @@ func (ts *azureCliTokenSource) getTokenBytes() ([]byte, error) {
if err := json.Unmarshal(accountRaw, &account); err != nil {
return nil, fmt.Errorf("cannot unmarshal account info: %w", err)
}

isMsi := account.User.Type == "servicePrincipal" && (account.User.Name == "systemAssignedIdentity" || account.User.Name == "userAssignedIdentity")
if !isMsi {
return ts.getTokenBytesWithTenantId(ts.azureTenantId)
// For regular service principals, try with tenant ID first
result, err := ts.getTokenBytesWithTenantId(ts.azureTenantId)
if err != nil && account.User.Type == "servicePrincipal" {
// If it fails for service principals, it might be a federated token scenario
// where tenant ID should not be specified. Fall back to no tenant ID.
logger.Infof(ts.ctx, "Failed to get token with tenant ID for service principal, trying without tenant ID: %v", err)
return ts.getTokenBytesWithTenantId("")
}
return result, err
}
return ts.getTokenBytesWithTenantId("")
}
Expand Down
40 changes: 40 additions & 0 deletions config/auth_azure_cli_federated_token_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package config

import (
"context"
"testing"

"github.com/databricks/databricks-sdk-go/internal/env"
"github.com/stretchr/testify/assert"
)

func TestAzureCliCredentials_FederatedTokenServicePrincipal(t *testing.T) {
// This test verifies the fix where service principals authenticated via
// federated token (like in AKS with workload identity) use a fallback mechanism:
// try with tenant ID first, then retry without tenant ID if it fails.

env.CleanupEnvironment(t)
t.Setenv("PATH", testdataPath())

// Simulate a service principal with a client ID (not systemAssignedIdentity/userAssignedIdentity)
// This represents the federated token scenario that occurs in AKS workload identity
t.Setenv("AZ_USER_NAME", "5817e630-86b3-4f67-a38e-a63e6a1a401c")
t.Setenv("AZ_USER_TYPE", "servicePrincipal")

// This makes the mock az command fail when --tenant is passed, simulating the federated
// token scenario where tenant ID causes authentication failure
t.Setenv("FAIL_IF_TENANT_ID_SET", "true")

aa := AzureCliCredentials{}
cfg := &Config{
Host: "https://adb-1891644720860465.5.azuredatabricks.net/",
AzureTenantID: "e6a2f6d5-ece9-4c0d-9464-9c493497cb8f",
}

// With the fallback fix, this should work: first attempt with --tenant fails,
// then fallback without --tenant succeeds
visitor, err := aa.Configure(context.Background(), cfg)

assert.NoError(t, err, "Authentication should work with federated token service principals via fallback mechanism")
assert.NotNil(t, visitor, "Should return a valid credentials provider")
}
Loading