default_azure_credential.go

  1//go:build go1.18
  2// +build go1.18
  3
  4// Copyright (c) Microsoft Corporation. All rights reserved.
  5// Licensed under the MIT License.
  6
  7package azidentity
  8
  9import (
 10	"context"
 11	"os"
 12	"strings"
 13
 14	"github.com/Azure/azure-sdk-for-go/sdk/azcore"
 15	"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
 16	"github.com/Azure/azure-sdk-for-go/sdk/internal/log"
 17)
 18
 19// DefaultAzureCredentialOptions contains optional parameters for DefaultAzureCredential.
 20// These options may not apply to all credentials in the chain.
 21type DefaultAzureCredentialOptions struct {
 22	// ClientOptions has additional options for credentials that use an Azure SDK HTTP pipeline. These options don't apply
 23	// to credential types that authenticate via external tools such as the Azure CLI.
 24	azcore.ClientOptions
 25
 26	// AdditionallyAllowedTenants specifies additional tenants for which the credential may acquire tokens. Add
 27	// the wildcard value "*" to allow the credential to acquire tokens for any tenant. This value can also be
 28	// set as a semicolon delimited list of tenants in the environment variable AZURE_ADDITIONALLY_ALLOWED_TENANTS.
 29	AdditionallyAllowedTenants []string
 30	// DisableInstanceDiscovery should be set true only by applications authenticating in disconnected clouds, or
 31	// private clouds such as Azure Stack. It determines whether the credential requests Microsoft Entra instance metadata
 32	// from https://login.microsoft.com before authenticating. Setting this to true will skip this request, making
 33	// the application responsible for ensuring the configured authority is valid and trustworthy.
 34	DisableInstanceDiscovery bool
 35	// TenantID sets the default tenant for authentication via the Azure CLI and workload identity.
 36	TenantID string
 37}
 38
 39// DefaultAzureCredential is a default credential chain for applications that will deploy to Azure.
 40// It combines credentials suitable for deployment with credentials suitable for local development.
 41// It attempts to authenticate with each of these credential types, in the following order, stopping
 42// when one provides a token:
 43//
 44//   - [EnvironmentCredential]
 45//   - [WorkloadIdentityCredential], if environment variable configuration is set by the Azure workload
 46//     identity webhook. Use [WorkloadIdentityCredential] directly when not using the webhook or needing
 47//     more control over its configuration.
 48//   - [ManagedIdentityCredential]
 49//   - [AzureCLICredential]
 50//   - [AzureDeveloperCLICredential]
 51//
 52// Consult the documentation for these credential types for more information on how they authenticate.
 53// Once a credential has successfully authenticated, DefaultAzureCredential will use that credential for
 54// every subsequent authentication.
 55type DefaultAzureCredential struct {
 56	chain *ChainedTokenCredential
 57}
 58
 59// NewDefaultAzureCredential creates a DefaultAzureCredential. Pass nil for options to accept defaults.
 60func NewDefaultAzureCredential(options *DefaultAzureCredentialOptions) (*DefaultAzureCredential, error) {
 61	var creds []azcore.TokenCredential
 62	var errorMessages []string
 63
 64	if options == nil {
 65		options = &DefaultAzureCredentialOptions{}
 66	}
 67	additionalTenants := options.AdditionallyAllowedTenants
 68	if len(additionalTenants) == 0 {
 69		if tenants := os.Getenv(azureAdditionallyAllowedTenants); tenants != "" {
 70			additionalTenants = strings.Split(tenants, ";")
 71		}
 72	}
 73
 74	envCred, err := NewEnvironmentCredential(&EnvironmentCredentialOptions{
 75		ClientOptions:              options.ClientOptions,
 76		DisableInstanceDiscovery:   options.DisableInstanceDiscovery,
 77		additionallyAllowedTenants: additionalTenants,
 78	})
 79	if err == nil {
 80		creds = append(creds, envCred)
 81	} else {
 82		errorMessages = append(errorMessages, "EnvironmentCredential: "+err.Error())
 83		creds = append(creds, &defaultCredentialErrorReporter{credType: "EnvironmentCredential", err: err})
 84	}
 85
 86	wic, err := NewWorkloadIdentityCredential(&WorkloadIdentityCredentialOptions{
 87		AdditionallyAllowedTenants: additionalTenants,
 88		ClientOptions:              options.ClientOptions,
 89		DisableInstanceDiscovery:   options.DisableInstanceDiscovery,
 90		TenantID:                   options.TenantID,
 91	})
 92	if err == nil {
 93		creds = append(creds, wic)
 94	} else {
 95		errorMessages = append(errorMessages, credNameWorkloadIdentity+": "+err.Error())
 96		creds = append(creds, &defaultCredentialErrorReporter{credType: credNameWorkloadIdentity, err: err})
 97	}
 98
 99	o := &ManagedIdentityCredentialOptions{ClientOptions: options.ClientOptions, dac: true}
100	if ID, ok := os.LookupEnv(azureClientID); ok {
101		o.ID = ClientID(ID)
102	}
103	miCred, err := NewManagedIdentityCredential(o)
104	if err == nil {
105		creds = append(creds, miCred)
106	} else {
107		errorMessages = append(errorMessages, credNameManagedIdentity+": "+err.Error())
108		creds = append(creds, &defaultCredentialErrorReporter{credType: credNameManagedIdentity, err: err})
109	}
110
111	cliCred, err := NewAzureCLICredential(&AzureCLICredentialOptions{AdditionallyAllowedTenants: additionalTenants, TenantID: options.TenantID})
112	if err == nil {
113		creds = append(creds, cliCred)
114	} else {
115		errorMessages = append(errorMessages, credNameAzureCLI+": "+err.Error())
116		creds = append(creds, &defaultCredentialErrorReporter{credType: credNameAzureCLI, err: err})
117	}
118
119	azdCred, err := NewAzureDeveloperCLICredential(&AzureDeveloperCLICredentialOptions{
120		AdditionallyAllowedTenants: additionalTenants,
121		TenantID:                   options.TenantID,
122	})
123	if err == nil {
124		creds = append(creds, azdCred)
125	} else {
126		errorMessages = append(errorMessages, credNameAzureDeveloperCLI+": "+err.Error())
127		creds = append(creds, &defaultCredentialErrorReporter{credType: credNameAzureDeveloperCLI, err: err})
128	}
129
130	if len(errorMessages) > 0 {
131		log.Writef(EventAuthentication, "NewDefaultAzureCredential failed to initialize some credentials:\n\t%s", strings.Join(errorMessages, "\n\t"))
132	}
133
134	chain, err := NewChainedTokenCredential(creds, nil)
135	if err != nil {
136		return nil, err
137	}
138	chain.name = "DefaultAzureCredential"
139	return &DefaultAzureCredential{chain: chain}, nil
140}
141
142// GetToken requests an access token from Microsoft Entra ID. This method is called automatically by Azure SDK clients.
143func (c *DefaultAzureCredential) GetToken(ctx context.Context, opts policy.TokenRequestOptions) (azcore.AccessToken, error) {
144	return c.chain.GetToken(ctx, opts)
145}
146
147var _ azcore.TokenCredential = (*DefaultAzureCredential)(nil)
148
149// defaultCredentialErrorReporter is a substitute for credentials that couldn't be constructed.
150// Its GetToken method always returns a credentialUnavailableError having the same message as
151// the error that prevented constructing the credential. This ensures the message is present
152// in the error returned by ChainedTokenCredential.GetToken()
153type defaultCredentialErrorReporter struct {
154	credType string
155	err      error
156}
157
158func (d *defaultCredentialErrorReporter) GetToken(ctx context.Context, opts policy.TokenRequestOptions) (azcore.AccessToken, error) {
159	if _, ok := d.err.(credentialUnavailable); ok {
160		return azcore.AccessToken{}, d.err
161	}
162	return azcore.AccessToken{}, newCredentialUnavailableError(d.credType, d.err.Error())
163}
164
165var _ azcore.TokenCredential = (*defaultCredentialErrorReporter)(nil)