Skip to content

Commit

Permalink
Support to recreate a deleted secret generated by the controller (#963)
Browse files Browse the repository at this point in the history
  • Loading branch information
alvneiayu authored Sep 27, 2022
1 parent bd88851 commit a5696b8
Show file tree
Hide file tree
Showing 5 changed files with 137 additions and 14 deletions.
2 changes: 1 addition & 1 deletion controller.jsonnet
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ controller {
{
apiGroups: [''],
resources: ['secrets'],
verbs: ['get', 'list', 'create', 'update', 'delete'],
verbs: ['get', 'list', 'create', 'update', 'delete', 'watch'],
},
{
apiGroups: [''],
Expand Down
1 change: 1 addition & 0 deletions helm/sealed-secrets/templates/cluster-role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ rules:
- create
- update
- delete
- watch
- apiGroups:
- ""
resources:
Expand Down
75 changes: 75 additions & 0 deletions integration/controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,81 @@ var _ = Describe("create", func() {
})
})

Describe("Secret Recreation", func() {
Context("With owned secret", func() {
JustBeforeEach(func() {
Eventually(func() (*v1.Secret, error) {
return c.Secrets(ns).Get(ctx, secretName, metav1.GetOptions{})
}, Timeout, PollingInterval).Should(WithTransform(getFirstOwnerName, Equal(ss.GetName())))
err:= c.Secrets(ns).Delete(ctx, secretName, metav1.DeleteOptions{})
Expect(err).NotTo(HaveOccurred())
})
It("should recreate the secret", func() {
expected := map[string][]byte{
"foo": []byte("bar"),
}
Eventually(func() (*v1.Secret, error) {
return c.Secrets(ns).Get(ctx, secretName, metav1.GetOptions{})
}, Timeout, PollingInterval).Should(WithTransform(getData, Equal(expected)))
Eventually(func() (*v1.EventList, error) {
return c.Events(ns).Search(scheme.Scheme, ss)
}, Timeout, PollingInterval).Should(
containEventWithReason(Equal("Unsealed")),
)
Eventually(func() (*v1.Secret, error) {
return c.Secrets(ns).Get(ctx, secretName, metav1.GetOptions{})
}, Timeout, PollingInterval).Should(WithTransform(getFirstOwnerName, Equal(ss.GetName())))
})
})

Context("With unowned secret with managed annotation", func() {
BeforeEach(func() {
s.Annotations = map[string]string{
ssv1alpha1.SealedSecretManagedAnnotation: "true",
}
c.Secrets(ns).Create(ctx, s, metav1.CreateOptions{})
})
JustBeforeEach(func() {
err:= c.Secrets(ns).Delete(ctx, secretName, metav1.DeleteOptions{})
Expect(err).NotTo(HaveOccurred())
})
It("should recreate the secret", func() {
expected := map[string][]byte{
"foo": []byte("bar"),
}
Eventually(func() (*v1.Secret, error) {
return c.Secrets(ns).Get(ctx, secretName, metav1.GetOptions{})
}, Timeout, PollingInterval).Should(WithTransform(getData, Equal(expected)))
Eventually(func() (*v1.EventList, error) {
return c.Events(ns).Search(scheme.Scheme, ss)
}, Timeout, PollingInterval).Should(
containEventWithReason(Equal("Unsealed")),
)
Eventually(func() (*v1.Secret, error) {
return c.Secrets(ns).Get(ctx, secretName, metav1.GetOptions{})
}, Timeout, PollingInterval).Should(WithTransform(getFirstOwnerName, Equal(ss.GetName())))
})
})

Context("With unowned secret without managed annotation", func() {
BeforeEach(func() {
s.Annotations = map[string]string{
}
c.Secrets(ns).Create(ctx, s, metav1.CreateOptions{})
})
JustBeforeEach(func() {
err:= c.Secrets(ns).Delete(ctx, secretName, metav1.DeleteOptions{})
Expect(err).NotTo(HaveOccurred())
})
It("should not recreate the secret", func() {
Consistently(func() error {
_, err := c.Secrets(ns).Get(ctx, secretName, metav1.GetOptions{})
return err
}).Should(WithTransform(errors.IsNotFound, Equal(true)))
})
})
})

Describe("Same name, wrong key", func() {
BeforeEach(func() {
// NB: weak keysize - this is just a test case
Expand Down
60 changes: 51 additions & 9 deletions pkg/controller/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import (
"k8s.io/client-go/util/workqueue"
"k8s.io/klog"

"k8s.io/client-go/informers"

ssv1alpha1 "github.com/bitnami-labs/sealed-secrets/pkg/apis/sealedsecrets/v1alpha1"
ssclientset "github.com/bitnami-labs/sealed-secrets/pkg/client/clientset/versioned"
ssscheme "github.com/bitnami-labs/sealed-secrets/pkg/client/clientset/versioned/scheme"
Expand Down Expand Up @@ -54,7 +56,8 @@ const (
// Controller implements the main sealed-secrets-controller loop.
type Controller struct {
queue workqueue.RateLimitingInterface
informer cache.SharedIndexInformer
ssInformer cache.SharedIndexInformer
sInformer cache.SharedIndexInformer
sclient v1.SecretsGetter
ssclient ssv1alpha1client.SealedSecretsGetter
recorder record.EventRecorder
Expand All @@ -65,7 +68,7 @@ type Controller struct {
}

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

utilruntime.Must(ssscheme.AddToScheme(scheme.Scheme))
Expand All @@ -74,11 +77,11 @@ func NewController(clientset kubernetes.Interface, ssclientset ssclientset.Inter
eventBroadcaster.StartRecordingToSink(&v1.EventSinkImpl{Interface: clientset.CoreV1().Events("")})
recorder := eventBroadcaster.NewRecorder(scheme.Scheme, corev1.EventSource{Component: "sealed-secrets"})

informer := ssinformer.Bitnami().V1alpha1().
ssInformer := ssinformer.Bitnami().V1alpha1().
SealedSecrets().
Informer()

informer.AddEventHandler(cache.ResourceEventHandlerFuncs{
ssInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {
key, err := cache.MetaNamespaceKeyFunc(obj)
if err == nil {
Expand All @@ -102,8 +105,46 @@ func NewController(clientset kubernetes.Interface, ssclientset ssclientset.Inter
},
})

sInformer := sinformer.Core().V1().Secrets().Informer()
sInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
DeleteFunc: func(obj interface{}) {
skey, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj)
if err != nil {
log.Printf("failed to fetch Secret key: %v", err)
return
}

ns, name, err := cache.SplitMetaNamespaceKey(skey)
if err != nil {
log.Printf("failed to get namespace and name from key: %v", err)
return
}

ssecret, err := ssclientset.BitnamiV1alpha1().SealedSecrets(ns).Get(context.Background(), name, metav1.GetOptions{})
if err != nil {
if !errors.IsNotFound(err) {
log.Printf("failed to get SealedSecret: %v", err)
}
return
}

if !metav1.IsControlledBy(obj.(*corev1.Secret), ssecret) && !isAnnotatedToBeManaged(obj.(*corev1.Secret)) {
return
}

sskey, err := cache.MetaNamespaceKeyFunc(ssecret)
if err != nil {
log.Printf("failed to fetch SealedSecret key: %v", err)
return
}

queue.Add(sskey)
},
})

return &Controller{
informer: informer,
ssInformer: ssInformer,
sInformer: sInformer,
queue: queue,
sclient: clientset.CoreV1(),
ssclient: ssclientset.BitnamiV1alpha1(),
Expand All @@ -115,15 +156,15 @@ func NewController(clientset kubernetes.Interface, ssclientset ssclientset.Inter
// HasSynced returns true once this controller has completed an
// initial resource listing
func (c *Controller) HasSynced() bool {
return c.informer.HasSynced()
return c.ssInformer.HasSynced() && c.sInformer.HasSynced()
}

// LastSyncResourceVersion is the resource version observed when last
// synced with the underlying store. The value returned is not
// synchronized with access to the underlying store and is not
// thread-safe.
func (c *Controller) LastSyncResourceVersion() string {
return c.informer.LastSyncResourceVersion()
return c.ssInformer.LastSyncResourceVersion()
}

// Run begins processing items, and will continue until a value is
Expand All @@ -134,7 +175,8 @@ func (c *Controller) Run(stopCh <-chan struct{}) {

defer c.queue.ShutDown()

go c.informer.Run(stopCh)
go c.ssInformer.Run(stopCh)
go c.sInformer.Run(stopCh)

if !cache.WaitForCacheSync(stopCh, c.HasSynced) {
utilruntime.HandleError(fmt.Errorf("Timed out waiting for caches to sync"))
Expand Down Expand Up @@ -180,7 +222,7 @@ func (c *Controller) processNextItem(ctx context.Context) bool {

func (c *Controller) unseal(ctx context.Context, key string) (unsealErr error) {
unsealRequestsTotal.Inc()
obj, exists, err := c.informer.GetIndexer().GetByKey(key)
obj, exists, err := c.ssInformer.GetIndexer().GetByKey(key)
if err != nil {
log.Printf("Error fetching object with key %s from store: %v", key, err)
unsealErrorsTotal.WithLabelValues("fetch", "").Inc()
Expand Down
13 changes: 9 additions & 4 deletions pkg/controller/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import (
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"

"k8s.io/client-go/informers"

ssv1alpha1 "github.com/bitnami-labs/sealed-secrets/pkg/apis/sealedsecrets/v1alpha1"
sealedsecrets "github.com/bitnami-labs/sealed-secrets/pkg/client/clientset/versioned"
ssinformers "github.com/bitnami-labs/sealed-secrets/pkg/client/informers/externalversions"
Expand Down Expand Up @@ -193,8 +195,9 @@ func Main(f *Flags, version string) error {
}
}

sinformer := informers.NewFilteredSharedInformerFactory(clientset, 0, namespace, tweakopts)
ssinformer := ssinformers.NewFilteredSharedInformerFactory(ssclientset, 0, namespace, tweakopts)
controller := NewController(clientset, ssclientset, ssinformer, keyRegistry)
controller := NewController(clientset, ssclientset, ssinformer, sinformer, keyRegistry)
controller.oldGCBehavior = f.OldGCBehavior
controller.updateStatus = f.UpdateStatus

Expand All @@ -206,7 +209,8 @@ func Main(f *Flags, version string) error {
if f.AdditionalNamespaces != "" {
addNS := removeDuplicates(strings.Split(f.AdditionalNamespaces, ","))

var inf ssinformers.SharedInformerFactory
var ssinf ssinformers.SharedInformerFactory
var sinf informers.SharedInformerFactory
var ctlr *Controller

for _, ns := range addNS {
Expand All @@ -218,8 +222,9 @@ func Main(f *Flags, version string) error {
return err
}
if ns != namespace {
inf = ssinformers.NewFilteredSharedInformerFactory(ssclientset, 0, ns, tweakopts)
ctlr = NewController(clientset, ssclientset, inf, keyRegistry)
ssinf = ssinformers.NewFilteredSharedInformerFactory(ssclientset, 0, ns, tweakopts)
sinf = informers.NewFilteredSharedInformerFactory(clientset, 0, ns, tweakopts)
ctlr = NewController(clientset, ssclientset, ssinf, sinf, keyRegistry)
ctlr.oldGCBehavior = f.OldGCBehavior
ctlr.updateStatus = f.UpdateStatus
log.Printf("Starting informer for namespace: %s\n", ns)
Expand Down

0 comments on commit a5696b8

Please sign in to comment.