c6c2c525a6
Also fix some tests that were broken by bumping golang and dependencies in the previous commits. Note that in addition to changes made to satisfy the linter which do not impact the behavior of the code, this commit also adds ReadHeaderTimeout to all usages of http.Server to satisfy the linter (and because it seemed like a good suggestion).
203 lines
5.6 KiB
Go
203 lines
5.6 KiB
Go
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
// Package generator provides a supervisorSecretsController that can ensure existence of a generated secret.
|
|
package generator
|
|
|
|
import (
|
|
"context"
|
|
"crypto/rand"
|
|
"fmt"
|
|
|
|
appsv1 "k8s.io/api/apps/v1"
|
|
corev1 "k8s.io/api/core/v1"
|
|
k8serrors "k8s.io/apimachinery/pkg/api/errors"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
corev1informers "k8s.io/client-go/informers/core/v1"
|
|
"k8s.io/client-go/kubernetes"
|
|
"k8s.io/client-go/util/retry"
|
|
"k8s.io/klog/v2"
|
|
|
|
pinnipedcontroller "go.pinniped.dev/internal/controller"
|
|
"go.pinniped.dev/internal/controllerlib"
|
|
"go.pinniped.dev/internal/plog"
|
|
)
|
|
|
|
// generateKey is stubbed out for the purpose of testing. The default behavior is to generate a symmetric key.
|
|
var generateKey = generateSymmetricKey //nolint:gochecknoglobals
|
|
|
|
type supervisorSecretsController struct {
|
|
labels map[string]string
|
|
kubeClient kubernetes.Interface
|
|
secretInformer corev1informers.SecretInformer
|
|
setCacheFunc func(secret []byte)
|
|
}
|
|
|
|
// NewSupervisorSecretsController instantiates a new controllerlib.Controller which will ensure existence of a generated secret.
|
|
func NewSupervisorSecretsController(
|
|
owner *appsv1.Deployment,
|
|
labels map[string]string,
|
|
kubeClient kubernetes.Interface,
|
|
secretInformer corev1informers.SecretInformer,
|
|
setCacheFunc func(secret []byte),
|
|
withInformer pinnipedcontroller.WithInformerOptionFunc,
|
|
initialEventFunc pinnipedcontroller.WithInitialEventOptionFunc,
|
|
) controllerlib.Controller {
|
|
c := supervisorSecretsController{
|
|
labels: labels,
|
|
kubeClient: kubeClient,
|
|
secretInformer: secretInformer,
|
|
setCacheFunc: setCacheFunc,
|
|
}
|
|
return controllerlib.New(
|
|
controllerlib.Config{Name: owner.Name + "-secret-generator", Syncer: &c},
|
|
withInformer(
|
|
secretInformer,
|
|
pinnipedcontroller.SimpleFilter(func(obj metav1.Object) bool {
|
|
secret, ok := obj.(*corev1.Secret)
|
|
if !ok {
|
|
return false
|
|
}
|
|
if secret.Type != SupervisorCSRFSigningKeySecretType {
|
|
return false
|
|
}
|
|
return true
|
|
}, nil),
|
|
controllerlib.InformerOption{},
|
|
),
|
|
initialEventFunc(controllerlib.Key{
|
|
Namespace: owner.Namespace,
|
|
Name: owner.Name + "-key",
|
|
}),
|
|
)
|
|
}
|
|
|
|
// Sync implements controllerlib.Syncer.Sync().
|
|
func (c *supervisorSecretsController) Sync(ctx controllerlib.Context) error {
|
|
secret, err := c.secretInformer.Lister().Secrets(ctx.Key.Namespace).Get(ctx.Key.Name)
|
|
isNotFound := k8serrors.IsNotFound(err)
|
|
if !isNotFound && err != nil {
|
|
return fmt.Errorf("failed to list secret %s/%s: %w", ctx.Key.Namespace, ctx.Key.Name, err)
|
|
}
|
|
|
|
secretNeedsUpdate := isNotFound || !isValid(secret, c.labels)
|
|
if !secretNeedsUpdate {
|
|
plog.Debug("secret is up to date", "secret", klog.KObj(secret))
|
|
c.setCacheFunc(secret.Data[symmetricSecretDataKey])
|
|
return nil
|
|
}
|
|
|
|
newSecret, err := generateSecret(ctx.Key.Namespace, ctx.Key.Name, c.labels, secretDataFunc)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to generate secret: %w", err)
|
|
}
|
|
|
|
if isNotFound {
|
|
err = c.createSecret(ctx.Context, newSecret)
|
|
} else {
|
|
err = c.updateSecret(ctx.Context, &newSecret, ctx.Key.Name)
|
|
}
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create/update secret %s/%s: %w", newSecret.Namespace, newSecret.Name, err)
|
|
}
|
|
|
|
c.setCacheFunc(newSecret.Data[symmetricSecretDataKey])
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *supervisorSecretsController) createSecret(ctx context.Context, newSecret *corev1.Secret) error {
|
|
_, err := c.kubeClient.CoreV1().Secrets(newSecret.Namespace).Create(ctx, newSecret, metav1.CreateOptions{})
|
|
return err
|
|
}
|
|
|
|
func (c *supervisorSecretsController) updateSecret(ctx context.Context, newSecret **corev1.Secret, secretName string) error {
|
|
secrets := c.kubeClient.CoreV1().Secrets((*newSecret).Namespace)
|
|
return retry.RetryOnConflict(retry.DefaultBackoff, func() error {
|
|
currentSecret, err := secrets.Get(ctx, secretName, metav1.GetOptions{})
|
|
isNotFound := k8serrors.IsNotFound(err)
|
|
if !isNotFound && err != nil {
|
|
return fmt.Errorf("failed to get secret: %w", err)
|
|
}
|
|
|
|
if isNotFound {
|
|
if err := c.createSecret(ctx, *newSecret); err != nil {
|
|
return fmt.Errorf("failed to create secret: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
if isValid(currentSecret, c.labels) {
|
|
*newSecret = currentSecret
|
|
return nil
|
|
}
|
|
|
|
currentSecret.Type = (*newSecret).Type
|
|
currentSecret.Data = (*newSecret).Data
|
|
for key, value := range c.labels {
|
|
currentSecret.Labels[key] = value
|
|
}
|
|
|
|
_, err = secrets.Update(ctx, currentSecret, metav1.UpdateOptions{})
|
|
return err
|
|
})
|
|
}
|
|
|
|
func generateSymmetricKey() ([]byte, error) {
|
|
b := make([]byte, symmetricKeySize)
|
|
if _, err := rand.Read(b); err != nil {
|
|
return nil, err
|
|
}
|
|
return b, nil
|
|
}
|
|
|
|
func isValid(secret *corev1.Secret, labels map[string]string) bool {
|
|
if secret.Type != SupervisorCSRFSigningKeySecretType {
|
|
return false
|
|
}
|
|
|
|
data, ok := secret.Data[symmetricSecretDataKey]
|
|
if !ok {
|
|
return false
|
|
}
|
|
if len(data) != symmetricKeySize {
|
|
return false
|
|
}
|
|
|
|
for key, value := range labels {
|
|
if secret.Labels[key] != value {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func secretDataFunc() (map[string][]byte, error) {
|
|
symmetricKey, err := generateKey()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return map[string][]byte{
|
|
symmetricSecretDataKey: symmetricKey,
|
|
}, nil
|
|
}
|
|
|
|
func generateSecret(namespace, name string, labels map[string]string, secretDataFunc func() (map[string][]byte, error)) (*corev1.Secret, error) {
|
|
secretData, err := secretDataFunc()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &corev1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: name,
|
|
Namespace: namespace,
|
|
Labels: labels,
|
|
},
|
|
Type: SupervisorCSRFSigningKeySecretType,
|
|
Data: secretData,
|
|
}, nil
|
|
}
|