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