diff --git a/sdk/auth/auth.go b/sdk/auth/auth.go index a731e5e30c..eda0aaf996 100644 --- a/sdk/auth/auth.go +++ b/sdk/auth/auth.go @@ -153,6 +153,7 @@ func NewAuthorizerFromCredentials(ctx context.Context, c Credentials, api enviro TenantId: c.TenantID, AuxTenantIds: c.AuxiliaryTenantIDs, SubscriptionIdHint: c.AzureCliSubscriptionIDHint, + ForceAuthAtTenant: c.ForceAuthAtTenant, } a, err := NewAzureCliAuthorizer(ctx, opts) if err != nil { diff --git a/sdk/auth/azure_cli_authorizer.go b/sdk/auth/azure_cli_authorizer.go index b73dbc1057..0abeb5060b 100644 --- a/sdk/auth/azure_cli_authorizer.go +++ b/sdk/auth/azure_cli_authorizer.go @@ -31,11 +31,16 @@ type AzureCliAuthorizerOptions struct { // SubscriptionIdHint is the subscription to target when selecting an account with which to obtain an access token // Used to hint to Azure CLI which of its signed-in accounts it should select, based on apparent access to the subscription. SubscriptionIdHint string + + // ForceAuthAtTenant skips the use of --subscription when authorising. This allows the CLI authorizer to obtain a tenant level token. + // Use with caution. It is recommended to limit the token scope to a Subscription unless multiple subscriptions in the same tenant + // need to be accessed. + ForceAuthAtTenant bool } // NewAzureCliAuthorizer returns an Authorizer which authenticates using the Azure CLI. func NewAzureCliAuthorizer(ctx context.Context, options AzureCliAuthorizerOptions) (Authorizer, error) { - conf, err := newAzureCliConfig(options.Api, options.TenantId, options.AuxTenantIds, options.SubscriptionIdHint) + conf, err := newAzureCliConfig(options) if err != nil { return nil, err } @@ -96,7 +101,7 @@ func (a *AzureCliAuthorizer) Token(_ context.Context, _ *http.Request) (*oauth2. // Prefer to specify subscription ID if provided, this hints to Azure CLI which account to use in the event // that multiple accounts are signed in, and each account has access to a subset of all subscriptions. - if a.SubscriptionIDHint != "" { + if a.SubscriptionIDHint != "" && !a.conf.ForceTenantAuth { azArgs = append(azArgs, "--subscription", a.conf.SubscriptionIDHint) // Cannot specify both `--subscription` and `--tenant` @@ -175,6 +180,9 @@ type azureCliConfig struct { // TenantID is the required tenant ID for the primary token TenantID string + // ForceTenantAuth enforces token acquisition at the Tenant, rather than the specified subscription. + ForceTenantAuth bool + // AuxiliaryTenantIDs is an optional list of tenant IDs for which to obtain additional tokens AuxiliaryTenantIDs []string @@ -186,12 +194,13 @@ type azureCliConfig struct { } // newAzureCliConfig validates the supplied tenant ID and returns a new azureCliConfig. -func newAzureCliConfig(api environments.Api, tenantId string, auxiliaryTenantIds []string, subscriptionIdHint string) (*azureCliConfig, error) { +func newAzureCliConfig(options AzureCliAuthorizerOptions) (*azureCliConfig, error) { // check az-cli version, ensure that MSAL is supported if err := azurecli.CheckAzVersion(); err != nil { return nil, err } + var tenantId = options.TenantId // obtain default tenant ID if no tenant ID was provided if strings.TrimSpace(tenantId) == "" { if defaultTenantId, err := azurecli.GetDefaultTenantID(); err != nil { @@ -219,8 +228,8 @@ func newAzureCliConfig(api environments.Api, tenantId string, auxiliaryTenantIds } // validate subscriptionIdHint, if applicable (currently only for Resource Manager) - if environments.ApiIsKnownPublished(api, "AzureResourceManager") { - if subscriptionIdHint != "" { + if environments.ApiIsKnownPublished(options.Api, "AzureResourceManager") && !options.ForceAuthAtTenant { + if options.SubscriptionIdHint != "" { if availableSubscriptionIds, err := azurecli.ListAvailableSubscriptionIDs(); err != nil { return nil, err } else if availableSubscriptionIds == nil { @@ -228,29 +237,30 @@ func newAzureCliConfig(api environments.Api, tenantId string, auxiliaryTenantIds } else { found := false for _, subId := range *availableSubscriptionIds { - if strings.EqualFold(subId, subscriptionIdHint) { + if strings.EqualFold(subId, options.SubscriptionIdHint) { found = true break } } if !found { - return nil, fmt.Errorf("the provided subscription ID %q is not known by Azure CLI", subscriptionIdHint) + return nil, fmt.Errorf("the provided subscription ID %q is not known by Azure CLI", options.SubscriptionIdHint) } } } } return &azureCliConfig{ - Api: api, + Api: options.Api, TenantID: tenantId, - AuxiliaryTenantIDs: auxiliaryTenantIds, + AuxiliaryTenantIDs: options.AuxTenantIds, DefaultSubscriptionID: subscriptionId, - SubscriptionIDHint: strings.ToLower(subscriptionIdHint), + SubscriptionIDHint: strings.ToLower(options.SubscriptionIdHint), + ForceTenantAuth: options.ForceAuthAtTenant, }, nil } // TokenSource provides a source for obtaining access tokens using AzureCliAuthorizer. -func (c *azureCliConfig) TokenSource(ctx context.Context) (Authorizer, error) { +func (c *azureCliConfig) TokenSource(_ context.Context) (Authorizer, error) { // Cache access tokens internally to avoid unnecessary `az` invocations return NewCachedAuthorizer(&AzureCliAuthorizer{ TenantID: c.TenantID, diff --git a/sdk/auth/azure_cli_authorizer_test.go b/sdk/auth/azure_cli_authorizer_test.go index da87a7e774..14a71ea5f7 100644 --- a/sdk/auth/azure_cli_authorizer_test.go +++ b/sdk/auth/azure_cli_authorizer_test.go @@ -114,6 +114,42 @@ func TestAccAzureCliAuthorizerWithTenant(t *testing.T) { } } +func TestAccAzureCliAuthorizerForceTenant(t *testing.T) { + test.AccTest(t) + + ctx := context.Background() + + env, err := environments.FromName(test.Environment) + if err != nil { + t.Fatal(err) + } + + opts := auth.AzureCliAuthorizerOptions{ + Api: env.MicrosoftGraph, + TenantId: test.TenantId, + SubscriptionIdHint: test.SubscriptionId, + ForceAuthAtTenant: true, + } + + authorizer, err := auth.NewAzureCliAuthorizer(ctx, opts) + if err != nil { + t.Fatalf("NewAzureCliAuthorizer(): %v", err) + } + + cliAuth, err := testCheckAzureCliAuthorizer(authorizer) + if err != nil { + t.Fatal(err) + } + + if cliAuth.TenantID != test.TenantId { + t.Fatalf("cliAuth.TenantID has unexpected value %q", cliAuth.TenantID) + } + + if _, err = testObtainAccessToken(ctx, authorizer); err != nil { + t.Fatal(err) + } +} + func testCheckAzureCliAuthorizer(authorizer auth.Authorizer) (*auth.AzureCliAuthorizer, error) { if authorizer == nil { return nil, fmt.Errorf("authorizer is nil, expected Authorizer") diff --git a/sdk/auth/config.go b/sdk/auth/config.go index 1b7b71e4d6..ae60b90d10 100644 --- a/sdk/auth/config.go +++ b/sdk/auth/config.go @@ -18,6 +18,8 @@ type Credentials struct { ClientID string // TenantID specifies the Azure Active Directory Tenant to connect to, which must be a valid UUID. TenantID string + // ForceAuthAtTenant forces the Authoriser to obtain tokens at the Tenant scope rather than limiting to Subscription - Currently only meaningful for CLI based authentication + ForceAuthAtTenant bool // EnableAuthenticatingUsingAzureCLI specifies whether Azure CLI authentication should be checked. EnableAuthenticatingUsingAzureCLI bool