environment_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	"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)