diff --git a/sdk/azidentity/README.md b/sdk/azidentity/README.md index 68b35a545c3b..a97f87961eba 100644 --- a/sdk/azidentity/README.md +++ b/sdk/azidentity/README.md @@ -168,7 +168,8 @@ client := armresources.NewResourceGroupsClient("subscription ID", chain, nil) |-|- |`AZURE_CLIENT_ID`|ID of an Azure Active Directory application |`AZURE_TENANT_ID`|ID of the application's Azure Active Directory tenant -|`AZURE_CLIENT_CERTIFICATE_PATH`|path to a certificate file including private key (without password protection) +|`AZURE_CLIENT_CERTIFICATE_PATH`|path to a certificate file including private key +|`AZURE_CLIENT_CERTIFICATE_PASSWORD`|password of the certificate file, if any #### Username and password diff --git a/sdk/azidentity/environment_credential.go b/sdk/azidentity/environment_credential.go index 16c595d1d375..6b2a9bab6e33 100644 --- a/sdk/azidentity/environment_credential.go +++ b/sdk/azidentity/environment_credential.go @@ -42,7 +42,9 @@ type EnvironmentCredentialOptions struct { // // AZURE_CLIENT_ID: the service principal's client ID // -// AZURE_CLIENT_CERTIFICATE_PATH: path to a PEM or PKCS12 certificate file including the unencrypted private key. +// AZURE_CLIENT_CERTIFICATE_PATH: path to a PEM or PKCS12 certificate file including the private key. +// +// AZURE_CLIENT_CERTIFICATE_PASSWORD: (optional) password for the certificate file. // // User with username and password // @@ -85,7 +87,11 @@ func NewEnvironmentCredential(options *EnvironmentCredentialOptions) (*Environme if err != nil { return nil, fmt.Errorf(`failed to read certificate file "%s": %v`, certPath, err) } - certs, key, err := ParseCertificates(certData, nil) + var password []byte + if v := os.Getenv("AZURE_CLIENT_CERTIFICATE_PASSWORD"); v != "" { + password = []byte(v) + } + certs, key, err := ParseCertificates(certData, password) if err != nil { return nil, fmt.Errorf(`failed to load certificate from "%s": %v`, certPath, err) } diff --git a/sdk/azidentity/environment_credential_test.go b/sdk/azidentity/environment_credential_test.go index 85f67a40572d..4d3b475a03e0 100644 --- a/sdk/azidentity/environment_credential_test.go +++ b/sdk/azidentity/environment_credential_test.go @@ -9,8 +9,10 @@ package azidentity import ( "context" "errors" + "fmt" "os" "reflect" + "strings" "testing" "github.com/Azure/azure-sdk-for-go/sdk/azcore" @@ -116,6 +118,36 @@ func TestEnvironmentCredential_ClientCertificatePathSet(t *testing.T) { } } +func TestEnvironmentCredential_ClientCertificatePassword(t *testing.T) { + for key, value := range map[string]string{ + "AZURE_TENANT_ID": fakeTenantID, + azureClientID: fakeClientID, + "AZURE_CLIENT_CERTIFICATE_PATH": "testdata/certificate_encrypted_key.pfx", + } { + t.Setenv(key, value) + } + for _, correctPassword := range []bool{true, false} { + t.Run(fmt.Sprintf("%v", correctPassword), func(t *testing.T) { + password := "wrong password" + if correctPassword { + password = "password" + } + t.Setenv("AZURE_CLIENT_CERTIFICATE_PASSWORD", password) + cred, err := NewEnvironmentCredential(nil) + if correctPassword { + if err != nil { + t.Fatal(err) + } + if _, ok := cred.cred.(*ClientCertificateCredential); !ok { + t.Fatalf("expected *azidentity.ClientCertificateCredential, got %t", cred) + } + } else if err == nil || !strings.Contains(err.Error(), "password") { + t.Fatal("expected an error about the password") + } + }) + } +} + func TestEnvironmentCredential_UsernameOnlySet(t *testing.T) { resetEnvironmentVarsForTest() err := os.Setenv("AZURE_TENANT_ID", fakeTenantID)