internal/loader/configuration_setting_loader.go (785 lines of code) (raw):

// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. package loader import ( acpv1 "azappconfig/provider/api/v1" "context" "crypto/x509" "encoding/base64" "encoding/json" "encoding/pem" "fmt" "net/url" "os" "strconv" "strings" "sync" "github.com/Azure/azure-sdk-for-go/sdk/azcore" "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" "github.com/Azure/azure-sdk-for-go/sdk/data/azappconfig" "github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets" "golang.org/x/crypto/pkcs12" "golang.org/x/exp/maps" "golang.org/x/sync/errgroup" "golang.org/x/sync/syncmap" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/klog/v2" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/config" ) //go:generate mockgen -destination=mocks/mock_configuration_settings_retriever.go -package mocks . ConfigurationSettingsRetriever type ConfigurationSettingLoader struct { acpv1.AzureAppConfigurationProvider ClientManager ClientManager SettingsClient SettingsClient } type TargetKeyValueSettings struct { ConfigMapSettings map[string]string // Multiple secrets could be managed SecretSettings map[string]corev1.Secret K8sSecrets map[string]*TargetK8sSecretMetadata KeyValueETags map[acpv1.Selector][]*azcore.ETag FeatureFlagETags map[acpv1.Selector][]*azcore.ETag SentinelETags map[acpv1.Sentinel]*azcore.ETag } type TargetK8sSecretMetadata struct { Type corev1.SecretType SecretsKeyVaultMetadata map[string]KeyVaultSecretMetadata SecretResourceVersion string } type RawSettings struct { KeyValueSettings map[string]*string IsJsonContentTypeMap map[string]bool FeatureFlagSettings map[string]interface{} SecretSettings map[string]corev1.Secret K8sSecrets map[string]*TargetK8sSecretMetadata KeyValueETags map[acpv1.Selector][]*azcore.ETag FeatureFlagETags map[acpv1.Selector][]*azcore.ETag } type ConfigurationSettingsRetriever interface { CreateTargetSettings(ctx context.Context, resolveSecretReference SecretReferenceResolver) (*TargetKeyValueSettings, error) CheckAndRefreshSentinels(ctx context.Context, provider *acpv1.AzureAppConfigurationProvider, eTags map[acpv1.Sentinel]*azcore.ETag) (bool, map[acpv1.Sentinel]*azcore.ETag, error) CheckPageETags(ctx context.Context, eTags map[acpv1.Selector][]*azcore.ETag) (bool, error) RefreshKeyValueSettings(ctx context.Context, existingConfigMapSettings *map[string]string, resolveSecretReference SecretReferenceResolver) (*TargetKeyValueSettings, error) RefreshFeatureFlagSettings(ctx context.Context, existingConfigMapSettings *map[string]string) (*TargetKeyValueSettings, error) ResolveSecretReferences(ctx context.Context, kvReferencesToResolve map[string]*TargetK8sSecretMetadata, kvResolver SecretReferenceResolver) (*TargetKeyValueSettings, error) } type ServicePrincipleAuthenticationParameters struct { ClientId string ClientSecret string TenantId string } const ( SecretReferenceContentType string = "application/vnd.microsoft.appconfig.keyvaultref+json;charset=utf-8" FeatureFlagContentType string = "application/vnd.microsoft.appconfig.ff+json;charset=utf-8" AzureClientId string = "azure_client_id" AzureClientSecret string = "azure_client_secret" AzureTenantId string = "azure_tenant_id" AzureAppConfigurationConnectionString string = "azure_app_configuration_connection_string" FeatureFlagKeyPrefix string = ".appconfig.featureflag/" FeatureFlagSectionName string = "feature_flags" FeatureManagementSectionName string = "feature_management" PreservedSecretTypeTag string = ".kubernetes.secret.type" CertTypePem string = "application/x-pem-file" CertTypePfx string = "application/x-pkcs12" TlsKey string = "tls.key" TlsCrt string = "tls.crt" RequestTracingEnabled string = "REQUEST_TRACING_ENABLED" ) // Feature flag telemetry const ( TelemetryKey string = "telemetry" EnabledKey string = "enabled" MetadataKey string = "metadata" ETagKey string = "ETag" FeatureFlagReferenceKey string = "FeatureFlagReference" ) func NewConfigurationSettingLoader(provider acpv1.AzureAppConfigurationProvider, clientManager ClientManager, settingsClient SettingsClient) (*ConfigurationSettingLoader, error) { return &ConfigurationSettingLoader{ AzureAppConfigurationProvider: provider, ClientManager: clientManager, SettingsClient: settingsClient, }, nil } func (csl *ConfigurationSettingLoader) CreateTargetSettings(ctx context.Context, resolveSecretReference SecretReferenceResolver) (*TargetKeyValueSettings, error) { rawSettings, err := csl.CreateKeyValueSettings(ctx, resolveSecretReference) if err != nil { return nil, err } initializedSentinelETags := make(map[acpv1.Sentinel]*azcore.ETag) if csl.Spec.Configuration.Refresh != nil && csl.Spec.Configuration.Refresh.Enabled && csl.Spec.Configuration.Refresh.Monitoring != nil { sentinels := normalizeSentinels(csl.Spec.Configuration.Refresh.Monitoring.Sentinels) eTags := make(map[acpv1.Sentinel]*azcore.ETag) for _, sentinel := range sentinels { eTags[sentinel] = nil } if _, initializedSentinelETags, err = csl.CheckAndRefreshSentinels(ctx, &csl.AzureAppConfigurationProvider, eTags); err != nil { return nil, err } } if csl.Spec.FeatureFlag != nil { if rawSettings.FeatureFlagSettings, rawSettings.FeatureFlagETags, err = csl.getFeatureFlagSettings(ctx); err != nil { return nil, err } } typedSettings, err := createTypedSettings(rawSettings, csl.Spec.Target.ConfigMapData) if err != nil { return nil, err } return &TargetKeyValueSettings{ ConfigMapSettings: typedSettings, SecretSettings: rawSettings.SecretSettings, K8sSecrets: rawSettings.K8sSecrets, KeyValueETags: rawSettings.KeyValueETags, FeatureFlagETags: rawSettings.FeatureFlagETags, SentinelETags: initializedSentinelETags, }, nil } func (csl *ConfigurationSettingLoader) RefreshKeyValueSettings(ctx context.Context, existingConfigMapSetting *map[string]string, resolveSecretReference SecretReferenceResolver) (*TargetKeyValueSettings, error) { rawSettings, err := csl.CreateKeyValueSettings(ctx, resolveSecretReference) if err != nil { return nil, err } if csl.Spec.FeatureFlag != nil { rawSettings.FeatureFlagSettings, _, err = unmarshalConfigMap(existingConfigMapSetting, csl.Spec.Target.ConfigMapData) if err != nil { return nil, err } } typedSettings, err := createTypedSettings(rawSettings, csl.Spec.Target.ConfigMapData) if err != nil { return nil, err } return &TargetKeyValueSettings{ ConfigMapSettings: typedSettings, SecretSettings: rawSettings.SecretSettings, K8sSecrets: rawSettings.K8sSecrets, KeyValueETags: rawSettings.KeyValueETags, }, nil } func (csl *ConfigurationSettingLoader) RefreshFeatureFlagSettings(ctx context.Context, existingConfigMapSetting *map[string]string) (*TargetKeyValueSettings, error) { latestFeatureFlagSettings, latestFeatureFlagETags, err := csl.getFeatureFlagSettings(ctx) if err != nil { return nil, err } _, existingSettings, err := unmarshalConfigMap(existingConfigMapSetting, csl.Spec.Target.ConfigMapData) if err != nil { return nil, err } existingSettings[FeatureManagementSectionName] = latestFeatureFlagSettings typedStr, err := marshalJsonYaml(existingSettings, csl.Spec.Target.ConfigMapData) if err != nil { return nil, err } return &TargetKeyValueSettings{ ConfigMapSettings: map[string]string{ csl.Spec.Target.ConfigMapData.Key: typedStr, }, FeatureFlagETags: latestFeatureFlagETags, }, nil } func (csl *ConfigurationSettingLoader) CreateKeyValueSettings(ctx context.Context, secretReferenceResolver SecretReferenceResolver) (*RawSettings, error) { keyValueFilters := GetKeyValueFilters(csl.Spec) settingsClient := csl.SettingsClient if settingsClient == nil { settingsClient = &SelectorSettingsClient{ selectors: keyValueFilters, } } settingsResponse, err := csl.ExecuteFailoverPolicy(ctx, settingsClient) if err != nil { return nil, err } rawSettings := &RawSettings{ KeyValueSettings: make(map[string]*string), IsJsonContentTypeMap: make(map[string]bool), SecretSettings: make(map[string]corev1.Secret), K8sSecrets: make(map[string]*TargetK8sSecretMetadata), KeyValueETags: settingsResponse.Etags, } if csl.Spec.Secret != nil { rawSettings.K8sSecrets[csl.Spec.Secret.Target.SecretName] = &TargetK8sSecretMetadata{ Type: corev1.SecretTypeOpaque, SecretsKeyVaultMetadata: make(map[string]KeyVaultSecretMetadata), } } resolver := secretReferenceResolver for _, setting := range settingsResponse.Settings { trimmedKey := trimPrefix(*setting.Key, csl.Spec.Configuration.TrimKeyPrefixes) if len(trimmedKey) == 0 { klog.Warningf("key of the setting '%s' is trimmed to the empty string, just ignore it", *setting.Key) continue } if setting.ContentType == nil { rawSettings.KeyValueSettings[trimmedKey] = setting.Value rawSettings.IsJsonContentTypeMap[trimmedKey] = false continue } switch *setting.ContentType { case FeatureFlagContentType: continue // ignore feature flag while getting key value settings case SecretReferenceContentType: if setting.Value == nil { return nil, fmt.Errorf("the value of Key Vault reference '%s' is null", *setting.Key) } if csl.Spec.Secret == nil { return nil, fmt.Errorf("a Key Vault reference is found in App Configuration, but 'spec.secret' was not configured in the Azure App Configuration provider '%s' in namespace '%s'", csl.Name, csl.Namespace) } var secretType corev1.SecretType = corev1.SecretTypeOpaque var err error if secretTypeTag, ok := setting.Tags[PreservedSecretTypeTag]; ok { secretType, err = parseSecretType(secretTypeTag) if err != nil { return nil, err } } if resolver == nil { if newResolver, err := csl.createSecretReferenceResolver(ctx); err != nil { return nil, err } else { resolver = newResolver } } currentUrl := *setting.Value secretMetadata, err := parse(currentUrl) if err != nil { return nil, err } secretName := trimmedKey // If the secret type is not specified, reside it to the Secret with name specified if secretType == corev1.SecretTypeOpaque { secretName = csl.Spec.Secret.Target.SecretName } if _, ok := rawSettings.K8sSecrets[secretName]; !ok { rawSettings.K8sSecrets[secretName] = &TargetK8sSecretMetadata{ Type: secretType, SecretsKeyVaultMetadata: make(map[string]KeyVaultSecretMetadata), } } rawSettings.K8sSecrets[secretName].SecretsKeyVaultMetadata[trimmedKey] = *secretMetadata default: rawSettings.KeyValueSettings[trimmedKey] = setting.Value rawSettings.IsJsonContentTypeMap[trimmedKey] = isJsonContentType(setting.ContentType) } } // resolve the secret reference settings if resolvedSecret, err := csl.ResolveSecretReferences(ctx, rawSettings.K8sSecrets, resolver); err != nil { return nil, err } else { rawSettings.K8sSecrets = resolvedSecret.K8sSecrets err = MergeSecret(rawSettings.SecretSettings, resolvedSecret.SecretSettings) if err != nil { return nil, err } } return rawSettings, nil } func (csl *ConfigurationSettingLoader) CheckAndRefreshSentinels( ctx context.Context, provider *acpv1.AzureAppConfigurationProvider, eTags map[acpv1.Sentinel]*azcore.ETag) (bool, map[acpv1.Sentinel]*azcore.ETag, error) { sentinelChanged := false refreshedETags := make(map[acpv1.Sentinel]*azcore.ETag) for sentinel, currentETag := range eTags { refreshedETags[sentinel] = currentETag settingsClient := csl.SettingsClient if settingsClient == nil { settingsClient = &SentinelSettingsClient{ sentinel: sentinel, etag: currentETag, refreshInterval: provider.Spec.Configuration.Refresh.Interval, } } response, err := csl.ExecuteFailoverPolicy(ctx, settingsClient) if err != nil { return false, eTags, err } if response != nil && response.Settings != nil && response.Settings[0].ETag != nil { sentinelChanged = true refreshedETags[sentinel] = response.Settings[0].ETag } } return sentinelChanged, refreshedETags, nil } func (csl *ConfigurationSettingLoader) CheckPageETags(ctx context.Context, eTags map[acpv1.Selector][]*azcore.ETag) (bool, error) { settingsClient := csl.SettingsClient if settingsClient == nil { settingsClient = &EtagSettingsClient{ etags: eTags, } } settingsResponse, err := csl.ExecuteFailoverPolicy(ctx, settingsClient) if err != nil { return false, err } // when the etag is nil, it means the page eTags are not changed return settingsResponse.Etags != nil, nil } func (csl *ConfigurationSettingLoader) getFeatureFlagSettings(ctx context.Context) (map[string]interface{}, map[acpv1.Selector][]*azcore.ETag, error) { featureFlagFilters := GetFeatureFlagFilters(csl.Spec) settingsClient := csl.SettingsClient if settingsClient == nil { settingsClient = &SelectorSettingsClient{ selectors: featureFlagFilters, } } settingsResponse, err := csl.ExecuteFailoverPolicy(ctx, settingsClient) if err != nil { return nil, nil, err } settingsLength := len(settingsResponse.Settings) featureFlagExist := make(map[string]bool, settingsLength) deduplicatedFeatureFlags := make([]interface{}, 0) clientEndpoint := "" if manager, ok := csl.ClientManager.(*ConfigurationClientManager); ok { // use primary client endpoint in feature flag reference clientEndpoint = manager.StaticClientWrappers[0].Endpoint } // if settings returned like this: [{"id": "Beta"...}, {"id": "Alpha"...}, {"id": "Beta"...}], we need to deduplicate it to [{"id": "Alpha"...}, {"id": "Beta"...}], the last one wins for i := settingsLength - 1; i >= 0; i-- { key := *settingsResponse.Settings[i].Key if featureFlagExist[key] { continue } featureFlagExist[key] = true var out map[string]interface{} err := json.Unmarshal([]byte(*settingsResponse.Settings[i].Value), &out) if err != nil { return nil, nil, fmt.Errorf("failed to unmarshal feature flag settings: %s", err.Error()) } populateTelemetryMetadata(out, settingsResponse.Settings[i], clientEndpoint) deduplicatedFeatureFlags = append(deduplicatedFeatureFlags, out) } // reverse the deduplicateFeatureFlags to keep the order for i, j := 0, len(deduplicatedFeatureFlags)-1; i < j; i, j = i+1, j-1 { deduplicatedFeatureFlags[i], deduplicatedFeatureFlags[j] = deduplicatedFeatureFlags[j], deduplicatedFeatureFlags[i] } // featureFlagSection = {"feature_flags": [{...}, {...}]} var featureFlagSection = map[string]interface{}{ FeatureFlagSectionName: deduplicatedFeatureFlags, } return featureFlagSection, settingsResponse.Etags, nil } func (csl *ConfigurationSettingLoader) ResolveSecretReferences( ctx context.Context, secretReferencesToResolve map[string]*TargetK8sSecretMetadata, resolver SecretReferenceResolver) (*TargetKeyValueSettings, error) { if resolver == nil { if kvResolver, err := csl.createSecretReferenceResolver(ctx); err != nil { return nil, err } else { resolver = kvResolver } } resolvedSecrets := make(map[string]corev1.Secret) for name, targetSecretReference := range secretReferencesToResolve { resolvedSecrets[name] = corev1.Secret{ Data: make(map[string][]byte), Type: targetSecretReference.Type, } var eg errgroup.Group if targetSecretReference.Type == corev1.SecretTypeOpaque { if len(targetSecretReference.SecretsKeyVaultMetadata) > 0 { lock := &sync.Mutex{} for key, kvReference := range targetSecretReference.SecretsKeyVaultMetadata { currentKey := key currentReference := kvReference eg.Go(func() error { resolvedSecret, err := resolver.Resolve(currentReference, ctx) if err != nil { return fmt.Errorf("fail to resolve the Key Vault reference type setting '%s': %s", currentKey, err.Error()) } lock.Lock() defer lock.Unlock() resolvedSecrets[name].Data[currentKey] = []byte(*resolvedSecret.Value) return nil }) } if err := eg.Wait(); err != nil { return nil, err } } } else if targetSecretReference.Type == corev1.SecretTypeTLS { eg.Go(func() error { resolvedSecret, err := resolver.Resolve(targetSecretReference.SecretsKeyVaultMetadata[name], ctx) if err != nil { return fmt.Errorf("fail to resolve the Key Vault reference type setting '%s': %s", name, err.Error()) } if resolvedSecret.ContentType == nil { return fmt.Errorf("unspecified content type") } switch *resolvedSecret.ContentType { case CertTypePfx: resolvedSecrets[name].Data[TlsKey], resolvedSecrets[name].Data[TlsCrt], err = decodePkcs12(*resolvedSecret.Value) case CertTypePem: resolvedSecrets[name].Data[TlsKey], resolvedSecrets[name].Data[TlsCrt], err = decodePem(*resolvedSecret.Value) default: err = fmt.Errorf("unknown content type '%s'", *resolvedSecret.ContentType) } if err != nil { return fmt.Errorf("fail to decode the cert '%s': %s", name, err.Error()) } return nil }) } // All other types are not supported if err := eg.Wait(); err != nil { return nil, err } } return &TargetKeyValueSettings{ SecretSettings: resolvedSecrets, K8sSecrets: secretReferencesToResolve, }, nil } func (csl *ConfigurationSettingLoader) createSecretReferenceResolver(ctx context.Context) (SecretReferenceResolver, error) { var defaultAuth *acpv1.AzureAppConfigurationProviderAuth = nil if csl.Spec.Secret != nil && csl.Spec.Secret.Auth != nil { defaultAuth = csl.Spec.Secret.Auth.AzureAppConfigurationProviderAuth } defaultCred, err := CreateTokenCredential(ctx, defaultAuth, csl.Namespace) if err != nil { return nil, err } secretClients, err := createSecretClients(ctx, csl.AzureAppConfigurationProvider) if err != nil { return nil, err } resolver := &KeyVaultConnector{ DefaultTokenCredential: defaultCred, Clients: secretClients, } return resolver, nil } func (csl *ConfigurationSettingLoader) ExecuteFailoverPolicy(ctx context.Context, settingsClient SettingsClient) (*SettingsResponse, error) { clients, err := csl.ClientManager.GetClients(ctx) if err != nil { return nil, err } if len(clients) == 0 { csl.ClientManager.RefreshClients(ctx) return nil, fmt.Errorf("no client is available to connect to the target App Configuration store") } manager, ok := csl.ClientManager.(*ConfigurationClientManager) if csl.AzureAppConfigurationProvider.Spec.LoadBalancingEnabled && ok && manager.lastSuccessfulEndpoint != "" && len(clients) > 1 { nextClientIndex := 0 for _, clientWrapper := range clients { nextClientIndex++ if clientWrapper.Endpoint == manager.lastSuccessfulEndpoint { break } } // If we found the last successful client,we'll rotate the list so that the next client is at the beginning if nextClientIndex < len(clients) { rotate(clients, nextClientIndex) } } errors := make([]error, 0) var tracingEnabled, isFailoverRequest bool if value, ok := os.LookupEnv(RequestTracingEnabled); ok { tracingEnabled, _ = strconv.ParseBool(value) } for _, clientWrapper := range clients { if tracingEnabled { ctx = policy.WithHTTPHeader(ctx, createCorrelationContextHeader(ctx, csl.AzureAppConfigurationProvider, csl.ClientManager, isFailoverRequest)) } settingsResponse, err := settingsClient.GetSettings(ctx, clientWrapper.Client) successful := true if err != nil { successful = false updateClientBackoffStatus(clientWrapper, successful) if IsFailoverable(err) { klog.Warningf("current client of '%s' failed to get settings: %s", clientWrapper.Endpoint, err.Error()) errors = append(errors, err) isFailoverRequest = true continue } return nil, err } if manager, ok := csl.ClientManager.(*ConfigurationClientManager); ok { manager.lastSuccessfulEndpoint = clientWrapper.Endpoint } updateClientBackoffStatus(clientWrapper, successful) return settingsResponse, nil } // Failed to execute failover policy csl.ClientManager.RefreshClients(ctx) return nil, fmt.Errorf("all app configuration clients failed to get settings: %v", errors) } func updateClientBackoffStatus(clientWrapper *ConfigurationClientWrapper, successful bool) { if successful { clientWrapper.BackOffEndTime = metav1.Time{} // Reset FailedAttempts when client succeeded if clientWrapper.FailedAttempts > 0 { clientWrapper.FailedAttempts = 0 } // Use negative value to indicate that successful attempt clientWrapper.FailedAttempts-- } else { //Reset FailedAttempts when client failed if clientWrapper.FailedAttempts < 0 { clientWrapper.FailedAttempts = 0 } clientWrapper.FailedAttempts++ clientWrapper.BackOffEndTime = metav1.Time{Time: metav1.Now().Add(calculateBackoffDuration(clientWrapper.FailedAttempts))} } } func trimPrefix(key string, prefixToTrim []string) string { if len(prefixToTrim) > 0 { for _, v := range prefixToTrim { if strings.HasPrefix(key, v) { return strings.TrimPrefix(key, v) } } } return key } func getConfigMap(ctx context.Context, namespacedConfigMapName types.NamespacedName) (*corev1.ConfigMap, error) { cfg, err := config.GetConfig() if err != nil { return nil, err } client, err := client.New(cfg, client.Options{}) if err != nil { return nil, err } configMapObject := &corev1.ConfigMap{} err = client.Get(ctx, namespacedConfigMapName, configMapObject) if err != nil { return nil, err } return configMapObject, nil } func GetSecret(ctx context.Context, namespacedSecretName types.NamespacedName) (*corev1.Secret, error) { cfg, err := config.GetConfig() if err != nil { return nil, err } client, err := client.New(cfg, client.Options{}) if err != nil { return nil, err } secretObject := &corev1.Secret{} err = client.Get(ctx, namespacedSecretName, secretObject) if err != nil { return nil, err } return secretObject, nil } func GetKeyValueFilters(acpSpec acpv1.AzureAppConfigurationProviderSpec) []acpv1.Selector { return deduplicateFilters(normalizeLabelFilter(acpSpec.Configuration.Selectors)) } func GetFeatureFlagFilters(acpSpec acpv1.AzureAppConfigurationProviderSpec) []acpv1.Selector { featureFlagFilters := make([]acpv1.Selector, 0) if acpSpec.FeatureFlag != nil { featureFlagFilters = deduplicateFilters(normalizeLabelFilter(acpSpec.FeatureFlag.Selectors)) for i := 0; i < len(featureFlagFilters); i++ { if featureFlagFilters[i].KeyFilter != nil { prefixedFeatureFlagFilter := FeatureFlagKeyPrefix + *featureFlagFilters[i].KeyFilter featureFlagFilters[i].KeyFilter = &prefixedFeatureFlagFilter } } } return featureFlagFilters } func normalizeSentinels(sentinels []acpv1.Sentinel) []acpv1.Sentinel { var results []acpv1.Sentinel nullString := "\x00" for _, sentinel := range sentinels { label := sentinel.Label if sentinel.Label == nil || len(*sentinel.Label) == 0 { label = &nullString } results = append(results, acpv1.Sentinel{ Key: sentinel.Key, Label: label, }) } return results } func normalizeLabelFilter(filters []acpv1.Selector) []acpv1.Selector { var result []acpv1.Selector nullString := "\x00" for i := 0; i < len(filters); i++ { labelFilter := filters[i].LabelFilter if filters[i].LabelFilter == nil || len(*filters[i].LabelFilter) == 0 { labelFilter = &nullString } result = append(result, acpv1.Selector{ KeyFilter: filters[i].KeyFilter, LabelFilter: labelFilter, SnapshotName: filters[i].SnapshotName, }) } return result } func deduplicateFilters(filters []acpv1.Selector) []acpv1.Selector { var result []acpv1.Selector findDuplicate := false if len(filters) > 0 { // // Deduplicate the filters in a way that in honor of what user tell us // If user populate the selectors with `{KeyFilter: "one*", LabelFilter: "prod"}, {KeyFilter: "two*", LabelFilter: "dev"}, {KeyFilter: "one*", LabelFilter: "prod"}` // We deduplicate it into `{KeyFilter: "two*", LabelFilter: "dev"}, {KeyFilter: "one*", LabelFilter: "prod"}` // not `{KeyFilter: "one*", LabelFilter: "prod"}, {KeyFilter: "two*", LabelFilter: "dev"}` for i := len(filters) - 1; i >= 0; i-- { findDuplicate = false for j := 0; j < len(result); j++ { if compare(result[j].KeyFilter, filters[i].KeyFilter) && compare(result[j].LabelFilter, filters[i].LabelFilter) && compare(result[j].SnapshotName, filters[i].SnapshotName) { findDuplicate = true break } } if !findDuplicate { result = append(result, filters[i]) } } reverse(result) } else { wildcard := "*" result = append(result, acpv1.Selector{ KeyFilter: &wildcard, LabelFilter: nil, }) } return result } func compare(a *string, b *string) bool { if a == b { return true } if a == nil || b == nil { return false } return strings.Compare(*a, *b) == 0 } func reverse(arr []acpv1.Selector) { for i, j := 0, len(arr)-1; i < j; i, j = i+1, j-1 { arr[i], arr[j] = arr[j], arr[i] } } func createSecretClients( ctx context.Context, acp acpv1.AzureAppConfigurationProvider) (*syncmap.Map, error) { secretClients := &syncmap.Map{} if acp.Spec.Secret == nil || acp.Spec.Secret.Auth == nil { return secretClients, nil } for _, keyVault := range acp.Spec.Secret.Auth.KeyVaults { url, _ := url.Parse(keyVault.Uri) tokenCredential, err := CreateTokenCredential(ctx, keyVault.AzureAppConfigurationProviderAuth, acp.Namespace) if err != nil { klog.ErrorS(err, fmt.Sprintf("Fail to create token credential for %q", keyVault.Uri)) return nil, err } hostName := strings.ToLower(url.Host) newSecretClient, err := azsecrets.NewClient("https://"+hostName, tokenCredential, nil) if err != nil { klog.ErrorS(err, fmt.Sprintf("Fail to create key vault secret client for %q", keyVault.Uri)) return nil, err } secretClients.Store(hostName, newSecretClient) } return secretClients, nil } func parseSecretType(secretType string) (corev1.SecretType, error) { secretTypeMap := map[string]corev1.SecretType{ "opaque": corev1.SecretTypeOpaque, "kubernetes.io/tls": corev1.SecretTypeTLS, } if parsedType, ok := secretTypeMap[secretType]; ok { if parsedType != corev1.SecretTypeTLS { return "", fmt.Errorf("secret type %q is not supported", secretType) } else { return parsedType, nil } } else { return "", fmt.Errorf("secret type %q is not supported", secretType) } } func decodePkcs12(value string) (key []byte, crt []byte, err error) { pfxRaw, err := base64.StdEncoding.DecodeString(value) if err != nil { return nil, nil, err } // using ToPEM to extract more than one certificate and key in pfxData pemBlock, err := pkcs12.ToPEM(pfxRaw, "") if err != nil { return nil, nil, err } return parsePemBlock(pemBlock) } func decodePem(value string) (key []byte, crt []byte, err error) { pemBlocks := []*pem.Block{} for pemBlock, rest := pem.Decode([]byte(value)); pemBlock != nil; pemBlock, rest = pem.Decode(rest) { pemBlocks = append(pemBlocks, pemBlock) } if len(pemBlocks) == 0 { return nil, nil, fmt.Errorf("failed to decode pem block") } return parsePemBlock(pemBlocks) } func parsePemBlock(pemBlock []*pem.Block) ([]byte, []byte, error) { // PEM block encoded form contains the headers // -----BEGIN Type----- // Headers // base64-encoded Bytes // -----END Type----- // Setting headers to nil to ensure no headers included in the encoded block var pemKeyData, pemCertData []byte for _, block := range pemBlock { block.Headers = make(map[string]string) if block.Type == "CERTIFICATE" { pemCertData = append(pemCertData, pem.EncodeToMemory(block)...) } else { key, err := parsePrivateKey(block.Bytes) if err != nil { return nil, nil, err } // pkcs1 RSA private key PEM file is specific for RSA keys. RSA is not used exclusively inside X509 // and SSL/TLS, a more generic key format is available in the form of PKCS#8 that identifies the type // of private key and contains the relevant data. // Converting to pkcs8 private key as ToPEM uses pkcs1 // The driver determines the key type from the pkcs8 form of the key and marshals appropriately block.Bytes, err = x509.MarshalPKCS8PrivateKey(key) if err != nil { return nil, nil, err } pemKeyData = append(pemKeyData, pem.EncodeToMemory(block)...) } } return pemKeyData, pemCertData, nil } func parsePrivateKey(block []byte) (interface{}, error) { if key, err := x509.ParsePKCS1PrivateKey(block); err == nil { return key, nil } if key, err := x509.ParsePKCS8PrivateKey(block); err == nil { return key, nil } if key, err := x509.ParseECPrivateKey(block); err == nil { return key, nil } return nil, fmt.Errorf("failed to parse key for type pkcs1, pkcs8 or ec") } func MergeSecret(secret map[string]corev1.Secret, newSecret map[string]corev1.Secret) error { for k, v := range newSecret { if _, ok := secret[k]; !ok { secret[k] = v } else if secret[k].Type != v.Type { return fmt.Errorf("secret type mismatch for key %q", k) } else { maps.Copy(secret[k].Data, v.Data) } } return nil } // rotates the slice to the left by k positions func rotate(clients []*ConfigurationClientWrapper, k int) { n := len(clients) k = k % n if k == 0 { return } // Reverse the entire slice reverseClients(clients, 0, n-1) // Reverse the first part reverseClients(clients, 0, n-k-1) // Reverse the second part reverseClients(clients, n-k, n-1) } func reverseClients(clients []*ConfigurationClientWrapper, start, end int) { for start < end { clients[start], clients[end] = clients[end], clients[start] start++ end-- } } func generateFeatureFlagReference(setting azappconfig.Setting, endpoint string) string { featureFlagReference := fmt.Sprintf("%s/kv/%s", endpoint, *setting.Key) // Check if the label is present and not empty if setting.Label != nil && strings.TrimSpace(*setting.Label) != "" { featureFlagReference += fmt.Sprintf("?label=%s", *setting.Label) } return featureFlagReference } func populateTelemetryMetadata(featureFlag map[string]interface{}, setting azappconfig.Setting, endpoint string) { if telemetry, ok := featureFlag[TelemetryKey].(map[string]interface{}); ok { if enabled, ok := telemetry[EnabledKey].(bool); ok && enabled { metadata, _ := telemetry[MetadataKey].(map[string]interface{}) if metadata == nil { metadata = make(map[string]interface{}) } // Set the new metadata metadata[ETagKey] = *setting.ETag metadata[FeatureFlagReferenceKey] = generateFeatureFlagReference(setting, endpoint) telemetry[MetadataKey] = metadata } } }