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"
"crypto/rand"
"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"
2020-12-11 16:11:49 +00:00
"k8s.io/apimachinery/pkg/runtime/schema"
2020-12-10 16:54:36 +00:00
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"
)
2020-12-14 15:36:45 +00:00
// TODO: de-dup me when we abstract these controllers.
2020-12-10 16:54:36 +00:00
const (
symmetricKeySecretType = "secrets.pinniped.dev/symmetric"
symmetricKeySecretDataKey = "key"
symmetricKeySize = 32 // TODO: what should this be?
)
// generateKey is stubbed out for the purpose of testing. The default behavior is to generate a symmetric key.
//nolint:gochecknoglobals
var generateKey = generateSymmetricKey
func generateSymmetricKey ( ) ( [ ] byte , error ) {
b := make ( [ ] byte , symmetricKeySize )
if _ , err := rand . Read ( b ) ; err != nil {
return nil , err
}
return b , nil
}
2020-12-12 00:05:08 +00:00
type supervisorSecretsController struct {
2020-12-11 20:37:10 +00:00
owner * appsv1 . Deployment
client kubernetes . Interface
secrets corev1informers . SecretInformer
setCache 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: label the generated secret like we do in the JWKSWriterController
// 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.
// TODO: add tests for the filter like we do in the JWKSWriterController?
2020-12-11 16:11:49 +00:00
owner * appsv1 . Deployment ,
client kubernetes . Interface ,
secrets corev1informers . SecretInformer ,
2020-12-11 20:37:10 +00:00
setCache func ( secret [ ] byte ) ,
2020-12-11 16:11:49 +00:00
) controllerlib . Controller {
2020-12-12 00:05:08 +00:00
c := supervisorSecretsController {
2020-12-11 20:37:10 +00:00
owner : owner ,
client : client ,
secrets : secrets ,
setCache : setCache ,
2020-12-10 16:54:36 +00:00
}
2020-12-11 16:11:49 +00:00
filter := pinnipedcontroller . SimpleFilter ( func ( obj metav1 . Object ) bool {
return metav1 . IsControlledBy ( obj , owner )
} , nil )
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-10 16:54:36 +00:00
controllerlib . WithInformer ( secrets , filter , 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-10 16:54:36 +00:00
secret , err := c . secrets . 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 )
}
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-11 20:37:10 +00:00
c . setCache ( secret . Data [ symmetricKeySecretDataKey ] )
2020-12-10 16:54:36 +00:00
return nil
}
2020-12-12 04:48:45 +00:00
newSecret , err := generateSecret ( ctx . Key . Namespace , ctx . Key . Name , 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-11 20:37:10 +00:00
c . setCache ( newSecret . Data [ symmetricKeySecretDataKey ] )
2020-12-11 16:11:49 +00:00
2020-12-10 16:54:36 +00:00
return nil
}
2020-12-12 04:48:45 +00:00
func isValid ( secret * corev1 . Secret ) bool {
2020-12-10 16:54:36 +00:00
if secret . Type != symmetricKeySecretType {
return false
}
data , ok := secret . Data [ symmetricKeySecretDataKey ]
if ! ok {
return false
}
if len ( data ) != symmetricKeySize {
return false
}
return true
}
2020-12-12 04:48:45 +00:00
func secretDataFunc ( ) ( map [ string ] [ ] byte , error ) {
2020-12-10 16:54:36 +00:00
symmetricKey , err := generateKey ( )
if err != nil {
return nil , err
}
2020-12-12 04:48:45 +00:00
return map [ string ] [ ] byte {
symmetricKeySecretDataKey : symmetricKey ,
} , nil
}
func generateSecret ( namespace , name string , secretDataFunc func ( ) ( map [ string ] [ ] byte , error ) , owner metav1 . Object ) ( * corev1 . Secret , error ) {
secretData , err := secretDataFunc ( )
if err != nil {
return nil , err
}
2020-12-11 16:11:49 +00:00
deploymentGVK := schema . GroupVersionKind {
Group : appsv1 . SchemeGroupVersion . Group ,
Version : appsv1 . SchemeGroupVersion . Version ,
Kind : "Deployment" ,
}
2020-12-10 16:54:36 +00:00
return & corev1 . Secret {
ObjectMeta : metav1 . ObjectMeta {
2020-12-11 16:11:49 +00:00
Name : name ,
Namespace : namespace ,
OwnerReferences : [ ] metav1 . OwnerReference {
2020-12-12 04:48:45 +00:00
* metav1 . NewControllerRef ( owner , deploymentGVK ) ,
2020-12-11 16:11:49 +00:00
} ,
2020-12-10 16:54:36 +00:00
} ,
Type : symmetricKeySecretType ,
2020-12-12 04:48:45 +00:00
Data : secretData ,
2020-12-10 16:54:36 +00:00
} , nil
}
2020-12-12 00:05:08 +00:00
func ( c * supervisorSecretsController ) createSecret ( ctx context . Context , newSecret * corev1 . Secret ) error {
2020-12-10 16:54:36 +00:00
_ , err := c . client . CoreV1 ( ) . Secrets ( newSecret . Namespace ) . Create ( ctx , newSecret , metav1 . CreateOptions { } )
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-11 16:11:49 +00:00
secrets := c . client . 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
} )
}