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
1 change: 1 addition & 0 deletions sdk/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
32 changes: 21 additions & 11 deletions sdk/auth/azure_cli_authorizer.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -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`
Expand Down Expand Up @@ -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

Expand All @@ -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 {
Expand Down Expand Up @@ -219,38 +228,39 @@ 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 {
return nil, fmt.Errorf("no available subscription IDs returned by Azure CLI")
} 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,
Expand Down
36 changes: 36 additions & 0 deletions sdk/auth/azure_cli_authorizer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
2 changes: 2 additions & 0 deletions sdk/auth/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading