| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538 |
- /*
- Copyright © The ESO 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
- https://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 keyvault
- import (
- "context"
- "testing"
- corev1 "k8s.io/api/core/v1"
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- "k8s.io/utils/ptr"
- "sigs.k8s.io/controller-runtime/pkg/client/fake"
- esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
- v1 "github.com/external-secrets/external-secrets/apis/meta/v1"
- )
- // TestFeatureFlagRouting tests that the UseAzureSDK feature flag correctly routes to the appropriate implementation.
- func TestFeatureFlagRouting(t *testing.T) {
- testCases := []struct {
- name string
- useAzureSDK *bool
- expectNewSDK bool
- description string
- }{
- {
- name: "default_legacy_sdk",
- useAzureSDK: nil,
- expectNewSDK: false,
- description: "When UseAzureSDK is nil (default), should use legacy SDK",
- },
- {
- name: "explicit_legacy_sdk",
- useAzureSDK: new(false),
- expectNewSDK: false,
- description: "When UseAzureSDK is explicitly false, should use legacy SDK",
- },
- {
- name: "explicit_new_sdk",
- useAzureSDK: new(true),
- expectNewSDK: true,
- description: "When UseAzureSDK is true, should use new SDK",
- },
- }
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
- // Create test provider with the specified feature flag
- provider := &esv1.AzureKVProvider{
- VaultURL: new("https://test-vault.vault.azure.net/"),
- TenantID: new("test-tenant"),
- AuthType: ptr.To(esv1.AzureServicePrincipal),
- UseAzureSDK: tc.useAzureSDK,
- AuthSecretRef: &esv1.AzureKVAuth{
- ClientID: &v1.SecretKeySelector{
- Name: "test-secret",
- Key: "client-id",
- },
- ClientSecret: &v1.SecretKeySelector{
- Name: "test-secret",
- Key: "client-secret",
- },
- },
- }
- // Create Azure client
- azure := &Azure{
- provider: provider,
- }
- // Test the useNewSDK() method
- result := azure.useNewSDK()
- if result != tc.expectNewSDK {
- t.Errorf("Expected useNewSDK() to return %v for %s, got %v", tc.expectNewSDK, tc.description, result)
- }
- })
- }
- }
- // TestClientInitialization tests that both client initialization paths work correctly.
- func TestClientInitialization(t *testing.T) {
- // Create test secret with credentials
- secret := &corev1.Secret{
- ObjectMeta: metav1.ObjectMeta{
- Name: "test-secret",
- Namespace: "test-namespace",
- },
- Data: map[string][]byte{
- "client-id": []byte("test-client-id"),
- "client-secret": []byte("test-client-secret"),
- },
- }
- fakeClient := fake.NewClientBuilder().WithObjects(secret).Build()
- testCases := []struct {
- name string
- useAzureSDK *bool
- expectedErrorPrefix string
- description string
- }{
- {
- name: "legacy_client_init",
- useAzureSDK: new(false),
- expectedErrorPrefix: "", // May succeed or fail with auth errors, but should not panic
- description: "Legacy client initialization should not panic",
- },
- {
- name: "new_sdk_client_init",
- useAzureSDK: new(true),
- expectedErrorPrefix: "", // May succeed or fail with auth errors, but should not panic
- description: "New SDK client initialization should not panic",
- },
- }
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
- provider := &esv1.AzureKVProvider{
- VaultURL: new("https://test-vault.vault.azure.net/"),
- TenantID: new("test-tenant"),
- AuthType: ptr.To(esv1.AzureServicePrincipal),
- UseAzureSDK: tc.useAzureSDK,
- AuthSecretRef: &esv1.AzureKVAuth{
- ClientID: &v1.SecretKeySelector{
- Name: "test-secret",
- Key: "client-id",
- },
- ClientSecret: &v1.SecretKeySelector{
- Name: "test-secret",
- Key: "client-secret",
- },
- },
- }
- store := &esv1.SecretStore{
- ObjectMeta: metav1.ObjectMeta{
- Name: "test-store",
- Namespace: "test-namespace",
- },
- Spec: esv1.SecretStoreSpec{
- Provider: &esv1.SecretStoreProvider{
- AzureKV: provider,
- },
- },
- }
- // Test that client initialization doesn't panic
- defer func() {
- if r := recover(); r != nil {
- t.Errorf("Client initialization panicked for %s: %v", tc.description, r)
- }
- }()
- azure := &Azure{}
- _, err := azure.NewClient(context.Background(), store, fakeClient, "test-namespace")
- // We expect errors due to authentication issues in tests, but no panics
- // The important thing is that the code paths are exercised without crashing
- if err != nil {
- t.Logf("Expected auth error for %s: %v", tc.description, err)
- }
- })
- }
- }
- // TestConfigurationValidation tests that the feature flag is properly validated and accepted.
- func TestConfigurationValidation(t *testing.T) {
- testCases := []struct {
- name string
- useAzureSDK *bool
- expectValid bool
- description string
- }{
- {
- name: "nil_feature_flag",
- useAzureSDK: nil,
- expectValid: true,
- description: "Nil feature flag should be valid (defaults to legacy)",
- },
- {
- name: "false_feature_flag",
- useAzureSDK: new(false),
- expectValid: true,
- description: "False feature flag should be valid (legacy SDK)",
- },
- {
- name: "true_feature_flag",
- useAzureSDK: new(true),
- expectValid: true,
- description: "True feature flag should be valid (new SDK)",
- },
- }
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
- provider := &esv1.AzureKVProvider{
- VaultURL: new("https://test-vault.vault.azure.net/"),
- TenantID: new("test-tenant"),
- AuthType: ptr.To(esv1.AzureServicePrincipal),
- UseAzureSDK: tc.useAzureSDK,
- AuthSecretRef: &esv1.AzureKVAuth{
- ClientID: &v1.SecretKeySelector{
- Name: "test-secret",
- Key: "client-id",
- },
- ClientSecret: &v1.SecretKeySelector{
- Name: "test-secret",
- Key: "client-secret",
- },
- },
- }
- store := &esv1.SecretStore{
- Spec: esv1.SecretStoreSpec{
- Provider: &esv1.SecretStoreProvider{
- AzureKV: provider,
- },
- },
- }
- azure := &Azure{}
- warnings, err := azure.ValidateStore(store)
- if tc.expectValid {
- if err != nil {
- t.Errorf("Expected validation to pass for %s, got error: %v", tc.description, err)
- }
- if len(warnings) > 0 {
- t.Logf("Validation warnings for %s: %v", tc.description, warnings)
- }
- } else if err == nil {
- t.Errorf("Expected validation to fail for %s, but it passed", tc.description)
- }
- })
- }
- }
- // TestBackwardCompatibility ensures that existing configurations continue to work.
- func TestBackwardCompatibility(t *testing.T) {
- // Test that existing configurations without UseAzureSDK still work
- provider := &esv1.AzureKVProvider{
- VaultURL: new("https://test-vault.vault.azure.net/"),
- TenantID: new("test-tenant"),
- AuthType: ptr.To(esv1.AzureServicePrincipal),
- // UseAzureSDK intentionally omitted to test backward compatibility
- AuthSecretRef: &esv1.AzureKVAuth{
- ClientID: &v1.SecretKeySelector{
- Name: "test-secret",
- Key: "client-id",
- },
- ClientSecret: &v1.SecretKeySelector{
- Name: "test-secret",
- Key: "client-secret",
- },
- },
- }
- azure := &Azure{
- provider: provider,
- }
- // Should default to legacy SDK (false)
- if azure.useNewSDK() {
- t.Error("Expected backward compatibility: nil UseAzureSDK should default to legacy SDK (false)")
- }
- // Validation should still pass
- store := &esv1.SecretStore{
- Spec: esv1.SecretStoreSpec{
- Provider: &esv1.SecretStoreProvider{
- AzureKV: provider,
- },
- },
- }
- warnings, err := azure.ValidateStore(store)
- if err != nil {
- t.Errorf("Backward compatibility failed: existing configuration should validate, got error: %v", err)
- }
- if len(warnings) > 0 {
- t.Logf("Backward compatibility warnings: %v", warnings)
- }
- }
- // TestAzureStackCloudConfiguration tests Azure Stack Cloud configuration validation.
- func TestAzureStackCloudConfiguration(t *testing.T) {
- testCases := []struct {
- name string
- useAzureSDK *bool
- envType esv1.AzureEnvironmentType
- customConfig *esv1.AzureCustomCloudConfig
- expectError bool
- expectedError string
- description string
- }{
- {
- name: "azure_stack_with_new_sdk_and_config",
- useAzureSDK: new(true),
- envType: esv1.AzureEnvironmentAzureStackCloud,
- customConfig: &esv1.AzureCustomCloudConfig{
- ActiveDirectoryEndpoint: "https://login.microsoftonline.com/",
- KeyVaultEndpoint: new("https://vault.local.azurestack.external/"),
- KeyVaultDNSSuffix: new(".vault.local.azurestack.external"),
- },
- expectError: false,
- description: "Azure Stack with new SDK and custom config should be valid",
- },
- {
- name: "azure_stack_without_custom_config",
- useAzureSDK: new(true),
- envType: esv1.AzureEnvironmentAzureStackCloud,
- customConfig: nil,
- expectError: true,
- expectedError: "CustomCloudConfig is required when EnvironmentType is AzureStackCloud",
- description: "Azure Stack without custom config should fail",
- },
- {
- name: "azure_stack_with_legacy_sdk",
- useAzureSDK: new(false),
- envType: esv1.AzureEnvironmentAzureStackCloud,
- customConfig: &esv1.AzureCustomCloudConfig{
- ActiveDirectoryEndpoint: "https://login.microsoftonline.com/",
- },
- expectError: true,
- expectedError: "CustomCloudConfig requires UseAzureSDK to be set to true - the legacy SDK does not support custom clouds",
- description: "Azure Stack with legacy SDK should fail",
- },
- {
- name: "azure_stack_without_new_sdk_flag",
- useAzureSDK: nil, // defaults to false
- envType: esv1.AzureEnvironmentAzureStackCloud,
- customConfig: &esv1.AzureCustomCloudConfig{
- ActiveDirectoryEndpoint: "https://login.microsoftonline.com/",
- },
- expectError: true,
- expectedError: "CustomCloudConfig requires UseAzureSDK to be set to true - the legacy SDK does not support custom clouds",
- description: "Azure Stack without explicit new SDK flag should fail",
- },
- {
- name: "azure_stack_missing_aad_endpoint",
- useAzureSDK: new(true),
- envType: esv1.AzureEnvironmentAzureStackCloud,
- customConfig: &esv1.AzureCustomCloudConfig{
- KeyVaultEndpoint: new("https://vault.custom.cloud/"),
- },
- expectError: true,
- expectedError: "activeDirectoryEndpoint is required in CustomCloudConfig",
- description: "Azure Stack without AAD endpoint should fail",
- },
- {
- name: "custom_config_with_china_cloud",
- useAzureSDK: new(true),
- envType: esv1.AzureEnvironmentChinaCloud,
- customConfig: &esv1.AzureCustomCloudConfig{
- ActiveDirectoryEndpoint: "https://login.partner.microsoftonline.cn/",
- },
- expectError: false,
- description: "Custom config with ChinaCloud environment should be allowed for AKS Workload Identity",
- },
- {
- name: "public_cloud_without_custom_config",
- useAzureSDK: new(true),
- envType: esv1.AzureEnvironmentPublicCloud,
- customConfig: nil,
- expectError: false,
- description: "Public cloud without custom config should be valid",
- },
- }
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
- provider := &esv1.AzureKVProvider{
- VaultURL: new("https://test-vault.vault.azure.net/"),
- TenantID: new("test-tenant"),
- AuthType: ptr.To(esv1.AzureServicePrincipal),
- UseAzureSDK: tc.useAzureSDK,
- EnvironmentType: tc.envType,
- CustomCloudConfig: tc.customConfig,
- AuthSecretRef: &esv1.AzureKVAuth{
- ClientID: &v1.SecretKeySelector{
- Name: "test-secret",
- Key: "client-id",
- },
- ClientSecret: &v1.SecretKeySelector{
- Name: "test-secret",
- Key: "client-secret",
- },
- },
- }
- azure := &Azure{provider: provider}
- store := &esv1.SecretStore{
- Spec: esv1.SecretStoreSpec{
- Provider: &esv1.SecretStoreProvider{
- AzureKV: provider,
- },
- },
- }
- warnings, err := azure.ValidateStore(store)
- if tc.expectError {
- if err == nil {
- t.Errorf("Expected validation to fail for %s, but it succeeded", tc.description)
- } else if tc.expectedError != "" && err.Error() != tc.expectedError {
- t.Errorf("Expected error message '%s' for %s, got: %v", tc.expectedError, tc.description, err)
- }
- } else {
- if err != nil {
- t.Errorf("Expected validation to succeed for %s, but got error: %v", tc.description, err)
- }
- }
- if len(warnings) > 0 {
- t.Logf("Warnings for %s: %v", tc.name, warnings)
- }
- })
- }
- }
- // TestGetCloudConfiguration tests the cloud configuration resolution.
- func TestGetCloudConfiguration(t *testing.T) {
- testCases := []struct {
- name string
- provider *esv1.AzureKVProvider
- expectError bool
- expectedError string
- description string
- }{
- {
- name: "public_cloud",
- provider: &esv1.AzureKVProvider{
- EnvironmentType: esv1.AzureEnvironmentPublicCloud,
- },
- expectError: false,
- description: "Public cloud should return valid configuration",
- },
- {
- name: "us_government_cloud",
- provider: &esv1.AzureKVProvider{
- EnvironmentType: esv1.AzureEnvironmentUSGovernmentCloud,
- },
- expectError: false,
- description: "US Government cloud should return valid configuration",
- },
- {
- name: "china_cloud",
- provider: &esv1.AzureKVProvider{
- EnvironmentType: esv1.AzureEnvironmentChinaCloud,
- },
- expectError: false,
- description: "China cloud should return valid configuration",
- },
- {
- name: "china_cloud_with_custom_endpoint",
- provider: &esv1.AzureKVProvider{
- EnvironmentType: esv1.AzureEnvironmentChinaCloud,
- UseAzureSDK: new(true),
- CustomCloudConfig: &esv1.AzureCustomCloudConfig{
- ActiveDirectoryEndpoint: "https://login.partner.microsoftonline.cn/",
- KeyVaultEndpoint: new("https://vault.azure.cn/"),
- ResourceManagerEndpoint: new("https://management.chinacloudapi.cn/"),
- },
- },
- expectError: false,
- description: "China cloud with custom AAD endpoint for AKS Workload Identity should work",
- },
- {
- name: "azure_stack_with_config",
- provider: &esv1.AzureKVProvider{
- EnvironmentType: esv1.AzureEnvironmentAzureStackCloud,
- UseAzureSDK: new(true),
- CustomCloudConfig: &esv1.AzureCustomCloudConfig{
- ActiveDirectoryEndpoint: "https://login.local.azurestack.external/",
- KeyVaultEndpoint: new("https://vault.local.azurestack.external/"),
- },
- },
- expectError: false,
- description: "Azure Stack with valid config should return custom configuration",
- },
- {
- name: "azure_stack_without_new_sdk",
- provider: &esv1.AzureKVProvider{
- EnvironmentType: esv1.AzureEnvironmentAzureStackCloud,
- UseAzureSDK: new(false),
- CustomCloudConfig: &esv1.AzureCustomCloudConfig{
- ActiveDirectoryEndpoint: "https://login.local.azurestack.external/",
- },
- },
- expectError: true,
- expectedError: "CustomCloudConfig requires UseAzureSDK to be set to true",
- description: "Azure Stack without new SDK should fail",
- },
- {
- name: "azure_stack_without_config",
- provider: &esv1.AzureKVProvider{
- EnvironmentType: esv1.AzureEnvironmentAzureStackCloud,
- UseAzureSDK: new(true),
- },
- expectError: true,
- expectedError: "CustomCloudConfig is required when EnvironmentType is AzureStackCloud",
- description: "Azure Stack without custom config should fail",
- },
- }
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
- config, err := getCloudConfiguration(tc.provider)
- if tc.expectError {
- if err == nil {
- t.Errorf("Expected error for %s, but got none", tc.description)
- } else if tc.expectedError != "" && err.Error() != tc.expectedError {
- t.Errorf("Expected error '%s' for %s, got: %v", tc.expectedError, tc.description, err)
- }
- } else {
- if err != nil {
- t.Errorf("Expected no error for %s, but got: %v", tc.description, err)
- }
- if config.ActiveDirectoryAuthorityHost == "" && tc.provider.EnvironmentType != esv1.AzureEnvironmentAzureStackCloud {
- // For predefined clouds, we should have a valid config
- t.Errorf("Expected valid cloud configuration for %s", tc.description)
- }
- }
- })
- }
- }
|