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 "errors"
12 "fmt"
13 "os"
14 "strings"
15
16 "github.com/Azure/azure-sdk-for-go/sdk/azcore"
17 "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
18 "github.com/Azure/azure-sdk-for-go/sdk/internal/log"
19)
20
21const envVarSendCertChain = "AZURE_CLIENT_SEND_CERTIFICATE_CHAIN"
22
23// EnvironmentCredentialOptions contains optional parameters for EnvironmentCredential
24type EnvironmentCredentialOptions struct {
25 azcore.ClientOptions
26
27 // DisableInstanceDiscovery should be set true only by applications authenticating in disconnected clouds, or
28 // private clouds such as Azure Stack. It determines whether the credential requests Microsoft Entra instance metadata
29 // from https://login.microsoft.com before authenticating. Setting this to true will skip this request, making
30 // the application responsible for ensuring the configured authority is valid and trustworthy.
31 DisableInstanceDiscovery bool
32 // additionallyAllowedTenants is used only by NewDefaultAzureCredential() to enable that constructor's explicit
33 // option to override the value of AZURE_ADDITIONALLY_ALLOWED_TENANTS. Applications using EnvironmentCredential
34 // directly should set that variable instead. This field should remain unexported to preserve this credential's
35 // unambiguous "all configuration from environment variables" design.
36 additionallyAllowedTenants []string
37}
38
39// EnvironmentCredential authenticates a service principal with a secret or certificate, or a user with a password, depending
40// on environment variable configuration. It reads configuration from these variables, in the following order:
41//
42// # Service principal with client secret
43//
44// AZURE_TENANT_ID: ID of the service principal's tenant. Also called its "directory" ID.
45//
46// AZURE_CLIENT_ID: the service principal's client ID
47//
48// AZURE_CLIENT_SECRET: one of the service principal's client secrets
49//
50// # Service principal with certificate
51//
52// AZURE_TENANT_ID: ID of the service principal's tenant. Also called its "directory" ID.
53//
54// AZURE_CLIENT_ID: the service principal's client ID
55//
56// AZURE_CLIENT_CERTIFICATE_PATH: path to a PEM or PKCS12 certificate file including the private key.
57//
58// AZURE_CLIENT_CERTIFICATE_PASSWORD: (optional) password for the certificate file.
59//
60// Note that this credential uses [ParseCertificates] to load the certificate and key from the file. If this
61// function isn't able to parse your certificate, use [ClientCertificateCredential] instead.
62//
63// # User with username and password
64//
65// AZURE_TENANT_ID: (optional) tenant to authenticate in. Defaults to "organizations".
66//
67// AZURE_CLIENT_ID: client ID of the application the user will authenticate to
68//
69// AZURE_USERNAME: a username (usually an email address)
70//
71// AZURE_PASSWORD: the user's password
72//
73// # Configuration for multitenant applications
74//
75// To enable multitenant authentication, set AZURE_ADDITIONALLY_ALLOWED_TENANTS with a semicolon delimited list of tenants
76// the credential may request tokens from in addition to the tenant specified by AZURE_TENANT_ID. Set
77// AZURE_ADDITIONALLY_ALLOWED_TENANTS to "*" to enable the credential to request a token from any tenant.
78type EnvironmentCredential struct {
79 cred azcore.TokenCredential
80}
81
82// NewEnvironmentCredential creates an EnvironmentCredential. Pass nil to accept default options.
83func NewEnvironmentCredential(options *EnvironmentCredentialOptions) (*EnvironmentCredential, error) {
84 if options == nil {
85 options = &EnvironmentCredentialOptions{}
86 }
87 tenantID := os.Getenv(azureTenantID)
88 if tenantID == "" {
89 return nil, errors.New("missing environment variable AZURE_TENANT_ID")
90 }
91 clientID := os.Getenv(azureClientID)
92 if clientID == "" {
93 return nil, errors.New("missing environment variable " + azureClientID)
94 }
95 // tenants set by NewDefaultAzureCredential() override the value of AZURE_ADDITIONALLY_ALLOWED_TENANTS
96 additionalTenants := options.additionallyAllowedTenants
97 if len(additionalTenants) == 0 {
98 if tenants := os.Getenv(azureAdditionallyAllowedTenants); tenants != "" {
99 additionalTenants = strings.Split(tenants, ";")
100 }
101 }
102 if clientSecret := os.Getenv(azureClientSecret); clientSecret != "" {
103 log.Write(EventAuthentication, "EnvironmentCredential will authenticate with ClientSecretCredential")
104 o := &ClientSecretCredentialOptions{
105 AdditionallyAllowedTenants: additionalTenants,
106 ClientOptions: options.ClientOptions,
107 DisableInstanceDiscovery: options.DisableInstanceDiscovery,
108 }
109 cred, err := NewClientSecretCredential(tenantID, clientID, clientSecret, o)
110 if err != nil {
111 return nil, err
112 }
113 return &EnvironmentCredential{cred: cred}, nil
114 }
115 if certPath := os.Getenv(azureClientCertificatePath); certPath != "" {
116 log.Write(EventAuthentication, "EnvironmentCredential will authenticate with ClientCertificateCredential")
117 certData, err := os.ReadFile(certPath)
118 if err != nil {
119 return nil, fmt.Errorf(`failed to read certificate file "%s": %v`, certPath, err)
120 }
121 var password []byte
122 if v := os.Getenv(azureClientCertificatePassword); v != "" {
123 password = []byte(v)
124 }
125 certs, key, err := ParseCertificates(certData, password)
126 if err != nil {
127 return nil, fmt.Errorf("failed to parse %q due to error %q. This may be due to a limitation of this module's certificate loader. Consider calling NewClientCertificateCredential instead", certPath, err.Error())
128 }
129 o := &ClientCertificateCredentialOptions{
130 AdditionallyAllowedTenants: additionalTenants,
131 ClientOptions: options.ClientOptions,
132 DisableInstanceDiscovery: options.DisableInstanceDiscovery,
133 }
134 if v, ok := os.LookupEnv(envVarSendCertChain); ok {
135 o.SendCertificateChain = v == "1" || strings.ToLower(v) == "true"
136 }
137 cred, err := NewClientCertificateCredential(tenantID, clientID, certs, key, o)
138 if err != nil {
139 return nil, err
140 }
141 return &EnvironmentCredential{cred: cred}, nil
142 }
143 if username := os.Getenv(azureUsername); username != "" {
144 if password := os.Getenv(azurePassword); password != "" {
145 log.Write(EventAuthentication, "EnvironmentCredential will authenticate with UsernamePasswordCredential")
146 o := &UsernamePasswordCredentialOptions{
147 AdditionallyAllowedTenants: additionalTenants,
148 ClientOptions: options.ClientOptions,
149 DisableInstanceDiscovery: options.DisableInstanceDiscovery,
150 }
151 cred, err := NewUsernamePasswordCredential(tenantID, clientID, username, password, o)
152 if err != nil {
153 return nil, err
154 }
155 return &EnvironmentCredential{cred: cred}, nil
156 }
157 return nil, errors.New("no value for AZURE_PASSWORD")
158 }
159 return nil, errors.New("incomplete environment variable configuration. Only AZURE_TENANT_ID and AZURE_CLIENT_ID are set")
160}
161
162// GetToken requests an access token from Microsoft Entra ID. This method is called automatically by Azure SDK clients.
163func (c *EnvironmentCredential) GetToken(ctx context.Context, opts policy.TokenRequestOptions) (azcore.AccessToken, error) {
164 return c.cred.GetToken(ctx, opts)
165}
166
167var _ azcore.TokenCredential = (*EnvironmentCredential)(nil)