// Copyright 2020-2021 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/latest/apis/supervisor/config/v1alpha1" ) // 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 Handles(metav1.Object) bool } const ( // SupervisorCSRFSigningKeySecretType for the Secret storing the CSRF signing key. SupervisorCSRFSigningKeySecretType corev1.SecretType = "secrets.pinniped.dev/supervisor-csrf-signing-key" // FederationDomainTokenSigningKeyType for the Secret storing the FederationDomain token signing key. FederationDomainTokenSigningKeyType corev1.SecretType = "secrets.pinniped.dev/federation-domain-token-signing-key" // FederationDomainStateSigningKeyType for the Secret storing the FederationDomain state signing key. FederationDomainStateSigningKeyType corev1.SecretType = "secrets.pinniped.dev/federation-domain-state-signing-key" // FederationDomainStateEncryptionKeyType for the Secret storing the FederationDomain state encryption key. FederationDomainStateEncryptionKeyType corev1.SecretType = "secrets.pinniped.dev/federation-domain-state-encryption-key" federationDomainKind = "FederationDomain" // 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: federationDomainKind, }), }, }, Type: s.secretType(), 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 != s.secretType() { 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 { s.updateCacheFunc(federationDomain.Spec.Issuer, 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: panic(fmt.Sprintf("unknown secret usage enum value: %d", s.secretUsage)) } return federationDomain } func (s *symmetricSecretHelper) secretType() corev1.SecretType { switch s.secretUsage { case SecretUsageTokenSigningKey: return FederationDomainTokenSigningKeyType case SecretUsageStateSigningKey: return FederationDomainStateSigningKeyType case SecretUsageStateEncryptionKey: return FederationDomainStateEncryptionKeyType default: panic(fmt.Sprintf("unknown secret usage enum value: %d", s.secretUsage)) } } func (s *symmetricSecretHelper) Handles(obj metav1.Object) bool { return IsFederationDomainSecretOfType(obj, s.secretType()) } func IsFederationDomainSecretOfType(obj metav1.Object, secretType corev1.SecretType) bool { secret, ok := obj.(*corev1.Secret) if !ok { return false } if secret.Type != secretType { return false } return isFederationDomainControllee(secret) } // isFederationDomainControllee returns whether the provided obj is controlled by an FederationDomain. func isFederationDomainControllee(obj metav1.Object) bool { controller := metav1.GetControllerOf(obj) return controller != nil && controller.APIVersion == configv1alpha1.SchemeGroupVersion.String() && controller.Kind == federationDomainKind }