2020-12-10 16:54:36 +00:00
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
2020-12-12 00:05:08 +00:00
// Package secretgenerator provides a supervisorSecretsController that can ensure existence of a generated secret.
package generator
2020-12-10 16:54:36 +00:00
import (
"context"
"fmt"
2020-12-11 16:11:49 +00:00
appsv1 "k8s.io/api/apps/v1"
2020-12-10 16:54:36 +00:00
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.
//nolint:gochecknoglobals
var generateKey = generateSymmetricKey
2020-12-12 00:05:08 +00:00
type supervisorSecretsController struct {
2020-12-15 00:08:48 +00:00
owner * appsv1 . Deployment
labels map [ string ] string
kubeClient kubernetes . Interface
secretInformer corev1informers . SecretInformer
setCacheFunc func ( secret [ ] byte )
2020-12-10 16:54:36 +00:00
}
2020-12-12 00:05:08 +00:00
// NewSupervisorSecretsController instantiates a new controllerlib.Controller which will ensure existence of a generated secret.
func NewSupervisorSecretsController (
2020-12-11 20:37:10 +00:00
// TODO: generate the name for the secret and label the secret with the UID of the owner? So that we don't have naming conflicts if the user has already created a Secret with that name.
2020-12-11 16:11:49 +00:00
owner * appsv1 . Deployment ,
2020-12-14 20:53:12 +00:00
labels map [ string ] string ,
2020-12-15 00:08:48 +00:00
kubeClient kubernetes . Interface ,
secretInformer corev1informers . SecretInformer ,
setCacheFunc func ( secret [ ] byte ) ,
withInformer pinnipedcontroller . WithInformerOptionFunc ,
2020-12-11 16:11:49 +00:00
) controllerlib . Controller {
2020-12-12 00:05:08 +00:00
c := supervisorSecretsController {
2020-12-15 00:08:48 +00:00
owner : owner ,
labels : labels ,
kubeClient : kubeClient ,
secretInformer : secretInformer ,
setCacheFunc : setCacheFunc ,
2020-12-10 16:54:36 +00:00
}
return controllerlib . New (
2020-12-11 16:11:49 +00:00
controllerlib . Config { Name : owner . Name + "-secret-generator" , Syncer : & c } ,
2020-12-15 00:08:48 +00:00
withInformer (
secretInformer ,
pinnipedcontroller . SimpleFilter ( func ( obj metav1 . Object ) bool {
return metav1 . IsControlledBy ( obj , owner )
} , nil ) ,
controllerlib . InformerOption { } ,
) ,
2020-12-11 16:11:49 +00:00
controllerlib . WithInitialEvent ( controllerlib . Key {
Namespace : owner . Namespace ,
2020-12-11 20:37:10 +00:00
Name : owner . Name + "-key" ,
2020-12-11 16:11:49 +00:00
} ) ,
2020-12-10 16:54:36 +00:00
)
}
// Sync implements controllerlib.Syncer.Sync().
2020-12-12 00:05:08 +00:00
func ( c * supervisorSecretsController ) Sync ( ctx controllerlib . Context ) error {
2020-12-15 00:08:48 +00:00
secret , err := c . secretInformer . Lister ( ) . Secrets ( ctx . Key . Namespace ) . Get ( ctx . Key . Name )
2020-12-10 16:54:36 +00:00
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 )
}
2020-12-12 04:48:45 +00:00
secretNeedsUpdate := isNotFound || ! isValid ( secret )
2020-12-10 16:54:36 +00:00
if ! secretNeedsUpdate {
plog . Debug ( "secret is up to date" , "secret" , klog . KObj ( secret ) )
2020-12-15 01:38:01 +00:00
c . setCacheFunc ( secret . Data [ SymmetricSecretDataKey ] )
2020-12-10 16:54:36 +00:00
return nil
}
2020-12-14 20:53:12 +00:00
newSecret , err := generateSecret ( ctx . Key . Namespace , ctx . Key . Name , c . labels , secretDataFunc , c . owner )
2020-12-10 16:54:36 +00:00
if err != nil {
return fmt . Errorf ( "failed to generate secret: %w" , err )
}
if isNotFound {
err = c . createSecret ( ctx . Context , newSecret )
} else {
2020-12-11 16:11:49 +00:00
err = c . updateSecret ( ctx . Context , & newSecret , ctx . Key . Name )
2020-12-10 16:54:36 +00:00
}
if err != nil {
2020-12-11 16:11:49 +00:00
return fmt . Errorf ( "failed to create/update secret %s/%s: %w" , newSecret . Namespace , newSecret . Name , err )
2020-12-10 16:54:36 +00:00
}
2020-12-15 01:38:01 +00:00
c . setCacheFunc ( newSecret . Data [ SymmetricSecretDataKey ] )
2020-12-11 16:11:49 +00:00
2020-12-10 16:54:36 +00:00
return nil
}
2020-12-12 00:05:08 +00:00
func ( c * supervisorSecretsController ) createSecret ( ctx context . Context , newSecret * corev1 . Secret ) error {
2020-12-15 00:08:48 +00:00
_ , err := c . kubeClient . CoreV1 ( ) . Secrets ( newSecret . Namespace ) . Create ( ctx , newSecret , metav1 . CreateOptions { } )
2020-12-10 16:54:36 +00:00
return err
}
2020-12-12 00:05:08 +00:00
func ( c * supervisorSecretsController ) updateSecret ( ctx context . Context , newSecret * * corev1 . Secret , secretName string ) error {
2020-12-15 00:08:48 +00:00
secrets := c . kubeClient . CoreV1 ( ) . Secrets ( ( * newSecret ) . Namespace )
2020-12-10 16:54:36 +00:00
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 {
2020-12-11 16:11:49 +00:00
if err := c . createSecret ( ctx , * newSecret ) ; err != nil {
2020-12-10 16:54:36 +00:00
return fmt . Errorf ( "failed to create secret: %w" , err )
}
return nil
}
2020-12-12 04:48:45 +00:00
if isValid ( currentSecret ) {
2020-12-11 16:11:49 +00:00
* newSecret = currentSecret
2020-12-10 16:54:36 +00:00
return nil
}
2020-12-11 16:11:49 +00:00
currentSecret . Type = ( * newSecret ) . Type
currentSecret . Data = ( * newSecret ) . Data
2020-12-10 16:54:36 +00:00
_ , err = secrets . Update ( ctx , currentSecret , metav1 . UpdateOptions { } )
return err
} )
}