Skip to content

Commit 2cf3d36

Browse files
angusleesMarko Mikulicic
authored and
Marko Mikulicic
committed
Add SealedSecret 'template' to use when constructing Secret
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 #92 Fixes #72
1 parent 30f8f0e commit 2cf3d36

File tree

8 files changed

+315
-46
lines changed

8 files changed

+315
-46
lines changed

cmd/controller/controller.go

+89-22
Original file line numberDiff line numberDiff line change
@@ -6,31 +6,55 @@ import (
66
"log"
77
"time"
88

9-
"k8s.io/apimachinery/pkg/runtime"
10-
11-
apiv1 "k8s.io/api/core/v1"
9+
corev1 "k8s.io/api/core/v1"
10+
apiequality "k8s.io/apimachinery/pkg/api/equality"
1211
"k8s.io/apimachinery/pkg/api/errors"
1312
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
13+
"k8s.io/apimachinery/pkg/runtime"
1414
runtimeserializer "k8s.io/apimachinery/pkg/runtime/serializer"
1515
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
1616
"k8s.io/apimachinery/pkg/util/wait"
1717
"k8s.io/client-go/kubernetes"
1818
"k8s.io/client-go/kubernetes/scheme"
19-
"k8s.io/client-go/kubernetes/typed/core/v1"
19+
v1 "k8s.io/client-go/kubernetes/typed/core/v1"
2020
"k8s.io/client-go/tools/cache"
21+
"k8s.io/client-go/tools/record"
2122
"k8s.io/client-go/util/workqueue"
2223

2324
ssv1alpha1 "github.com/bitnami-labs/sealed-secrets/pkg/apis/sealed-secrets/v1alpha1"
25+
ssclientset "github.com/bitnami-labs/sealed-secrets/pkg/client/clientset/versioned"
26+
ssscheme "github.com/bitnami-labs/sealed-secrets/pkg/client/clientset/versioned/scheme"
27+
ssv1alpha1client "github.com/bitnami-labs/sealed-secrets/pkg/client/clientset/versioned/typed/sealed-secrets/v1alpha1"
2428
ssinformer "github.com/bitnami-labs/sealed-secrets/pkg/client/informers/externalversions"
2529
)
2630

27-
const maxRetries = 5
31+
const (
32+
maxRetries = 5
33+
34+
// SuccessUnsealed is used as part of the Event 'reason' when
35+
// a SealedSecret is unsealed successfully.
36+
SuccessUnsealed = "Unsealed"
37+
38+
// ErrUpdateFailed is used as part of the Event 'reason' when
39+
// a SealedSecret fails to update the target Secret for a
40+
// non-cryptography reason. Typically this is due to API I/O
41+
// or RBAC issues.
42+
ErrUpdateFailed = "ErrUpdateFailed"
43+
44+
// ErrUnsealFailed is used as part of the Event 'reason' when a
45+
// SealedSecret fails the unsealing process. Typically this
46+
// is because it is encrypted with the wrong key or has been
47+
// renamed from its original namespace/name.
48+
ErrUnsealFailed = "ErrUnsealFailed"
49+
)
2850

2951
// Controller implements the main sealed-secrets-controller loop.
3052
type Controller struct {
3153
queue workqueue.RateLimitingInterface
3254
informer cache.SharedIndexInformer
3355
sclient v1.SecretsGetter
56+
ssclient ssv1alpha1client.SealedSecretsGetter
57+
recorder record.EventRecorder
3458
keyRegistry *KeyRegistry
3559
}
3660

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

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

92+
ssscheme.AddToScheme(scheme.Scheme)
93+
eventBroadcaster := record.NewBroadcaster()
94+
eventBroadcaster.StartLogging(log.Printf)
95+
eventBroadcaster.StartRecordingToSink(&v1.EventSinkImpl{Interface: clientset.CoreV1().Events("")})
96+
recorder := eventBroadcaster.NewRecorder(scheme.Scheme, corev1.EventSource{Component: "sealed-secrets"})
97+
6898
informer := ssinformer.Bitnami().V1alpha1().
6999
SealedSecrets().
70100
Informer()
@@ -93,7 +123,9 @@ func NewController(clientset kubernetes.Interface, ssinformer ssinformer.SharedI
93123
return &Controller{
94124
informer: informer,
95125
queue: queue,
96-
sclient: clientset.Core(),
126+
sclient: clientset.CoreV1(),
127+
ssclient: ssclientset.BitnamiV1alpha1(),
128+
recorder: recorder,
97129
keyRegistry: keyRegistry,
98130
}
99131
}
@@ -185,31 +217,66 @@ func (c *Controller) unseal(key string) error {
185217
ssecret := obj.(*ssv1alpha1.SealedSecret)
186218
log.Printf("Updating %s", key)
187219

188-
secret, err := c.attemptUnseal(ssecret)
220+
newSecret, err := c.attemptUnseal(ssecret)
189221
if err != nil {
222+
c.recorder.Eventf(ssecret, corev1.EventTypeWarning, ErrUnsealFailed, "Failed to unseal: %v", err)
190223
return err
191224
}
192225

193-
_, err = c.sclient.Secrets(ssecret.GetObjectMeta().GetNamespace()).Create(secret)
194-
if err == nil {
195-
// Secret successfully created
196-
return nil
226+
secret, err := c.sclient.Secrets(ssecret.GetObjectMeta().GetNamespace()).Get(newSecret.GetObjectMeta().GetName(), metav1.GetOptions{})
227+
if errors.IsNotFound(err) {
228+
secret, err = c.sclient.Secrets(ssecret.GetObjectMeta().GetNamespace()).Create(newSecret)
197229
}
198-
if !errors.IsAlreadyExists(err) {
199-
// Error wasn't already exists so is real error
230+
if err != nil {
231+
c.recorder.Event(ssecret, corev1.EventTypeWarning, ErrUpdateFailed, err.Error())
200232
return err
201233
}
202234

203-
// Secret already exists so update it in place with new data/owner reference
204-
updatedSecret, err := c.updateSecret(secret)
235+
if !metav1.IsControlledBy(secret, ssecret) {
236+
msg := fmt.Sprintf("Resource %q already exists and is not managed by SealedSecret", secret.Name)
237+
c.recorder.Event(ssecret, corev1.EventTypeWarning, ErrUpdateFailed, msg)
238+
return fmt.Errorf("failed update: %s", msg)
239+
}
240+
241+
origSecret := secret
242+
secret = secret.DeepCopy()
243+
244+
secret.Data = newSecret.Data
245+
secret.Type = newSecret.Type
246+
secret.ObjectMeta.Annotations = newSecret.ObjectMeta.Annotations
247+
secret.ObjectMeta.Labels = newSecret.ObjectMeta.Labels
248+
249+
if !apiequality.Semantic.DeepEqual(origSecret, secret) {
250+
secret, err = c.sclient.Secrets(ssecret.GetObjectMeta().GetNamespace()).Update(secret)
251+
if err != nil {
252+
c.recorder.Event(ssecret, corev1.EventTypeWarning, ErrUpdateFailed, err.Error())
253+
return err
254+
}
255+
}
256+
257+
err = c.updateSealedSecretStatus(ssecret, secret)
205258
if err != nil {
206-
return fmt.Errorf("failed to update existing secret: %s", err)
259+
// Non-fatal. Log and continue.
260+
log.Printf("Error updating SealedSecret %s status: %v", key, err)
207261
}
208-
_, err = c.sclient.Secrets(ssecret.GetObjectMeta().GetNamespace()).Update(updatedSecret)
262+
263+
c.recorder.Event(ssecret, corev1.EventTypeNormal, SuccessUnsealed, "SealedSecret unsealed successfully")
264+
return nil
265+
}
266+
267+
func (c *Controller) updateSealedSecretStatus(ssecret *ssv1alpha1.SealedSecret, secret *corev1.Secret) error {
268+
ssecret = ssecret.DeepCopy()
269+
270+
ssecret.Status.ObservedGeneration = secret.ObjectMeta.Generation
271+
272+
// TODO: Use UpdateStatus when k8s CustomResourceSubresources
273+
// feature is widespread.
274+
var err error
275+
ssecret, err = c.ssclient.SealedSecrets(ssecret.GetObjectMeta().GetNamespace()).Update(ssecret)
209276
return err
210277
}
211278

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

225-
func (c *Controller) updateOwnerReferences(existing, new *apiv1.Secret) {
292+
func (c *Controller) updateOwnerReferences(existing, new *corev1.Secret) {
226293
ownerRefs := existing.GetOwnerReferences()
227294

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

291-
func (c *Controller) attemptUnseal(ss *ssv1alpha1.SealedSecret) (*apiv1.Secret, error) {
358+
func (c *Controller) attemptUnseal(ss *ssv1alpha1.SealedSecret) (*corev1.Secret, error) {
292359
return attemptUnseal(ss, c.keyRegistry)
293360
}
294361

295-
func attemptUnseal(ss *ssv1alpha1.SealedSecret, keyRegistry *KeyRegistry) (*apiv1.Secret, error) {
362+
func attemptUnseal(ss *ssv1alpha1.SealedSecret, keyRegistry *KeyRegistry) (*corev1.Secret, error) {
296363
for _, privKey := range keyRegistry.privateKeys {
297364
if secret, err := ss.Unseal(scheme.Codecs, privKey); err == nil {
298365
return secret, nil

cmd/controller/main.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ func main2() error {
134134
return err
135135
}
136136

137-
ssclient, err := sealedsecrets.NewForConfig(config)
137+
ssclientset, err := sealedsecrets.NewForConfig(config)
138138
if err != nil {
139139
return err
140140
}
@@ -158,8 +158,8 @@ func main2() error {
158158

159159
initKeyGenSignalListener(trigger)
160160

161-
ssinformer := ssinformers.NewSharedInformerFactory(ssclient, 0)
162-
controller := NewController(clientset, ssinformer, keyRegistry)
161+
ssinformer := ssinformers.NewSharedInformerFactory(ssclientset, 0)
162+
controller := NewController(clientset, ssclientset, ssinformer, keyRegistry)
163163

164164
stop := make(chan struct{})
165165
defer close(stop)

go.mod

+1-3
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,7 @@ require (
2020
github.com/hashicorp/golang-lru v0.0.0-20160813221303-0a025b7e63ad // indirect
2121
github.com/howeyc/gopass v0.0.0-20170109162249-bf9dde6d0d2c // indirect
2222
github.com/imdario/mergo v0.0.0-20170620104701-e3000cb3d28c // indirect
23-
github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3 // indirect
2423
github.com/kr/pretty v0.1.0 // indirect
25-
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
2624
github.com/modern-go/reflect2 v1.0.1 // indirect
2725
github.com/onsi/ginkgo v0.0.0-20180119174237-747514b53ddd
2826
github.com/onsi/gomega v0.0.0-20180205174834-a9c79f175573
@@ -39,8 +37,8 @@ require (
3937
google.golang.org/appengine v0.0.0-20170801183137-c5a90ac045b7 // indirect
4038
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
4139
gopkg.in/inf.v0 v0.9.0 // indirect
42-
gopkg.in/yaml.v2 v2.0.0 // indirect
4340
k8s.io/api v0.0.0-20180828232432-12444147eb11
4441
k8s.io/apimachinery v0.0.0-20180619225948-e386b2658ed2
4542
k8s.io/client-go v0.0.0-20180817174322-745ca8300397
43+
k8s.io/kube-openapi v0.0.0-20190709113604-33be087ad058 // indirect
4644
)

0 commit comments

Comments
 (0)