/* Copyright 2021 The Dapr Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package azure import ( "context" "encoding/base64" "os" "testing" "time" "github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud" "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" "github.com/Azure/azure-sdk-for-go/sdk/azidentity" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) const ( fakeTenantID = "14bec2db-7f9a-4f3d-97ca-2d384ac83389" fakeClientID = "04bec2db-7f9a-4f3d-97ca-3d384ac83389" // Base64 encoded test pfx cert - Expire date: 09/19/2119. testCert = "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" ) func TestGetClientCert(t *testing.T) { settings, err := NewEnvironmentSettings( map[string]string{ "azureCertificateFile": "testfile", "azureCertificate": "testcert", "azureCertificatePassword": "1234", "azureClientId": fakeClientID, "azureTenantId": fakeTenantID, "vaultName": "vaultName", }, ) assert.NoError(t, err) testCertConfig, _ := settings.GetClientCert() assert.Equal(t, "testfile", testCertConfig.CertificatePath) assert.Equal(t, []byte("testcert"), testCertConfig.CertificateData) assert.Equal(t, "1234", testCertConfig.CertificatePassword) assert.Equal(t, fakeClientID, testCertConfig.ClientID) assert.Equal(t, fakeTenantID, testCertConfig.TenantID) require.NotNil(t, testCertConfig.AzureCloud) assert.Equal(t, "https://login.microsoftonline.com/", testCertConfig.AzureCloud.ActiveDirectoryAuthorityHost) assert.Equal(t, "core.windows.net", settings.EndpointSuffix(ServiceAzureStorage)) } func TestAzureCloud(t *testing.T) { settings, err := NewEnvironmentSettings( map[string]string{ "azureCertificateFile": "testfile", "azureCertificate": "testcert", "azureCertificatePassword": "1234", "azureClientId": fakeClientID, "azureTenantId": fakeTenantID, "vaultName": "vaultName", "azureEnvironment": "AzureChina", }, ) require.NoError(t, err) testCertConfig, _ := settings.GetClientCert() assert.Equal(t, "testfile", testCertConfig.CertificatePath) assert.Equal(t, []byte("testcert"), testCertConfig.CertificateData) assert.Equal(t, "1234", testCertConfig.CertificatePassword) assert.Equal(t, fakeClientID, testCertConfig.ClientID) assert.Equal(t, fakeTenantID, testCertConfig.TenantID) require.NotNil(t, testCertConfig.AzureCloud) assert.Equal(t, "https://login.chinacloudapi.cn/", testCertConfig.AzureCloud.ActiveDirectoryAuthorityHost) assert.Equal(t, "core.chinacloudapi.cn", settings.EndpointSuffix(ServiceAzureStorage)) } func TestEndpointSuffix(t *testing.T) { es := EnvironmentSettings{} es.Cloud = nil assert.Equal(t, "vault.azure.net", es.EndpointSuffix(ServiceAzureKeyVault)) es.Cloud = &cloud.AzurePublic assert.Equal(t, "vault.azure.net", es.EndpointSuffix(ServiceAzureKeyVault)) es.Cloud = &cloud.AzureGovernment assert.Equal(t, "vault.usgovcloudapi.net", es.EndpointSuffix(ServiceAzureKeyVault)) } //nolint:gosec func TestAuthorizorWithCertFile(t *testing.T) { testCertFileName := "./.cert.pfx" certBytes := getTestCert() err := os.WriteFile(testCertFileName, certBytes, 0o644) require.NoError(t, err) settings, err := NewEnvironmentSettings( map[string]string{ "azureCertificateFile": testCertFileName, "azureCertificatePassword": "", "azureClientId": fakeClientID, "azureTenantId": fakeTenantID, "vaultName": "vaultName", }, ) require.NoError(t, err) testCertConfig, _ := settings.GetClientCert() spt, err := testCertConfig.GetTokenCredential() assert.NoError(t, err) assert.NotNil(t, spt) err = os.Remove(testCertFileName) assert.NoError(t, err) } func TestAuthorizorWithCertBytes(t *testing.T) { t.Run("Certificate is valid", func(t *testing.T) { certBytes := getTestCert() settings, err := NewEnvironmentSettings( map[string]string{ "azureCertificate": string(certBytes), "azureCertificatePassword": "", "azureClientId": fakeClientID, "azureTenantId": fakeTenantID, "vaultName": "vaultName", }, ) assert.NoError(t, err) testCertConfig, _ := settings.GetClientCert() assert.NotNil(t, testCertConfig) spt, err := testCertConfig.GetTokenCredential() assert.NoError(t, err) assert.NotNil(t, spt) }) t.Run("Certificate is invalid", func(t *testing.T) { certBytes := getTestCert() settings, err := NewEnvironmentSettings( map[string]string{ "azureCertificate": string(certBytes[0:20]), "azureCertificatePassword": "", "azureClientId": fakeClientID, "azureTenantId": fakeTenantID, "vaultName": "vaultName", }, ) assert.NoError(t, err) testCertConfig, _ := settings.GetClientCert() assert.NotNil(t, testCertConfig) _, err = testCertConfig.GetTokenCredential() assert.Error(t, err) }) } func TestGetMSI(t *testing.T) { settings, err := NewEnvironmentSettings( map[string]string{ "azureClientId": fakeClientID, "vaultName": "vaultName", }, ) assert.NoError(t, err) testCertConfig := settings.GetMSI() assert.Equal(t, fakeClientID, testCertConfig.ClientID) } func TestFallbackToMSI(t *testing.T) { os.Setenv("MSI_ENDPOINT", "test") defer os.Unsetenv("MSI_ENDPOINT") settings, err := NewEnvironmentSettings( map[string]string{ "azureClientId": fakeClientID, "vaultName": "vaultName", }, ) assert.NoError(t, err) spt, err := settings.GetTokenCredential() assert.NoError(t, err) assert.NotNil(t, spt) } func TestAuthorizorWithMSI(t *testing.T) { os.Setenv("MSI_ENDPOINT", "test") defer os.Unsetenv("MSI_ENDPOINT") settings, err := NewEnvironmentSettings( map[string]string{ "azureClientId": fakeClientID, "vaultName": "vaultName", }, ) assert.NoError(t, err) testCertConfig := settings.GetMSI() assert.NotNil(t, testCertConfig) spt, err := settings.GetTokenCredential() assert.NoError(t, err) assert.NotNil(t, spt) } func TestAuthorizorWithMSIAndUserAssignedID(t *testing.T) { os.Setenv("MSI_ENDPOINT", "test") defer os.Unsetenv("MSI_ENDPOINT") settings, err := NewEnvironmentSettings( map[string]string{ "azureClientId": fakeClientID, "vaultName": "vaultName", }, ) assert.NoError(t, err) testCertConfig := settings.GetMSI() assert.NotNil(t, testCertConfig) spt, err := settings.GetTokenCredential() assert.NoError(t, err) assert.NotNil(t, spt) } func getTestCert() []byte { certBytes, _ := base64.StdEncoding.DecodeString(testCert) return certBytes } func TestFallbackToCLI(t *testing.T) { settings, err := NewEnvironmentSettings( map[string]string{ "vaultName": "vaultName", }, ) assert.NoError(t, err) // check whether this test can be run (i.e. Azure CLI is installed and logged in) runTest := false cred, credErr := azidentity.NewAzureCLICredential(nil) if credErr == nil { ctx, cancelFunc := context.WithTimeout(context.Background(), 5*time.Second) defer cancelFunc() token, err := cred.GetToken(ctx, policy.TokenRequestOptions{}) if err == nil && token.Token != "" { runTest = true } } if runTest { spt, err := settings.GetTokenCredential() assert.Nil(t, err) token, _ := spt.GetToken(context.Background(), policy.TokenRequestOptions{}) assert.NotNil(t, token) assert.NotEmpty(t, token.Token) } else { t.Skip("Skipping test as Azure CLI is not installed or logged in. This test would fall through to MSI which is not available in the test environment.") } }