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)