Skip to content

Commit

Permalink
Add SealedSecret 'template' to use when constructing Secret
Browse files Browse the repository at this point in the history
This change adds a `spec.template` to the SealedSecrets schema, which
includes `metadata` and `type` used when constructing the new Secret.
(All the Secret fields _other_ than `data`/`encryptedData`)

Also this change adds:
- Events to the SealedSecret describing progress/errors while
  unsealing.
- A SealedSecret `status` structure that exposes overall
  success/failure.

Fixes bitnami-labs#92
Fixes bitnami-labs#72
  • Loading branch information
anguslees authored and Marko Mikulicic committed Jul 10, 2019
1 parent 30f8f0e commit d7a4622
Show file tree
Hide file tree
Showing 8 changed files with 315 additions and 45 deletions.
111 changes: 89 additions & 22 deletions cmd/controller/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,31 +6,55 @@ import (
"log"
"time"

"k8s.io/apimachinery/pkg/runtime"

apiv1 "k8s.io/api/core/v1"
corev1 "k8s.io/api/core/v1"
apiequality "k8s.io/apimachinery/pkg/api/equality"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
runtimeserializer "k8s.io/apimachinery/pkg/runtime/serializer"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/kubernetes/typed/core/v1"
v1 "k8s.io/client-go/kubernetes/typed/core/v1"
"k8s.io/client-go/tools/cache"
"k8s.io/client-go/tools/record"
"k8s.io/client-go/util/workqueue"

ssv1alpha1 "github.com/bitnami-labs/sealed-secrets/pkg/apis/sealed-secrets/v1alpha1"
ssclientset "github.com/bitnami-labs/sealed-secrets/pkg/client/clientset/versioned"
ssscheme "github.com/bitnami-labs/sealed-secrets/pkg/client/clientset/versioned/scheme"
ssv1alpha1client "github.com/bitnami-labs/sealed-secrets/pkg/client/clientset/versioned/typed/sealed-secrets/v1alpha1"
ssinformer "github.com/bitnami-labs/sealed-secrets/pkg/client/informers/externalversions"
)

const maxRetries = 5
const (
maxRetries = 5

// SuccessUnsealed is used as part of the Event 'reason' when
// a SealedSecret is unsealed successfully.
SuccessUnsealed = "Unsealed"

// ErrUpdateFailed is used as part of the Event 'reason' when
// a SealedSecret fails to update the target Secret for a
// non-cryptography reason. Typically this is due to API I/O
// or RBAC issues.
ErrUpdateFailed = "ErrUpdateFailed"

// ErrUnsealFailed is used as part of the Event 'reason' when a
// SealedSecret fails the unsealing process. Typically this
// is because it is encrypted with the wrong key or has been
// renamed from its original namespace/name.
ErrUnsealFailed = "ErrUnsealFailed"
)

// Controller implements the main sealed-secrets-controller loop.
type Controller struct {
queue workqueue.RateLimitingInterface
informer cache.SharedIndexInformer
sclient v1.SecretsGetter
ssclient ssv1alpha1client.SealedSecretsGetter
recorder record.EventRecorder
keyRegistry *KeyRegistry
}

Expand Down Expand Up @@ -62,9 +86,15 @@ func unseal(sclient v1.SecretsGetter, codecs runtimeserializer.CodecFactory, key
}

// NewController returns the main sealed-secrets controller loop.
func NewController(clientset kubernetes.Interface, ssinformer ssinformer.SharedInformerFactory, keyRegistry *KeyRegistry) *Controller {
func NewController(clientset kubernetes.Interface, ssclientset ssclientset.Interface, ssinformer ssinformer.SharedInformerFactory, keyRegistry *KeyRegistry) *Controller {
queue := workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter())

ssscheme.AddToScheme(scheme.Scheme)
eventBroadcaster := record.NewBroadcaster()
eventBroadcaster.StartLogging(log.Printf)
eventBroadcaster.StartRecordingToSink(&v1.EventSinkImpl{Interface: clientset.CoreV1().Events("")})
recorder := eventBroadcaster.NewRecorder(scheme.Scheme, corev1.EventSource{Component: "sealed-secrets"})

informer := ssinformer.Bitnami().V1alpha1().
SealedSecrets().
Informer()
Expand Down Expand Up @@ -93,7 +123,9 @@ func NewController(clientset kubernetes.Interface, ssinformer ssinformer.SharedI
return &Controller{
informer: informer,
queue: queue,
sclient: clientset.Core(),
sclient: clientset.CoreV1(),
ssclient: ssclientset.BitnamiV1alpha1(),
recorder: recorder,
keyRegistry: keyRegistry,
}
}
Expand Down Expand Up @@ -185,31 +217,66 @@ func (c *Controller) unseal(key string) error {
ssecret := obj.(*ssv1alpha1.SealedSecret)
log.Printf("Updating %s", key)

secret, err := c.attemptUnseal(ssecret)
newSecret, err := c.attemptUnseal(ssecret)
if err != nil {
c.recorder.Eventf(ssecret, corev1.EventTypeWarning, ErrUnsealFailed, "Failed to unseal: %v", err)
return err
}

_, err = c.sclient.Secrets(ssecret.GetObjectMeta().GetNamespace()).Create(secret)
if err == nil {
// Secret successfully created
return nil
secret, err := c.sclient.Secrets(ssecret.GetObjectMeta().GetNamespace()).Get(newSecret.GetObjectMeta().GetName(), metav1.GetOptions{})
if errors.IsNotFound(err) {
secret, err = c.sclient.Secrets(ssecret.GetObjectMeta().GetNamespace()).Create(newSecret)
}
if !errors.IsAlreadyExists(err) {
// Error wasn't already exists so is real error
if err != nil {
c.recorder.Event(ssecret, corev1.EventTypeWarning, ErrUpdateFailed, err.Error())
return err
}

// Secret already exists so update it in place with new data/owner reference
updatedSecret, err := c.updateSecret(secret)
if !metav1.IsControlledBy(secret, ssecret) {
msg := fmt.Sprintf("Resource %q already exists and is not managed by SealedSecret", secret.Name)
c.recorder.Event(ssecret, corev1.EventTypeWarning, ErrUpdateFailed, msg)
return fmt.Errorf("failed update: %s", msg)
}

origSecret := secret
secret = secret.DeepCopy()

secret.Data = newSecret.Data
secret.Type = newSecret.Type
secret.ObjectMeta.Annotations = newSecret.ObjectMeta.Annotations
secret.ObjectMeta.Labels = newSecret.ObjectMeta.Labels

if !apiequality.Semantic.DeepEqual(origSecret, secret) {
secret, err = c.sclient.Secrets(ssecret.GetObjectMeta().GetNamespace()).Update(secret)
if err != nil {
c.recorder.Event(ssecret, corev1.EventTypeWarning, ErrUpdateFailed, err.Error())
return err
}
}

err = c.updateSealedSecretStatus(ssecret, secret)
if err != nil {
return fmt.Errorf("failed to update existing secret: %s", err)
// Non-fatal. Log and continue.
log.Printf("Error updating SealedSecret %s status: %v", key, err)
}
_, err = c.sclient.Secrets(ssecret.GetObjectMeta().GetNamespace()).Update(updatedSecret)

c.recorder.Event(ssecret, corev1.EventTypeNormal, SuccessUnsealed, "SealedSecret unsealed successfully")
return nil
}

func (c *Controller) updateSealedSecretStatus(ssecret *ssv1alpha1.SealedSecret, secret *corev1.Secret) error {
ssecret = ssecret.DeepCopy()

ssecret.Status.ObservedGeneration = secret.ObjectMeta.Generation

// TODO: Use UpdateStatus when k8s CustomResourceSubresources
// feature is widespread.
var err error
ssecret, err = c.ssclient.SealedSecrets(ssecret.GetObjectMeta().GetNamespace()).Update(ssecret)
return err
}

func (c *Controller) updateSecret(newSecret *apiv1.Secret) (*apiv1.Secret, error) {
func (c *Controller) updateSecret(newSecret *corev1.Secret) (*corev1.Secret, error) {
existingSecret, err := c.sclient.Secrets(newSecret.GetObjectMeta().GetNamespace()).Get(newSecret.GetObjectMeta().GetName(), metav1.GetOptions{})
if err != nil {
return nil, fmt.Errorf("failed to read existing secret: %s", err)
Expand All @@ -222,7 +289,7 @@ func (c *Controller) updateSecret(newSecret *apiv1.Secret) (*apiv1.Secret, error
return existingSecret, nil
}

func (c *Controller) updateOwnerReferences(existing, new *apiv1.Secret) {
func (c *Controller) updateOwnerReferences(existing, new *corev1.Secret) {
ownerRefs := existing.GetOwnerReferences()

for _, newRef := range new.GetOwnerReferences() {
Expand Down Expand Up @@ -288,11 +355,11 @@ func (c *Controller) Rotate(content []byte) ([]byte, error) {
}
}

func (c *Controller) attemptUnseal(ss *ssv1alpha1.SealedSecret) (*apiv1.Secret, error) {
func (c *Controller) attemptUnseal(ss *ssv1alpha1.SealedSecret) (*corev1.Secret, error) {
return attemptUnseal(ss, c.keyRegistry)
}

func attemptUnseal(ss *ssv1alpha1.SealedSecret, keyRegistry *KeyRegistry) (*apiv1.Secret, error) {
func attemptUnseal(ss *ssv1alpha1.SealedSecret, keyRegistry *KeyRegistry) (*corev1.Secret, error) {
for _, privKey := range keyRegistry.privateKeys {
if secret, err := ss.Unseal(scheme.Codecs, privKey); err == nil {
return secret, nil
Expand Down
6 changes: 3 additions & 3 deletions cmd/controller/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ func main2() error {
return err
}

ssclient, err := sealedsecrets.NewForConfig(config)
ssclientset, err := sealedsecrets.NewForConfig(config)
if err != nil {
return err
}
Expand All @@ -158,8 +158,8 @@ func main2() error {

initKeyGenSignalListener(trigger)

ssinformer := ssinformers.NewSharedInformerFactory(ssclient, 0)
controller := NewController(clientset, ssinformer, keyRegistry)
ssinformer := ssinformers.NewSharedInformerFactory(ssclientset, 0)
controller := NewController(clientset, ssclientset, ssinformer, keyRegistry)

stop := make(chan struct{})
defer close(stop)
Expand Down
4 changes: 1 addition & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,7 @@ require (
github.com/hashicorp/golang-lru v0.0.0-20160813221303-0a025b7e63ad // indirect
github.com/howeyc/gopass v0.0.0-20170109162249-bf9dde6d0d2c // indirect
github.com/imdario/mergo v0.0.0-20170620104701-e3000cb3d28c // indirect
github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3 // indirect
github.com/kr/pretty v0.1.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.1 // indirect
github.com/onsi/ginkgo v0.0.0-20180119174237-747514b53ddd
github.com/onsi/gomega v0.0.0-20180205174834-a9c79f175573
Expand All @@ -39,8 +37,8 @@ require (
google.golang.org/appengine v0.0.0-20170801183137-c5a90ac045b7 // indirect
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
gopkg.in/inf.v0 v0.9.0 // indirect
gopkg.in/yaml.v2 v2.0.0 // indirect
k8s.io/api v0.0.0-20180828232432-12444147eb11
k8s.io/apimachinery v0.0.0-20180619225948-e386b2658ed2
k8s.io/client-go v0.0.0-20180817174322-745ca8300397
k8s.io/kube-openapi v0.0.0-20190709113604-33be087ad058 // indirect
)
Loading

0 comments on commit d7a4622

Please sign in to comment.