open-cluster-management.io/governance-policy-propagator@v0.13.0/controllers/encryptionkeys/encryptionkeys_controller_test.go (about) 1 // Copyright (c) 2022 Red Hat, Inc. 2 // Copyright Contributors to the Open Cluster Management project 3 4 package encryptionkeys 5 6 import ( 7 "bytes" 8 "context" 9 "crypto/rand" 10 "errors" 11 "fmt" 12 "strings" 13 "testing" 14 "time" 15 16 . "github.com/onsi/ginkgo/v2" 17 . "github.com/onsi/gomega" 18 corev1 "k8s.io/api/core/v1" 19 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 20 k8sruntime "k8s.io/apimachinery/pkg/runtime" 21 "k8s.io/apimachinery/pkg/types" 22 clientgoscheme "k8s.io/client-go/kubernetes/scheme" 23 ctrl "sigs.k8s.io/controller-runtime" 24 "sigs.k8s.io/controller-runtime/pkg/client" 25 "sigs.k8s.io/controller-runtime/pkg/client/fake" 26 "sigs.k8s.io/controller-runtime/pkg/reconcile" 27 28 v1 "open-cluster-management.io/governance-policy-propagator/api/v1" 29 "open-cluster-management.io/governance-policy-propagator/controllers/common" 30 "open-cluster-management.io/governance-policy-propagator/controllers/propagator" 31 ) 32 33 const ( 34 keySize = 256 35 clusterName = "local-cluster" 36 day = time.Hour * 24 37 ) 38 39 type erroringFakeClient struct { 40 client.Client 41 GetError bool 42 ListError bool 43 PatchError bool 44 UpdateError bool 45 } 46 47 func (c *erroringFakeClient) Get( 48 ctx context.Context, key client.ObjectKey, obj client.Object, _ ...client.GetOption, 49 ) error { 50 if c.GetError { 51 return errors.New("some get error") 52 } 53 54 return c.Client.Get(ctx, key, obj) 55 } 56 57 func (c *erroringFakeClient) List(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error { 58 if c.ListError { 59 return errors.New("some list error") 60 } 61 62 return c.Client.List(ctx, list, opts...) 63 } 64 65 func (c *erroringFakeClient) Patch( 66 ctx context.Context, obj client.Object, patch client.Patch, opts ...client.PatchOption, 67 ) error { 68 if c.PatchError { 69 return errors.New("some patch error") 70 } 71 72 return c.Client.Patch(ctx, obj, patch, opts...) 73 } 74 75 func (c *erroringFakeClient) Update(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { 76 if c.UpdateError { 77 return errors.New("some update error") 78 } 79 80 return c.Client.Update(ctx, obj, opts...) 81 } 82 83 func generateSecret() *corev1.Secret { 84 key := make([]byte, keySize/8) 85 _, err := rand.Read(key) 86 Expect(err).ToNot(HaveOccurred()) 87 88 encryptionSecret := &corev1.Secret{ 89 ObjectMeta: metav1.ObjectMeta{ 90 Name: propagator.EncryptionKeySecret, 91 Namespace: clusterName, 92 }, 93 Data: map[string][]byte{"key": key}, 94 } 95 96 prevKey := make([]byte, keySize/8) 97 _, err = rand.Read(prevKey) 98 Expect(err).ToNot(HaveOccurred()) 99 100 encryptionSecret.Data["previousKey"] = prevKey 101 102 return encryptionSecret 103 } 104 105 func generatePolicies() []client.Object { 106 return []client.Object{ 107 client.Object( 108 &v1.Policy{ 109 ObjectMeta: metav1.ObjectMeta{ 110 Name: "default.policy-one", 111 Namespace: clusterName, 112 Annotations: map[string]string{propagator.IVAnnotation: "7cznVUq5SXEE4RMZNkGOrQ=="}, 113 Labels: map[string]string{common.RootPolicyLabel: "default.policy-one"}, 114 }, 115 }, 116 ), 117 client.Object( 118 &v1.Policy{ 119 ObjectMeta: metav1.ObjectMeta{ 120 Name: "policy-one", 121 Namespace: "default", 122 Annotations: map[string]string{}, 123 }, 124 }, 125 ), 126 client.Object( 127 &v1.Policy{ 128 ObjectMeta: metav1.ObjectMeta{ 129 Name: "default.policy-two", 130 Namespace: clusterName, 131 Annotations: map[string]string{}, 132 Labels: map[string]string{common.RootPolicyLabel: "default.policy-two"}, 133 }, 134 }, 135 ), 136 client.Object( 137 &v1.Policy{ 138 ObjectMeta: metav1.ObjectMeta{ 139 Name: "policy-two", 140 Namespace: "default", 141 Annotations: map[string]string{}, 142 }, 143 }, 144 ), 145 client.Object( 146 &v1.Policy{ 147 ObjectMeta: metav1.ObjectMeta{ 148 Name: "default.policy-three", 149 Namespace: "some-other-cluster", 150 Annotations: map[string]string{propagator.IVAnnotation: "7cznVUq5SXEE4RMZNkGOrQ=="}, 151 Labels: map[string]string{common.RootPolicyLabel: "default.policy-three"}, 152 }, 153 }, 154 ), 155 client.Object( 156 &v1.Policy{ 157 ObjectMeta: metav1.ObjectMeta{ 158 Name: "policy-three", 159 Namespace: "default", 160 Annotations: map[string]string{}, 161 }, 162 }, 163 ), 164 client.Object( 165 &v1.Policy{ 166 ObjectMeta: metav1.ObjectMeta{ 167 Name: "default.policy-four", 168 Namespace: clusterName, 169 Annotations: map[string]string{propagator.IVAnnotation: "7cznVUq5SXEE4RMZNkGOrQ=="}, 170 Labels: map[string]string{common.RootPolicyLabel: "default.policy-four"}, 171 }, 172 }, 173 ), 174 client.Object( 175 &v1.Policy{ 176 ObjectMeta: metav1.ObjectMeta{ 177 Name: "policy-four", 178 Namespace: "default", 179 Annotations: map[string]string{}, 180 }, 181 }, 182 ), 183 } 184 } 185 186 func getRequeueAfterDays(result reconcile.Result) int { 187 return int(result.RequeueAfter.Round(day) / (day)) 188 } 189 190 func getReconciler(encryptionSecret *corev1.Secret) *EncryptionKeysReconciler { 191 policies := generatePolicies() 192 193 scheme := k8sruntime.NewScheme() 194 err := clientgoscheme.AddToScheme(scheme) 195 Expect(err).ToNot(HaveOccurred()) 196 err = v1.AddToScheme(scheme) 197 Expect(err).ToNot(HaveOccurred()) 198 199 builder := fake.NewClientBuilder().WithObjects(policies...).WithScheme(scheme) 200 201 if encryptionSecret != nil { 202 builder = builder.WithObjects(encryptionSecret) 203 } 204 205 client := builder.Build() 206 207 return &EncryptionKeysReconciler{ 208 Client: client, 209 KeyRotationDays: 30, 210 Scheme: scheme, 211 } 212 } 213 214 func assertTriggerUpdate(r *EncryptionKeysReconciler) { 215 policyList := v1.PolicyList{} 216 err := r.List(context.TODO(), &policyList) 217 Expect(err).ToNot(HaveOccurred()) 218 219 for _, policy := range policyList.Items { 220 annotation := policy.Annotations[propagator.TriggerUpdateAnnotation] 221 222 if policy.ObjectMeta.Name == "policy-one" || policy.ObjectMeta.Name == "policy-four" { 223 expectedPrefix := fmt.Sprintf("rotate-key-%s-", clusterName) 224 Expect(strings.HasPrefix(annotation, expectedPrefix)).To(BeTrue()) 225 } else { 226 Expect(annotation).To(Equal("")) 227 } 228 } 229 } 230 231 func assertNoTriggerUpdate(r *EncryptionKeysReconciler) { 232 policyList := v1.PolicyList{} 233 err := r.List(context.TODO(), &policyList) 234 Expect(err).ToNot(HaveOccurred()) 235 236 for _, policy := range policyList.Items { 237 annotation := policy.Annotations[propagator.TriggerUpdateAnnotation] 238 Expect(annotation).To(Equal("")) 239 } 240 } 241 242 func TestReconcileRotateKey(t *testing.T) { 243 t.Parallel() 244 RegisterFailHandler(Fail) 245 246 tests := []struct{ Annotation string }{{""}, {"2020-04-15T01:02:03Z"}, {"not-a-timestamp"}} 247 248 for _, test := range tests { 249 test := test 250 251 t.Run( 252 fmt.Sprintf(`annotation="%s"`, test.Annotation), 253 func(t *testing.T) { 254 t.Parallel() 255 256 encryptionSecret := generateSecret() 257 if test.Annotation != "" { 258 annotations := map[string]string{propagator.LastRotatedAnnotation: test.Annotation} 259 encryptionSecret.SetAnnotations(annotations) 260 } 261 originalKey := encryptionSecret.Data["key"] 262 263 r := getReconciler(encryptionSecret) 264 265 secretID := types.NamespacedName{ 266 Namespace: clusterName, Name: propagator.EncryptionKeySecret, 267 } 268 request := ctrl.Request{NamespacedName: secretID} 269 result, err := r.Reconcile(context.TODO(), request) 270 271 Expect(err).ToNot(HaveOccurred()) 272 Expect(result.Requeue).To(BeFalse()) 273 Expect(getRequeueAfterDays(result)).To(Equal(30)) 274 275 err = r.Get(context.TODO(), secretID, encryptionSecret) 276 Expect(err).ToNot(HaveOccurred()) 277 Expect(bytes.Equal(encryptionSecret.Data["key"], originalKey)).To(BeFalse()) 278 Expect(bytes.Equal(encryptionSecret.Data["previousKey"], originalKey)).To(BeTrue()) 279 280 assertTriggerUpdate(r) 281 }, 282 ) 283 } 284 } 285 286 func TestReconcileNoRotation(t *testing.T) { 287 t.Parallel() 288 RegisterFailHandler(Fail) 289 290 encryptionSecret := generateSecret() 291 now := time.Now().UTC().Format(time.RFC3339) 292 293 encryptionSecret.SetAnnotations(map[string]string{propagator.LastRotatedAnnotation: now}) 294 295 originalKey := encryptionSecret.Data["key"] 296 297 r := getReconciler(encryptionSecret) 298 299 secretID := types.NamespacedName{ 300 Namespace: clusterName, Name: propagator.EncryptionKeySecret, 301 } 302 request := ctrl.Request{NamespacedName: secretID} 303 result, err := r.Reconcile(context.TODO(), request) 304 305 Expect(err).ToNot(HaveOccurred()) 306 Expect(getRequeueAfterDays(result)).To(Equal(30)) 307 308 err = r.Get(context.TODO(), secretID, encryptionSecret) 309 Expect(err).ToNot(HaveOccurred()) 310 Expect(bytes.Equal(encryptionSecret.Data["key"], originalKey)).To(BeTrue()) 311 Expect(bytes.Equal(encryptionSecret.Data["previousKey"], originalKey)).To(BeFalse()) 312 313 assertNoTriggerUpdate(r) 314 } 315 316 func TestReconcileNotFound(t *testing.T) { 317 t.Parallel() 318 RegisterFailHandler(Fail) 319 320 r := getReconciler(nil) 321 322 secretID := types.NamespacedName{ 323 Namespace: clusterName, Name: propagator.EncryptionKeySecret, 324 } 325 request := ctrl.Request{NamespacedName: secretID} 326 result, err := r.Reconcile(context.TODO(), request) 327 328 Expect(err).ToNot(HaveOccurred()) 329 Expect(result.RequeueAfter).To(Equal(time.Duration(0))) 330 331 policyList := v1.PolicyList{} 332 err = r.List(context.TODO(), &policyList) 333 Expect(err).ToNot(HaveOccurred()) 334 335 for _, policy := range policyList.Items { 336 annotation := policy.Annotations[propagator.TriggerUpdateAnnotation] 337 Expect(annotation).To(Equal("")) 338 } 339 } 340 341 func TestReconcileManualRotation(t *testing.T) { 342 t.Parallel() 343 RegisterFailHandler(Fail) 344 345 encryptionSecret := generateSecret() 346 annotations := map[string]string{ 347 "policy.open-cluster-management.io/disable-rotation": "true", 348 propagator.LastRotatedAnnotation: "2020-04-15T01:02:03Z", 349 } 350 encryptionSecret.SetAnnotations(annotations) 351 originalKey := encryptionSecret.Data["key"] 352 originalPrevKey := encryptionSecret.Data["previousKey"] 353 354 r := getReconciler(encryptionSecret) 355 356 secretID := types.NamespacedName{ 357 Namespace: clusterName, Name: propagator.EncryptionKeySecret, 358 } 359 request := ctrl.Request{NamespacedName: secretID} 360 result, err := r.Reconcile(context.TODO(), request) 361 362 Expect(err).ToNot(HaveOccurred()) 363 Expect(result.Requeue).To(BeFalse()) 364 Expect(result.RequeueAfter).To(Equal(time.Duration(0))) 365 366 err = r.Get(context.TODO(), secretID, encryptionSecret) 367 Expect(err).ToNot(HaveOccurred()) 368 Expect(bytes.Equal(encryptionSecret.Data["key"], originalKey)).To(BeTrue()) 369 Expect(bytes.Equal(encryptionSecret.Data["previousKey"], originalPrevKey)).To(BeTrue()) 370 371 assertTriggerUpdate(r) 372 } 373 374 func TestReconcileInvalidKey(t *testing.T) { 375 t.Parallel() 376 RegisterFailHandler(Fail) 377 378 encryptionSecret := generateSecret() 379 encryptionSecret.Data["key"] = []byte("not-a-key") 380 originalKey := encryptionSecret.Data["key"] 381 382 now := time.Now().UTC().Format(time.RFC3339) 383 384 encryptionSecret.SetAnnotations(map[string]string{propagator.LastRotatedAnnotation: now}) 385 386 r := getReconciler(encryptionSecret) 387 388 secretID := types.NamespacedName{ 389 Namespace: clusterName, Name: propagator.EncryptionKeySecret, 390 } 391 request := ctrl.Request{NamespacedName: secretID} 392 result, err := r.Reconcile(context.TODO(), request) 393 394 Expect(err).ToNot(HaveOccurred()) 395 Expect(result.Requeue).To(BeFalse()) 396 Expect(getRequeueAfterDays(result)).To(Equal(30)) 397 398 err = r.Get(context.TODO(), secretID, encryptionSecret) 399 Expect(err).ToNot(HaveOccurred()) 400 Expect(bytes.Equal(encryptionSecret.Data["key"], originalKey)).To(BeFalse()) 401 Expect(encryptionSecret.Data["previousKey"]).To(BeEmpty()) 402 403 assertTriggerUpdate(r) 404 } 405 406 func TestReconcileInvalidPreviousKey(t *testing.T) { 407 t.Parallel() 408 RegisterFailHandler(Fail) 409 410 encryptionSecret := generateSecret() 411 encryptionSecret.Data["previousKey"] = []byte("not-a-key") 412 originalKey := encryptionSecret.Data["key"] 413 414 now := time.Now().UTC().Format(time.RFC3339) 415 416 encryptionSecret.SetAnnotations(map[string]string{propagator.LastRotatedAnnotation: now}) 417 418 r := getReconciler(encryptionSecret) 419 420 secretID := types.NamespacedName{ 421 Namespace: clusterName, Name: propagator.EncryptionKeySecret, 422 } 423 request := ctrl.Request{NamespacedName: secretID} 424 result, err := r.Reconcile(context.TODO(), request) 425 426 Expect(err).ToNot(HaveOccurred()) 427 Expect(result.Requeue).To(BeFalse()) 428 Expect(getRequeueAfterDays(result)).To(Equal(30)) 429 430 err = r.Get(context.TODO(), secretID, encryptionSecret) 431 Expect(err).ToNot(HaveOccurred()) 432 Expect(bytes.Equal(encryptionSecret.Data["key"], originalKey)).To(BeTrue()) 433 Expect(encryptionSecret.Data["previousKey"]).To(BeEmpty()) 434 435 assertNoTriggerUpdate(r) 436 } 437 438 func TestReconcileSecretNotFiltered(t *testing.T) { 439 t.Parallel() 440 RegisterFailHandler(Fail) 441 442 randomSecret := &corev1.Secret{ 443 ObjectMeta: metav1.ObjectMeta{ 444 Name: "random-secret", 445 Namespace: clusterName, 446 }, 447 } 448 449 r := getReconciler(randomSecret) 450 451 secretID := types.NamespacedName{Namespace: clusterName, Name: "random-secret"} 452 request := ctrl.Request{NamespacedName: secretID} 453 result, err := r.Reconcile(context.TODO(), request) 454 455 Expect(err).ToNot(HaveOccurred()) 456 Expect(result.Requeue).To(BeFalse()) 457 Expect(result.RequeueAfter).To(Equal(time.Duration(0))) 458 459 assertNoTriggerUpdate(r) 460 } 461 462 func TestReconcileAPIFails(t *testing.T) { 463 t.Parallel() 464 RegisterFailHandler(Fail) 465 466 tests := []struct { 467 ExpectedRotation bool 468 GetError bool 469 ListError bool 470 Name string 471 PatchError bool 472 UpdateError bool 473 }{ 474 {ExpectedRotation: false, GetError: true, Name: "get-secret"}, 475 {ExpectedRotation: false, UpdateError: true, Name: "update-secret"}, 476 {ExpectedRotation: true, ListError: true, Name: "list-policy"}, 477 {ExpectedRotation: true, PatchError: true, Name: "patch-policy"}, 478 } 479 480 for _, test := range tests { 481 test := test 482 t.Run( 483 test.Name, 484 func(t *testing.T) { 485 t.Parallel() 486 encryptionSecret := generateSecret() 487 originalKey := encryptionSecret.Data["key"] 488 r := getReconciler(encryptionSecret) 489 erroringClient := erroringFakeClient{ 490 Client: r.Client, 491 GetError: test.GetError, 492 ListError: test.ListError, 493 PatchError: test.PatchError, 494 UpdateError: test.UpdateError, 495 } 496 r.Client = &erroringClient 497 498 secretID := types.NamespacedName{Namespace: clusterName, Name: propagator.EncryptionKeySecret} 499 request := ctrl.Request{NamespacedName: secretID} 500 result, err := r.Reconcile(context.TODO(), request) 501 502 if !test.ExpectedRotation { 503 Expect(err).Should(HaveOccurred()) 504 Expect(result.RequeueAfter).Should(Equal(time.Duration(0))) 505 } else { 506 Expect(err).ShouldNot(HaveOccurred()) 507 Expect(result.RequeueAfter).ShouldNot(Equal(time.Duration(0))) 508 } 509 510 Expect(result.Requeue).To(BeFalse()) 511 512 // Revert back the fake client to verify the secret and that no policy updates were triggered 513 r.Client = erroringClient.Client 514 515 err = r.Get(context.TODO(), client.ObjectKeyFromObject(encryptionSecret), encryptionSecret) 516 Expect(err).ShouldNot(HaveOccurred()) 517 518 if test.ExpectedRotation { 519 Expect(bytes.Equal(originalKey, encryptionSecret.Data["key"])).Should(BeFalse()) 520 } else { 521 Expect(bytes.Equal(originalKey, encryptionSecret.Data["key"])).Should(BeTrue()) 522 } 523 524 // Revert back the fake client to verify no policy updates were triggered 525 r.Client = erroringClient.Client 526 assertNoTriggerUpdate(r) 527 }, 528 ) 529 } 530 }