Skip to content

Commit

Permalink
Update secrets interface to work for CEL export (#4383)
Browse files Browse the repository at this point in the history
* Interface is now ExportKubernetesSecrets.
* Now accepts additionalSecrets.
* Now returns KubernetesSecretExportResult including RawSecrets
  map[string]string, which can be passed to CEL.
* Also a new ExportConfigMaps interface for ConfigMaps.
  • Loading branch information
matthchr authored Oct 30, 2024
1 parent 6e7915f commit 0572b94
Show file tree
Hide file tree
Showing 99 changed files with 2,246 additions and 593 deletions.
50 changes: 35 additions & 15 deletions v2/api/apimanagement/customizations/subscription_extensions.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,25 +12,31 @@ import (
"github.com/go-logr/logr"
"github.com/pkg/errors"
v1 "k8s.io/api/core/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/conversion"

apimanagement "github.com/Azure/azure-service-operator/v2/api/apimanagement/v1api20220801/storage"
"github.com/Azure/azure-service-operator/v2/internal/genericarmclient"
. "github.com/Azure/azure-service-operator/v2/internal/logging"
"github.com/Azure/azure-service-operator/v2/internal/set"
"github.com/Azure/azure-service-operator/v2/internal/util/to"
"github.com/Azure/azure-service-operator/v2/pkg/genruntime"
"github.com/Azure/azure-service-operator/v2/pkg/genruntime/secrets"
)

var _ genruntime.KubernetesExporter = &SubscriptionExtension{}
const (
primaryKey = "primaryKey"
secondaryKey = "secondaryKey"
)

var _ genruntime.KubernetesSecretExporter = &SubscriptionExtension{}

func (ext *SubscriptionExtension) ExportKubernetesResources(
func (ext *SubscriptionExtension) ExportKubernetesSecrets(
ctx context.Context,
obj genruntime.MetaObject,
additionalSecrets set.Set[string],
armClient *genericarmclient.GenericClient,
log logr.Logger,
) ([]client.Object, error) {
) (*genruntime.KubernetesSecretExportResult, error) {
// This has to be the current hub storage version. It will need to be updated
// if the hub storage version changes.
typedObj, ok := obj.(*apimanagement.Subscription)
Expand All @@ -42,8 +48,9 @@ func (ext *SubscriptionExtension) ExportKubernetesResources(
// the hub type has been changed but this extension has not
var _ conversion.Hub = typedObj

hasSecrets := secretsSpecified(typedObj)
if !hasSecrets {
primarySecrets := secretsSpecified(typedObj)
requestedSecrets := set.Union(primarySecrets, additionalSecrets)
if len(requestedSecrets) == 0 {
log.V(Debug).Info("No secrets retrieval to perform as operatorSpec is empty")
return nil, nil
}
Expand All @@ -60,7 +67,7 @@ func (ext *SubscriptionExtension) ExportKubernetesResources(

// Only bother calling ListSecrets if there are secrets to retrieve
var s armapimanagement.SubscriptionKeysContract
if hasSecrets {
if len(requestedSecrets) > 0 {
subscription := id.SubscriptionID
// Using armClient.ClientOptions() here ensures we share the same HTTP connection, so this is not opening a new
// connection each time through
Expand All @@ -79,32 +86,45 @@ func (ext *SubscriptionExtension) ExportKubernetesResources(
s = resp.SubscriptionKeysContract
}

resolvedSecrets := map[string]string{}
if to.Value(s.PrimaryKey) != "" {
resolvedSecrets[primaryKey] = to.Value(s.PrimaryKey)
}
if to.Value(s.SecondaryKey) != "" {
resolvedSecrets[secondaryKey] = to.Value(s.SecondaryKey)
}
secretSlice, err := secretsToWrite(typedObj, s)
if err != nil {
return nil, err
}

return secrets.SliceToClientObjectSlice(secretSlice), nil
return &genruntime.KubernetesSecretExportResult{
Objs: secrets.SliceToClientObjectSlice(secretSlice),
RawSecrets: secrets.SelectSecrets(additionalSecrets, resolvedSecrets),
}, nil
}

func secretsSpecified(obj *apimanagement.Subscription) bool {
func secretsSpecified(obj *apimanagement.Subscription) set.Set[string] {
if obj.Spec.OperatorSpec == nil || obj.Spec.OperatorSpec.Secrets == nil {
return false
return nil
}

hasSecrets := false
secrets := obj.Spec.OperatorSpec.Secrets
if secrets.PrimaryKey != nil || secrets.SecondaryKey != nil {
hasSecrets = true
result := make(set.Set[string])
if secrets.PrimaryKey != nil {
result.Add(primaryKey)
}
if secrets.SecondaryKey != nil {
result.Add(secondaryKey)
}

return hasSecrets
return result
}

func secretsToWrite(obj *apimanagement.Subscription, s armapimanagement.SubscriptionKeysContract) ([]*v1.Secret, error) {
operatorSpecSecrets := obj.Spec.OperatorSpec.Secrets
if operatorSpecSecrets == nil {
return nil, errors.Errorf("unexpected nil operatorspec")
return nil, nil
}

collector := secrets.NewCollector(obj.Namespace)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright (c) Microsoft Corporation.
* Licensed under the MIT license.
*/

package customizations

import (
"reflect"
"testing"

. "github.com/onsi/gomega"

apimanagement "github.com/Azure/azure-service-operator/v2/api/apimanagement/v1api20220801/storage"
"github.com/Azure/azure-service-operator/v2/internal/reflecthelpers"
testreflect "github.com/Azure/azure-service-operator/v2/internal/testcommon/reflect"
)

func Test_SecretsSpecified_AllSecretsSpecifiedAllSecretsReturned(t *testing.T) {
t.Parallel()
g := NewGomegaWithT(t)

secrets := &apimanagement.SubscriptionOperatorSecrets{}
testreflect.PopulateStruct(secrets)

obj := &apimanagement.Subscription{
Spec: apimanagement.Subscription_Spec{
OperatorSpec: &apimanagement.SubscriptionOperatorSpec{
Secrets: secrets,
},
},
}
secretNames := secretsSpecified(obj)
expectedTags := reflecthelpers.GetJSONTags(reflect.TypeOf(apimanagement.SubscriptionOperatorSecrets{}))
expectedTags.Remove("$propertyBag")

g.Expect(expectedTags).To(Equal(secretNames))
}
167 changes: 120 additions & 47 deletions v2/api/appconfiguration/customizations/configuration_store_extensions.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,24 +13,41 @@ import (
"github.com/go-logr/logr"
"github.com/pkg/errors"
v1 "k8s.io/api/core/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/conversion"

storage "github.com/Azure/azure-service-operator/v2/api/appconfiguration/v1api20220501/storage"
"github.com/Azure/azure-service-operator/v2/internal/genericarmclient"
. "github.com/Azure/azure-service-operator/v2/internal/logging"
"github.com/Azure/azure-service-operator/v2/internal/set"
"github.com/Azure/azure-service-operator/v2/internal/util/to"
"github.com/Azure/azure-service-operator/v2/pkg/genruntime"
"github.com/Azure/azure-service-operator/v2/pkg/genruntime/secrets"
)

var _ genruntime.KubernetesExporter = &ConfigurationStoreExtension{}
const (
primaryKeyID = "primaryKeyID"
secondaryKeyID = "secondaryKeyID"
primaryReadOnlyKeyID = "primaryReadOnlyKeyID"
secondaryReadOnlyKeyID = "secondaryReadOnlyKeyID"
primaryKey = "primaryKey"
secondaryKey = "secondaryKey"
primaryReadOnlyKey = "primaryReadOnlyKey"
secondaryReadOnlyKey = "secondaryReadOnlyKey"
primaryConnectionString = "primaryConnectionString"
secondaryConnectionString = "secondaryConnectionString"
primaryReadOnlyConnectionString = "primaryReadOnlyConnectionString"
secondaryReadOnlyConnectionString = "secondaryReadOnlyConnectionString"
)

var _ genruntime.KubernetesSecretExporter = &ConfigurationStoreExtension{}

func (ext *ConfigurationStoreExtension) ExportKubernetesResources(
func (ext *ConfigurationStoreExtension) ExportKubernetesSecrets(
ctx context.Context,
obj genruntime.MetaObject,
additionalSecrets set.Set[string],
armClient *genericarmclient.GenericClient,
log logr.Logger,
) ([]client.Object, error) {
) (*genruntime.KubernetesSecretExportResult, error) {
// This has to be the current hub storage version. It will need to be updated
// if the hub storage version changes.
typedObj, ok := obj.(*storage.ConfigurationStore)
Expand All @@ -42,8 +59,9 @@ func (ext *ConfigurationStoreExtension) ExportKubernetesResources(
// the hub type has been changed but this extension has not
var _ conversion.Hub = typedObj

hasSecrets := secretsSpecified(typedObj)
if !hasSecrets {
primarySecrets := secretsSpecified(typedObj)
requestedSecrets := set.Union(primarySecrets, additionalSecrets)
if len(requestedSecrets) == 0 {
log.V(Debug).Info("No secrets retrieval to perform as operatorSpec is empty")
return nil, nil
}
Expand All @@ -55,7 +73,7 @@ func (ext *ConfigurationStoreExtension) ExportKubernetesResources(

keys := make(map[string]armappconfiguration.APIKey)
// Only bother calling ListKeys if there are secrets to retrieve
if hasSecrets {
if len(requestedSecrets) > 0 {
subscription := id.SubscriptionID
// Using armClient.ClientOptions() here ensures we share the same HTTP connection, so this is not opening a new
// connection each time through
Expand All @@ -70,49 +88,71 @@ func (ext *ConfigurationStoreExtension) ExportKubernetesResources(
pager = confClient.NewListKeysPager(id.ResourceGroupName, typedObj.AzureName(), nil)
for pager.More() {
resp, err = pager.NextPage(ctx)
if err != nil {
return nil, errors.Wrapf(err, "failed to retreive response")
}
addSecretsToMap(resp.Value, keys)

}
if err != nil {
return nil, errors.Wrapf(err, "failed to retreive response")
}
if err != nil {
return nil, errors.Wrapf(err, "failed listing keys")
}

}

secretSlice, err := secretsToWrite(typedObj, keys)
if err != nil {
return nil, err
}

return secrets.SliceToClientObjectSlice(secretSlice), nil
resolvedSecrets := makeResolvedSecretsMap(keys)

return &genruntime.KubernetesSecretExportResult{
Objs: secrets.SliceToClientObjectSlice(secretSlice),
RawSecrets: secrets.SelectSecrets(additionalSecrets, resolvedSecrets),
}, nil
}

func secretsSpecified(obj *storage.ConfigurationStore) bool {
func secretsSpecified(obj *storage.ConfigurationStore) set.Set[string] {
if obj.Spec.OperatorSpec == nil || obj.Spec.OperatorSpec.Secrets == nil {
return false
return nil
}

secrets := obj.Spec.OperatorSpec.Secrets

if secrets.PrimaryKeyID != nil ||
secrets.SecondaryKeyID != nil ||
secrets.PrimaryReadOnlyKeyID != nil ||
secrets.SecondaryReadOnlyKeyID != nil ||
secrets.PrimaryKey != nil ||
secrets.SecondaryKey != nil ||
secrets.PrimaryReadOnlyKey != nil ||
secrets.SecondaryReadOnlyKey != nil ||
secrets.PrimaryConnectionString != nil ||
secrets.SecondaryConnectionString != nil ||
secrets.PrimaryReadOnlyConnectionString != nil ||
secrets.SecondaryReadOnlyConnectionString != nil {
return true
}

return false
result := make(set.Set[string])
if secrets.PrimaryKeyID != nil {
result.Add(primaryKeyID)
}
if secrets.SecondaryKeyID != nil {
result.Add(secondaryKeyID)
}
if secrets.PrimaryReadOnlyKeyID != nil {
result.Add(primaryReadOnlyKeyID)
}
if secrets.SecondaryReadOnlyKeyID != nil {
result.Add(secondaryReadOnlyKeyID)
}
if secrets.PrimaryKey != nil {
result.Add(primaryKey)
}
if secrets.SecondaryKey != nil {
result.Add(secondaryKey)
}
if secrets.PrimaryReadOnlyKey != nil {
result.Add(primaryReadOnlyKey)
}
if secrets.SecondaryReadOnlyKey != nil {
result.Add(secondaryReadOnlyKey)
}
if secrets.PrimaryConnectionString != nil {
result.Add(primaryConnectionString)
}
if secrets.SecondaryConnectionString != nil {
result.Add(secondaryConnectionString)
}
if secrets.PrimaryReadOnlyConnectionString != nil {
result.Add(primaryReadOnlyConnectionString)
}
if secrets.SecondaryReadOnlyConnectionString != nil {
result.Add(secondaryReadOnlyConnectionString)
}
return result
}

func addSecretsToMap(keys []*armappconfiguration.APIKey, result map[string]armappconfiguration.APIKey) {
Expand All @@ -127,37 +167,70 @@ func addSecretsToMap(keys []*armappconfiguration.APIKey, result map[string]armap
func secretsToWrite(obj *storage.ConfigurationStore, keys map[string]armappconfiguration.APIKey) ([]*v1.Secret, error) {
operatorSpecSecrets := obj.Spec.OperatorSpec.Secrets
if operatorSpecSecrets == nil {
return nil, errors.Errorf("unexpected nil operatorspec")
return nil, nil
}

collector := secrets.NewCollector(obj.Namespace)
primary, ok := keys["Primary"]
if ok {
collector.AddValue(operatorSpecSecrets.PrimaryConnectionString, *primary.ConnectionString)
collector.AddValue(operatorSpecSecrets.PrimaryKeyID, *primary.ID)
collector.AddValue(operatorSpecSecrets.PrimaryKey, *primary.Value)
collector.AddValue(operatorSpecSecrets.PrimaryConnectionString, to.Value(primary.ConnectionString))
collector.AddValue(operatorSpecSecrets.PrimaryKeyID, to.Value(primary.ID))
collector.AddValue(operatorSpecSecrets.PrimaryKey, to.Value(primary.Value))
}

primaryReadOnly, ok := keys["Primary Read Only"]
if ok {
collector.AddValue(operatorSpecSecrets.PrimaryReadOnlyConnectionString, *primaryReadOnly.ConnectionString)
collector.AddValue(operatorSpecSecrets.PrimaryReadOnlyKeyID, *primaryReadOnly.ID)
collector.AddValue(operatorSpecSecrets.PrimaryReadOnlyKey, *primaryReadOnly.Value)
collector.AddValue(operatorSpecSecrets.PrimaryReadOnlyConnectionString, to.Value(primaryReadOnly.ConnectionString))
collector.AddValue(operatorSpecSecrets.PrimaryReadOnlyKeyID, to.Value(primaryReadOnly.ID))
collector.AddValue(operatorSpecSecrets.PrimaryReadOnlyKey, to.Value(primaryReadOnly.Value))
}

secondary, ok := keys["Secondary"]
if ok {
collector.AddValue(operatorSpecSecrets.SecondaryConnectionString, *secondary.ConnectionString)
collector.AddValue(operatorSpecSecrets.SecondaryKeyID, *secondary.ID)
collector.AddValue(operatorSpecSecrets.SecondaryKey, *secondary.Value)
collector.AddValue(operatorSpecSecrets.SecondaryConnectionString, to.Value(secondary.ConnectionString))
collector.AddValue(operatorSpecSecrets.SecondaryKeyID, to.Value(secondary.ID))
collector.AddValue(operatorSpecSecrets.SecondaryKey, to.Value(secondary.Value))
}

secondaryReadOnly, ok := keys["Secondary Read Only"]
if ok {
collector.AddValue(operatorSpecSecrets.SecondaryReadOnlyConnectionString, *secondaryReadOnly.ConnectionString)
collector.AddValue(operatorSpecSecrets.SecondaryReadOnlyKeyID, *secondaryReadOnly.ID)
collector.AddValue(operatorSpecSecrets.SecondaryReadOnlyKey, *secondaryReadOnly.Value)
collector.AddValue(operatorSpecSecrets.SecondaryReadOnlyConnectionString, to.Value(secondaryReadOnly.ConnectionString))
collector.AddValue(operatorSpecSecrets.SecondaryReadOnlyKeyID, to.Value(secondaryReadOnly.ID))
collector.AddValue(operatorSpecSecrets.SecondaryReadOnlyKey, to.Value(secondaryReadOnly.Value))
}

return collector.Values()
}

func makeResolvedSecretsMap(keys map[string]armappconfiguration.APIKey) map[string]string {
result := make(map[string]string)
primary, ok := keys["Primary"]
if ok {
result[primaryConnectionString] = to.Value(primary.ConnectionString)
result[primaryKeyID] = to.Value(primary.ID)
result[primaryKey] = to.Value(primary.Value)
}

primaryReadOnly, ok := keys["Primary Read Only"]
if ok {
result[primaryReadOnlyConnectionString] = to.Value(primaryReadOnly.ConnectionString)
result[primaryReadOnlyKeyID] = to.Value(primaryReadOnly.ID)
result[primaryReadOnlyKey] = to.Value(primaryReadOnly.Value)
}

secondary, ok := keys["Secondary"]
if ok {
result[secondaryConnectionString] = to.Value(secondary.ConnectionString)
result[secondaryKeyID] = to.Value(secondary.ID)
result[secondaryKey] = to.Value(secondary.Value)
}

secondaryReadOnly, ok := keys["Secondary Read Only"]
if ok {
result[secondaryReadOnlyConnectionString] = to.Value(secondaryReadOnly.ConnectionString)
result[secondaryReadOnlyKeyID] = to.Value(secondaryReadOnly.ID)
result[secondaryReadOnlyKey] = to.Value(secondaryReadOnly.Value)
}

return result
}
Loading

0 comments on commit 0572b94

Please sign in to comment.