1
16
17 package encryptionctl
18
19 import (
20 "context"
21 "fmt"
22 "strings"
23 "time"
24
25 "k8s.io/apimachinery/pkg/runtime"
26 "sigs.k8s.io/controller-runtime/pkg/event"
27 "sigs.k8s.io/controller-runtime/pkg/predicate"
28
29 corev1 "k8s.io/api/core/v1"
30 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
31 ctrl "sigs.k8s.io/controller-runtime"
32 "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
33
34 edgeConditions "edge-infra.dev/pkg/k8s/runtime/conditions"
35
36 "sigs.k8s.io/controller-runtime/pkg/client"
37 logger "sigs.k8s.io/controller-runtime/pkg/log"
38
39 "k8s.io/apimachinery/pkg/types"
40
41 "edge-infra.dev/pkg/k8s/runtime/controller/metrics"
42
43 "github.com/go-logr/logr"
44
45 api "edge-infra.dev/pkg/edge/iam/api/v1alpha1"
46 "edge-infra.dev/pkg/edge/iam/storage/database"
47 )
48
49
50
51 type EncryptionSecretReconciler struct {
52 client.Client
53 Scheme *runtime.Scheme
54
55 Name string
56
57
58 Metrics metrics.Metrics
59 }
60
61 const encryptionKeyPrefix = "id-encryption-key-"
62
63
64
65
66
67
68
69
70 func secretReconcilerPredicate() predicate.Predicate {
71 return predicate.Funcs{
72
73
74 UpdateFunc: func(e event.UpdateEvent) bool {
75 s, ok := e.ObjectNew.(*corev1.Secret)
76 if ok && e.ObjectNew.GetNamespace() == "edge-iam" &&
77 strings.HasPrefix(e.ObjectNew.GetName(), encryptionKeyPrefix) &&
78 !s.ObjectMeta.DeletionTimestamp.IsZero() {
79 return true
80 }
81 return false
82 },
83
84
85
86 CreateFunc: func(e event.CreateEvent) bool {
87 _, ok := e.Object.(*corev1.Secret)
88 if ok && e.Object.GetNamespace() == "edge-iam" &&
89 strings.HasPrefix(e.Object.GetName(), encryptionKeyPrefix) {
90 return true
91 }
92 return false
93 },
94 DeleteFunc: func(_ event.DeleteEvent) bool {
95 return false
96 },
97 }
98 }
99
100 func (r *EncryptionSecretReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
101 log := logger.FromContext(ctx)
102 finalizer := "finalizers.edge.ncr.com/encryption"
103
104
105 var provider = api.Provider{}
106 err := r.Get(ctx, types.NamespacedName{Namespace: req.Namespace, Name: "provider"}, &provider)
107 if err != nil {
108 log.Error(err, "Unable to get provider crd")
109 return ctrl.Result{}, err
110 }
111
112
113 var secret = corev1.Secret{}
114 err = r.Get(ctx, types.NamespacedName{Namespace: req.Namespace, Name: req.Name}, &secret)
115 if err != nil {
116 log.Error(err, "Unable to get encryption secret", "secret", req.Name)
117 return ctrl.Result{}, err
118 }
119
120
121
122 statusVersion, upToDate := CheckStatusVersion(provider)
123 if upToDate && secret.ObjectMeta.DeletionTimestamp.IsZero() {
124 log.Info("Databases are already up to date with provider's spec.encryption.version. Returning without reconciling encryption secret controller.")
125 return ctrl.Result{}, nil
126 }
127
128
129 if !secret.ObjectMeta.DeletionTimestamp.IsZero() {
130 if controllerutil.ContainsFinalizer(&secret, finalizer) {
131 if provider.Spec.Encryption.Version == "" {
132 err := r.DecryptDatabases(ctx, secret)
133 if err != nil {
134 log.Error(err, "Unable to decrypt databases")
135 return ctrl.Result{}, err
136 }
137 }
138
139
140 controllerutil.RemoveFinalizer(&secret, finalizer)
141 if err := r.Update(ctx, &secret); err != nil {
142 log.Error(err, "Unable update secret to remove finalizer for deletion")
143 return ctrl.Result{}, err
144 }
145
146 log.Info("Removed Finalizer on secret", "secret", secret.Name)
147 }
148
149 } else {
150 controllerutil.AddFinalizer(&secret, finalizer)
151 err = r.Client.Update(ctx, &secret)
152 if err != nil {
153 log.Error(err, "Unable to update secret to include encryption finalizer")
154 return ctrl.Result{}, err
155 }
156
157
158 reconcileErr := r.reconcile(ctx, req, statusVersion, provider)
159 if reconcileErr != nil {
160 log.Error(reconcileErr, "reconciled with error")
161 return ctrl.Result{RequeueAfter: 5 * time.Second}, reconcileErr
162 }
163 }
164
165
166 if err := r.updateStatus(ctx, provider); err != nil {
167 log.Error(err, "unable to update status")
168 return ctrl.Result{Requeue: true}, err
169 }
170
171 return ctrl.Result{}, nil
172 }
173
174
175 func (r *EncryptionSecretReconciler) SetupWithManager(mgr ctrl.Manager) error {
176 return ctrl.NewControllerManagedBy(mgr).
177 For(&corev1.Secret{}).
178 WithEventFilter(secretReconcilerPredicate()).
179 Complete(r)
180 }
181
182
183 func (r *EncryptionSecretReconciler) reconcile(ctx context.Context, req ctrl.Request, statusVersion string, provider api.Provider) error {
184 log := logger.FromContext(ctx)
185 version := provider.Spec.Encryption.Version
186
187
188 newSecretName := encryptionKeyPrefix + version
189
190
191 var newSecret = corev1.Secret{}
192 err := r.Get(ctx, types.NamespacedName{Namespace: req.Namespace, Name: newSecretName}, &newSecret)
193 if err != nil {
194 log.Error(err, "Unable to get new encryption secret", newSecretName)
195 return err
196 }
197
198 newKey := newSecret.Data["key"]
199
200
201 if statusVersion == "" {
202 err = encryptDatabases(ctx, log, newKey)
203 if err != nil {
204 log.Error(err, "Unable to encrypt databases")
205 return err
206 }
207 }
208
209
210 oldKey, rotate, err := r.rotateKeys(ctx, newSecretName)
211 if err != nil {
212 log.Error(err, "Unable to determine if we need to rotate databases")
213 return err
214 }
215
216 if rotate {
217 err = updateDatabases(ctx, log, oldKey, newKey)
218 if err != nil {
219 log.Error(err, "Failed to update dabases with new encryption key")
220 return err
221 }
222 }
223
224 log.Info("Databases successfully updated")
225 return nil
226 }
227
228
229 func updateDatabases(ctx context.Context, log logr.Logger, oldKey []byte, newKey []byte) error {
230 s, err := database.NewProviderStore(log)
231 if err != nil {
232 return err
233 }
234
235
236 err = s.RotateCouchEncryptionKey(ctx, oldKey, newKey)
237 if err != nil {
238 return err
239 }
240
241 log.Info("Successfully updated couchdb to use the new encryption key")
242 return nil
243 }
244
245
246 func encryptDatabases(ctx context.Context, log logr.Logger, newKey []byte) error {
247 s, err := database.NewProviderStore(log)
248 if err != nil {
249 return err
250 }
251
252
253 err = s.EncryptCouchDB(ctx, newKey)
254 if err != nil {
255 log.Error(err, "Unable to encrypt couchdb")
256 return err
257 }
258
259 log.Info("Successfully updated couchdb to be encrypted and use the new encryption key")
260 return nil
261 }
262
263
264 func (r *EncryptionSecretReconciler) updateStatus(ctx context.Context, provider api.Provider) error {
265 statusMessage := "successfully updated databases to version: " + provider.Spec.Encryption.Version
266
267 var p = api.Provider{}
268 if err := r.Get(ctx, types.NamespacedName{Namespace: "edge-iam", Name: "provider"}, &p); err != nil {
269 return err
270 }
271 gen := p.GetGeneration()
272 condition := metav1.Condition{
273 Type: "DatabaseUpdated",
274 Status: metav1.ConditionTrue,
275 Reason: "EncryptionRotationSucceeded",
276 Message: statusMessage,
277 ObservedGeneration: gen,
278 LastTransitionTime: metav1.Now(),
279 }
280
281 patch := client.MergeFrom(p.DeepCopy())
282
283 edgeConditions.Set(&p, &condition)
284
285 return r.Status().Patch(ctx, &p, patch)
286 }
287
288
289 func CheckStatusVersion(provider api.Provider) (string, bool) {
290 statusVersion := ""
291 prefix := "successfully updated databases to version: "
292
293 for i := range provider.Status.Conditions {
294 if provider.Status.Conditions[i].Reason == "EncryptionRotationSucceeded" {
295
296 statusVersion = strings.TrimSpace(provider.Status.Conditions[i].Message[len(prefix):])
297 }
298 }
299
300
301 return statusVersion, statusVersion == provider.Spec.Encryption.Version
302 }
303
304
305 func (r *EncryptionSecretReconciler) rotateKeys(ctx context.Context, newSecretName string) ([]byte, bool, error) {
306 log := logger.FromContext(ctx)
307
308
309 secretList := &corev1.SecretList{}
310 if err := r.Client.List(ctx, secretList, &client.ListOptions{Namespace: "edge-iam"}); err != nil {
311 log.Error(err, "Couldn't list secrets in the edge-iam namespace")
312 return []byte(""), false, err
313 }
314
315
316 for _, secret := range secretList.Items {
317 if strings.HasPrefix(secret.Name, "id-encryption-key") && secret.Name != newSecretName {
318 var oldSecret = corev1.Secret{}
319 err := r.Get(ctx, types.NamespacedName{Namespace: "edge-iam", Name: secret.Name}, &oldSecret)
320 if err != nil {
321 log.Error(err, fmt.Sprintf("Unable to get old secret %s", secret.Name))
322 return []byte(""), false, err
323 }
324
325 oldKey := oldSecret.Data["key"]
326 log.Info("Found key to rotate", "key", secret.Name)
327 return oldKey, true, nil
328 }
329 }
330
331 return []byte(""), false, nil
332 }
333
334
335 func decryptDatabases(ctx context.Context, oldKey []byte) error {
336 log := logger.FromContext(ctx)
337 s, err := database.NewProviderStore(log)
338 if err != nil {
339 return err
340 }
341
342 err = s.DecryptCouchDB(ctx, oldKey)
343 if err != nil {
344 log.Info("Unable to decrypt couch database", err)
345 return err
346 }
347 log.Info("Successfully decrypted couchdb")
348 return nil
349 }
350
351 func (r *EncryptionSecretReconciler) DecryptDatabases(ctx context.Context, secret corev1.Secret) error {
352 log := logger.FromContext(ctx)
353
354 key := secret.Data["key"]
355
356 err := decryptDatabases(ctx, key)
357 if err != nil {
358 log.Info("Error reverting databases to be unencrypted.")
359 return err
360 }
361
362 log.Info("Successfully decrypted databases.")
363 return nil
364 }
365
View as plain text