ContainerImage.Pinniped/internal/controller/supervisorconfig/generator/secret_helper.go
Andrew Keesler 82ae98d9d0
Set secret names on OIDCProvider status field
We believe this API is more forwards compatible with future secrets management
use cases. The implementation is a cry for help, but I was trying to follow the
previously established pattern of encapsulating the secret generation
functionality to a single group of packages.

This commit makes a breaking change to the current OIDCProvider API, but that
OIDCProvider API was added after the latest release, so it is technically still
in development until we release, and therefore we can continue to thrash on it.

I also took this opportunity to make some things private that didn't need to be
public.

Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-12-15 09:13:01 -05:00

152 lines
4.6 KiB
Go

// 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.OIDCProvider) (*corev1.Secret, error)
IsValid(*configv1alpha1.OIDCProvider, *corev1.Secret) bool
ObserveActiveSecretAndUpdateParentOIDCProvider(*configv1alpha1.OIDCProvider, *corev1.Secret) *configv1alpha1.OIDCProvider
}
const (
// symmetricSecretType is corev1.Secret.Type of all corev1.Secret's generated by this helper.
symmetricSecretType = "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 OIDCProvider 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.OIDCProvider) (*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: "OIDCProvider",
}),
},
},
Type: symmetricSecretType,
Data: map[string][]byte{
symmetricSecretDataKey: key,
},
}, nil
}
// IsValid implements SecretHelper.IsValid().
func (s *symmetricSecretHelper) IsValid(parent *configv1alpha1.OIDCProvider, 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
}
// ObserveActiveSecretAndUpdateParentOIDCProvider implements SecretHelper.ObserveActiveSecretAndUpdateParentOIDCProvider().
func (s *symmetricSecretHelper) ObserveActiveSecretAndUpdateParentOIDCProvider(
op *configv1alpha1.OIDCProvider,
secret *corev1.Secret,
) *configv1alpha1.OIDCProvider {
var cacheKey string
if op != nil {
cacheKey = op.Spec.Issuer
}
s.updateCacheFunc(cacheKey, secret.Data[symmetricSecretDataKey])
switch s.secretUsage {
case SecretUsageTokenSigningKey:
op.Status.Secrets.TokenSigningKey.Name = secret.Name
case SecretUsageStateSigningKey:
op.Status.Secrets.StateSigningKey.Name = secret.Name
case SecretUsageStateEncryptionKey:
op.Status.Secrets.StateEncryptionKey.Name = secret.Name
default:
plog.Warning("unknown secret usage enum value: %d", s.secretUsage)
}
return op
}