// Copyright 2020 the Pinniped contributors. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 package generator import ( "fmt" "io" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" configv1alpha1 "go.pinniped.dev/generated/1.19/apis/supervisor/config/v1alpha1" "go.pinniped.dev/internal/plog" ) // SecretHelper describes an object that can Generate() a Secret and determine whether a Secret // IsValid(). It can also be Notify()'d about a Secret being persisted. // // A SecretHelper has a NamePrefix() that can be used to identify it from other SecretHelper instances. type SecretHelper interface { NamePrefix() string Generate(*configv1alpha1.FederationDomain) (*corev1.Secret, error) IsValid(*configv1alpha1.FederationDomain, *corev1.Secret) bool ObserveActiveSecretAndUpdateParentFederationDomain(*configv1alpha1.FederationDomain, *corev1.Secret) *configv1alpha1.FederationDomain } const ( // SupervisorCSRFSigningKeySecretType for the Secret storing the CSRF signing key. SupervisorCSRFSigningKeySecretType corev1.SecretType = "secrets.pinniped.dev/supervisor-csrf-signing-key" // symmetricSecretType for all corev1.Secret's generated by this helper. symmetricSecretType corev1.SecretType = "secrets.pinniped.dev/symmetric" // symmetricSecretDataKey is the corev1.Secret.Data key for the symmetric key value generated by this helper. symmetricSecretDataKey = "key" // symmetricKeySize is the default length, in bytes, of generated keys. It is set to 32 since this // seems like reasonable entropy for our keys, and a 32-byte key will allow for AES-256 // to be used in our codecs (see dynamiccodec.Codec). symmetricKeySize = 32 ) // SecretUsage describes how a cryptographic secret is going to be used. It is currently used to // indicate to a SecretHelper which status field to set on the parent FederationDomain for a Secret. type SecretUsage int const ( SecretUsageTokenSigningKey SecretUsage = iota SecretUsageStateSigningKey SecretUsageStateEncryptionKey ) // New returns a SecretHelper that has been parameterized with common symmetric secret generation // knobs. func NewSymmetricSecretHelper( namePrefix string, labels map[string]string, rand io.Reader, secretUsage SecretUsage, updateCacheFunc func(cacheKey string, cacheValue []byte), ) SecretHelper { return &symmetricSecretHelper{ namePrefix: namePrefix, labels: labels, rand: rand, secretUsage: secretUsage, updateCacheFunc: updateCacheFunc, } } type symmetricSecretHelper struct { namePrefix string labels map[string]string rand io.Reader secretUsage SecretUsage updateCacheFunc func(cacheKey string, cacheValue []byte) } func (s *symmetricSecretHelper) NamePrefix() string { return s.namePrefix } // Generate implements SecretHelper.Generate(). func (s *symmetricSecretHelper) Generate(parent *configv1alpha1.FederationDomain) (*corev1.Secret, error) { key := make([]byte, symmetricKeySize) if _, err := s.rand.Read(key); err != nil { return nil, err } return &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: fmt.Sprintf("%s%s", s.namePrefix, parent.UID), Namespace: parent.Namespace, Labels: s.labels, OwnerReferences: []metav1.OwnerReference{ *metav1.NewControllerRef(parent, schema.GroupVersionKind{ Group: configv1alpha1.SchemeGroupVersion.Group, Version: configv1alpha1.SchemeGroupVersion.Version, Kind: "FederationDomain", }), }, }, Type: symmetricSecretType, Data: map[string][]byte{ symmetricSecretDataKey: key, }, }, nil } // IsValid implements SecretHelper.IsValid(). func (s *symmetricSecretHelper) IsValid(parent *configv1alpha1.FederationDomain, secret *corev1.Secret) bool { if !metav1.IsControlledBy(secret, parent) { return false } if secret.Type != symmetricSecretType { return false } key, ok := secret.Data[symmetricSecretDataKey] if !ok { return false } if len(key) != symmetricKeySize { return false } return true } // ObserveActiveSecretAndUpdateParentFederationDomain implements SecretHelper.ObserveActiveSecretAndUpdateParentFederationDomain(). func (s *symmetricSecretHelper) ObserveActiveSecretAndUpdateParentFederationDomain( federationDomain *configv1alpha1.FederationDomain, secret *corev1.Secret, ) *configv1alpha1.FederationDomain { var cacheKey string if federationDomain != nil { cacheKey = federationDomain.Spec.Issuer } s.updateCacheFunc(cacheKey, secret.Data[symmetricSecretDataKey]) switch s.secretUsage { case SecretUsageTokenSigningKey: federationDomain.Status.Secrets.TokenSigningKey.Name = secret.Name case SecretUsageStateSigningKey: federationDomain.Status.Secrets.StateSigningKey.Name = secret.Name case SecretUsageStateEncryptionKey: federationDomain.Status.Secrets.StateEncryptionKey.Name = secret.Name default: plog.Warning("unknown secret usage enum value: %d", s.secretUsage) } return federationDomain }