github.com/operator-framework/operator-lifecycle-manager@v0.30.0/pkg/controller/install/certresources_test.go (about) 1 package install 2 3 import ( 4 "fmt" 5 "reflect" 6 "testing" 7 "time" 8 9 "github.com/golang/mock/gomock" 10 "github.com/google/go-cmp/cmp" 11 "github.com/stretchr/testify/assert" 12 "github.com/stretchr/testify/require" 13 appsv1 "k8s.io/api/apps/v1" 14 corev1 "k8s.io/api/core/v1" 15 rbacv1 "k8s.io/api/rbac/v1" 16 "k8s.io/apimachinery/pkg/api/errors" 17 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 18 "k8s.io/apimachinery/pkg/runtime/schema" 19 corev1ac "k8s.io/client-go/applyconfigurations/core/v1" 20 rbacv1ac "k8s.io/client-go/applyconfigurations/rbac/v1" 21 22 "github.com/operator-framework/api/pkg/operators/v1alpha1" 23 "github.com/operator-framework/operator-lifecycle-manager/pkg/api/wrappers" 24 "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/certs" 25 listerfakes "github.com/operator-framework/operator-lifecycle-manager/pkg/fakes/client-go/listers" 26 "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorclient/operatorclientmocks" 27 "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorlister/operatorlisterfakes" 28 "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/ownerutil" 29 ) 30 31 func keyPair(t *testing.T, expiration time.Time) *certs.KeyPair { 32 p, err := certs.GenerateCA(expiration, Organization) 33 assert.NoError(t, err) 34 return p 35 } 36 37 func selector(t *testing.T, selector string) *metav1.LabelSelector { 38 s, err := metav1.ParseToLabelSelector(selector) 39 assert.NoError(t, err) 40 return s 41 } 42 43 var staticCerts *certs.KeyPair 44 45 // staticCertGenerator replaces the CertGenerator to get consistent keys for testing 46 func staticCertGenerator(notAfter time.Time, organization string, ca *certs.KeyPair, hosts []string) (*certs.KeyPair, error) { 47 if staticCerts != nil { 48 return staticCerts, nil 49 } 50 c, err := certs.CreateSignedServingPair(notAfter, organization, ca, hosts) 51 if err != nil { 52 return nil, err 53 } 54 staticCerts = c 55 return staticCerts, nil 56 } 57 58 type fakeState struct { 59 existingService *corev1.Service 60 getServiceError error 61 62 existingSecret *corev1.Secret 63 getSecretError error 64 65 existingRole *rbacv1.Role 66 getRoleError error 67 68 existingRoleBinding *rbacv1.RoleBinding 69 getRoleBindingError error 70 71 existingClusterRoleBinding *rbacv1.ClusterRoleBinding 72 getClusterRoleBindingError error 73 } 74 75 func newFakeLister(state fakeState) *operatorlisterfakes.FakeOperatorLister { 76 fakeLister := &operatorlisterfakes.FakeOperatorLister{} 77 fakeCoreV1Lister := &operatorlisterfakes.FakeCoreV1Lister{} 78 fakeRbacV1Lister := &operatorlisterfakes.FakeRbacV1Lister{} 79 fakeLister.CoreV1Returns(fakeCoreV1Lister) 80 fakeLister.RbacV1Returns(fakeRbacV1Lister) 81 82 fakeServiceLister := &listerfakes.FakeServiceLister{} 83 fakeCoreV1Lister.ServiceListerReturns(fakeServiceLister) 84 fakeServiceNamespacedLister := &listerfakes.FakeServiceNamespaceLister{} 85 fakeServiceLister.ServicesReturns(fakeServiceNamespacedLister) 86 fakeServiceNamespacedLister.GetReturns(state.existingService, state.getServiceError) 87 88 fakeSecretLister := &listerfakes.FakeSecretLister{} 89 fakeCoreV1Lister.SecretListerReturns(fakeSecretLister) 90 fakeSecretNamespacedLister := &listerfakes.FakeSecretNamespaceLister{} 91 fakeSecretLister.SecretsReturns(fakeSecretNamespacedLister) 92 fakeSecretNamespacedLister.GetReturns(state.existingSecret, state.getSecretError) 93 94 fakeRoleLister := &listerfakes.FakeRoleLister{} 95 fakeRbacV1Lister.RoleListerReturns(fakeRoleLister) 96 fakeRoleNamespacedLister := &listerfakes.FakeRoleNamespaceLister{} 97 fakeRoleLister.RolesReturns(fakeRoleNamespacedLister) 98 fakeRoleNamespacedLister.GetReturns(state.existingRole, state.getRoleError) 99 100 fakeRoleBindingLister := &listerfakes.FakeRoleBindingLister{} 101 fakeRbacV1Lister.RoleBindingListerReturns(fakeRoleBindingLister) 102 fakeRoleBindingNamespacedLister := &listerfakes.FakeRoleBindingNamespaceLister{} 103 fakeRoleBindingLister.RoleBindingsReturns(fakeRoleBindingNamespacedLister) 104 fakeRoleBindingNamespacedLister.GetReturns(state.existingRoleBinding, state.getRoleBindingError) 105 106 fakeClusterRoleBindingLister := &listerfakes.FakeClusterRoleBindingLister{} 107 fakeRbacV1Lister.ClusterRoleBindingListerReturns(fakeClusterRoleBindingLister) 108 fakeClusterRoleBindingLister.GetReturns(state.existingClusterRoleBinding, state.getClusterRoleBindingError) 109 110 return fakeLister 111 } 112 113 func TestInstallCertRequirementsForDeployment(t *testing.T) { 114 owner := ownerutil.Owner(&v1alpha1.ClusterServiceVersion{ 115 TypeMeta: metav1.TypeMeta{ 116 Kind: v1alpha1.ClusterServiceVersionKind, 117 APIVersion: v1alpha1.ClusterServiceVersionAPIVersion, 118 }, 119 ObjectMeta: metav1.ObjectMeta{ 120 Name: "owner", 121 Namespace: "test-namespace", 122 UID: "123-uid", 123 }, 124 }) 125 ca := keyPair(t, time.Now().Add(time.Hour)) 126 caPEM, _, err := ca.ToPEM() 127 assert.NoError(t, err) 128 caHash := certs.PEMSHA256(caPEM) 129 type fields struct { 130 owner ownerutil.Owner 131 previousStrategy Strategy 132 templateAnnotations map[string]string 133 initializers DeploymentInitializerFuncChain 134 apiServiceDescriptions []certResource 135 webhookDescriptions []certResource 136 } 137 type args struct { 138 deploymentName string 139 ca *certs.KeyPair 140 rotateAt time.Time 141 depSpec appsv1.DeploymentSpec 142 ports []corev1.ServicePort 143 } 144 145 type expectedExternalFunc func(clientInterface *operatorclientmocks.MockClientInterface, fakeLister *operatorlisterfakes.FakeOperatorLister, namespace string, args args) 146 tests := []struct { 147 name string 148 mockExternal expectedExternalFunc 149 state fakeState 150 fields fields 151 args args 152 want *appsv1.DeploymentSpec 153 wantErr bool 154 }{ 155 { 156 name: "adds certs to deployment spec", 157 mockExternal: func(mockOpClient *operatorclientmocks.MockClientInterface, fakeLister *operatorlisterfakes.FakeOperatorLister, namespace string, args args) { 158 service := corev1.Service{ 159 ObjectMeta: metav1.ObjectMeta{ 160 Name: "test-service", 161 Labels: map[string]string{OLMManagedLabelKey: OLMManagedLabelValue}, 162 OwnerReferences: []metav1.OwnerReference{ 163 ownerutil.NonBlockingOwner(&v1alpha1.ClusterServiceVersion{}), 164 }, 165 }, 166 Spec: corev1.ServiceSpec{ 167 Ports: args.ports, 168 Selector: selector(t, "test=label").MatchLabels, 169 }, 170 } 171 172 portsApplyConfig := []*corev1ac.ServicePortApplyConfiguration{} 173 for _, p := range args.ports { 174 ac := corev1ac.ServicePort(). 175 WithName(p.Name). 176 WithPort(p.Port). 177 WithTargetPort(p.TargetPort) 178 portsApplyConfig = append(portsApplyConfig, ac) 179 } 180 181 svcApplyConfig := corev1ac.Service(service.Name, service.Namespace). 182 WithSpec(corev1ac.ServiceSpec(). 183 WithPorts(portsApplyConfig...). 184 WithSelector(selector(t, "test=label").MatchLabels)). 185 WithOwnerReferences(ownerutil.NonBlockingOwnerApplyConfiguration(&v1alpha1.ClusterServiceVersion{})). 186 WithLabels(map[string]string{OLMManagedLabelKey: OLMManagedLabelValue}) 187 188 mockOpClient.EXPECT().ApplyService(svcApplyConfig, metav1.ApplyOptions{Force: true, FieldManager: "olm.install"}).Return(&service, nil) 189 190 hosts := []string{ 191 fmt.Sprintf("%s.%s", service.GetName(), namespace), 192 fmt.Sprintf("%s.%s.svc", service.GetName(), namespace), 193 } 194 servingPair, err := certGenerator.Generate(args.rotateAt, Organization, args.ca, hosts) 195 require.NoError(t, err) 196 197 // Create Secret for serving cert 198 certPEM, privPEM, err := servingPair.ToPEM() 199 require.NoError(t, err) 200 201 secret := &corev1.Secret{ 202 ObjectMeta: metav1.ObjectMeta{ 203 Name: "test-service-cert", 204 Namespace: namespace, 205 Annotations: map[string]string{OLMCAHashAnnotationKey: caHash}, 206 Labels: map[string]string{OLMManagedLabelKey: OLMManagedLabelValue}, 207 }, 208 Data: map[string][]byte{ 209 "tls.crt": certPEM, 210 "tls.key": privPEM, 211 OLMCAPEMKey: caPEM, 212 }, 213 Type: corev1.SecretTypeTLS, 214 } 215 mockOpClient.EXPECT().UpdateSecret(secret).Return(secret, nil) 216 217 secretRole := &rbacv1.Role{ 218 ObjectMeta: metav1.ObjectMeta{ 219 Name: secret.GetName(), 220 Namespace: namespace, 221 Labels: map[string]string{OLMManagedLabelKey: OLMManagedLabelValue}, 222 }, 223 Rules: []rbacv1.PolicyRule{ 224 { 225 Verbs: []string{"get"}, 226 APIGroups: []string{""}, 227 Resources: []string{"secrets"}, 228 ResourceNames: []string{secret.GetName()}, 229 }, 230 }, 231 } 232 mockOpClient.EXPECT().UpdateRole(secretRole).Return(secretRole, nil) 233 234 roleBinding := &rbacv1.RoleBinding{ 235 ObjectMeta: metav1.ObjectMeta{ 236 Name: secret.GetName(), 237 Namespace: namespace, 238 Labels: map[string]string{OLMManagedLabelKey: OLMManagedLabelValue}, 239 }, 240 Subjects: []rbacv1.Subject{ 241 { 242 Kind: "ServiceAccount", 243 APIGroup: "", 244 Name: "test-sa", 245 Namespace: namespace, 246 }, 247 }, 248 RoleRef: rbacv1.RoleRef{ 249 APIGroup: "rbac.authorization.k8s.io", 250 Kind: "Role", 251 Name: secretRole.GetName(), 252 }, 253 } 254 mockOpClient.EXPECT().UpdateRoleBinding(roleBinding).Return(roleBinding, nil) 255 256 authDelegatorClusterRoleBinding := &rbacv1.ClusterRoleBinding{ 257 ObjectMeta: metav1.ObjectMeta{ 258 Name: service.GetName() + "-system:auth-delegator", 259 Labels: map[string]string{OLMManagedLabelKey: OLMManagedLabelValue}, 260 }, 261 Subjects: []rbacv1.Subject{ 262 { 263 Kind: "ServiceAccount", 264 APIGroup: "", 265 Name: "test-sa", 266 Namespace: namespace, 267 }, 268 }, 269 RoleRef: rbacv1.RoleRef{ 270 APIGroup: "rbac.authorization.k8s.io", 271 Kind: "ClusterRole", 272 Name: "system:auth-delegator", 273 }, 274 } 275 276 crbLabels := map[string]string{OLMManagedLabelKey: OLMManagedLabelValue} 277 for key, val := range ownerutil.OwnerLabel(ownerutil.Owner(&v1alpha1.ClusterServiceVersion{}), owner.GetObjectKind().GroupVersionKind().Kind) { 278 crbLabels[key] = val 279 } 280 crbApplyConfig := rbacv1ac.ClusterRoleBinding(AuthDelegatorClusterRoleBindingName(service.GetName())). 281 WithSubjects(rbacv1ac.Subject(). 282 WithKind("ServiceAccount"). 283 WithAPIGroup(""). 284 WithName(args.depSpec.Template.Spec.ServiceAccountName). 285 WithNamespace("")). // Empty owner with no namespace 286 WithRoleRef(rbacv1ac.RoleRef(). 287 WithAPIGroup("rbac.authorization.k8s.io"). 288 WithKind("ClusterRole"). 289 WithName("system:auth-delegator")). 290 WithLabels(crbLabels) 291 mockOpClient.EXPECT().ApplyClusterRoleBinding(crbApplyConfig, metav1.ApplyOptions{Force: true, FieldManager: "olm.install"}).Return(authDelegatorClusterRoleBinding, nil) 292 293 authReaderRoleBinding := &rbacv1.RoleBinding{ 294 Subjects: []rbacv1.Subject{ 295 { 296 Kind: "ServiceAccount", 297 APIGroup: "", 298 Name: args.depSpec.Template.Spec.ServiceAccountName, 299 Namespace: namespace, 300 }, 301 }, 302 RoleRef: rbacv1.RoleRef{ 303 APIGroup: "rbac.authorization.k8s.io", 304 Kind: "Role", 305 Name: "extension-apiserver-authentication-reader", 306 }, 307 } 308 authReaderRoleBinding.SetName(service.GetName() + "-auth-reader") 309 authReaderRoleBinding.SetNamespace(KubeSystem) 310 authReaderRoleBinding.SetLabels(map[string]string{OLMManagedLabelKey: OLMManagedLabelValue}) 311 312 authReaderRoleBindingApplyConfig := rbacv1ac.RoleBinding(AuthReaderRoleBindingName(service.GetName()), KubeSystem). 313 WithLabels(map[string]string{OLMManagedLabelKey: OLMManagedLabelValue}). 314 WithSubjects(rbacv1ac.Subject(). 315 WithKind("ServiceAccount"). 316 WithAPIGroup(""). 317 WithName(args.depSpec.Template.Spec.ServiceAccountName). 318 WithNamespace(namespace)). 319 WithRoleRef(rbacv1ac.RoleRef(). 320 WithAPIGroup("rbac.authorization.k8s.io"). 321 WithKind("Role"). 322 WithName("extension-apiserver-authentication-reader")) 323 324 mockOpClient.EXPECT().ApplyRoleBinding(authReaderRoleBindingApplyConfig, metav1.ApplyOptions{Force: true, FieldManager: "olm.install"}).Return(authReaderRoleBinding, nil) 325 }, 326 state: fakeState{ 327 existingService: &corev1.Service{ 328 ObjectMeta: metav1.ObjectMeta{ 329 OwnerReferences: []metav1.OwnerReference{ 330 ownerutil.NonBlockingOwner(&v1alpha1.ClusterServiceVersion{}), 331 }, 332 }, 333 }, 334 existingSecret: &corev1.Secret{ 335 ObjectMeta: metav1.ObjectMeta{}, 336 }, 337 existingRole: &rbacv1.Role{ 338 ObjectMeta: metav1.ObjectMeta{}, 339 }, 340 existingRoleBinding: &rbacv1.RoleBinding{ 341 ObjectMeta: metav1.ObjectMeta{}, 342 }, 343 existingClusterRoleBinding: &rbacv1.ClusterRoleBinding{ 344 ObjectMeta: metav1.ObjectMeta{}, 345 }, 346 }, 347 fields: fields{ 348 owner: &v1alpha1.ClusterServiceVersion{}, 349 previousStrategy: nil, 350 templateAnnotations: nil, 351 initializers: nil, 352 apiServiceDescriptions: []certResource{}, 353 webhookDescriptions: []certResource{}, 354 }, 355 args: args{ 356 deploymentName: "test", 357 ca: ca, 358 rotateAt: time.Now().Add(time.Hour), 359 ports: []corev1.ServicePort{}, 360 depSpec: appsv1.DeploymentSpec{ 361 Selector: selector(t, "test=label"), 362 Template: corev1.PodTemplateSpec{ 363 Spec: corev1.PodSpec{ 364 ServiceAccountName: "test-sa", 365 }, 366 ObjectMeta: metav1.ObjectMeta{ 367 Annotations: map[string]string{ 368 "foo": "bar", 369 }, 370 }, 371 }, 372 }, 373 }, 374 want: &appsv1.DeploymentSpec{ 375 Selector: selector(t, "test=label"), 376 Template: corev1.PodTemplateSpec{ 377 ObjectMeta: metav1.ObjectMeta{ 378 Annotations: map[string]string{ 379 "foo": "bar", 380 OLMCAHashAnnotationKey: caHash}, 381 }, 382 Spec: corev1.PodSpec{ 383 ServiceAccountName: "test-sa", 384 Volumes: []corev1.Volume{ 385 { 386 Name: "apiservice-cert", 387 VolumeSource: corev1.VolumeSource{ 388 Secret: &corev1.SecretVolumeSource{ 389 SecretName: "test-service-cert", 390 Items: []corev1.KeyToPath{ 391 { 392 Key: "tls.crt", 393 Path: "apiserver.crt", 394 }, 395 { 396 Key: "tls.key", 397 Path: "apiserver.key", 398 }, 399 }, 400 }, 401 }, 402 }, 403 { 404 Name: "webhook-cert", 405 VolumeSource: corev1.VolumeSource{ 406 Secret: &corev1.SecretVolumeSource{ 407 SecretName: "test-service-cert", 408 Items: []corev1.KeyToPath{ 409 { 410 Key: "tls.crt", 411 Path: "tls.crt", 412 }, 413 { 414 Key: "tls.key", 415 Path: "tls.key", 416 }, 417 }, 418 }, 419 }, 420 }, 421 }, 422 }, 423 }, 424 }, 425 }, 426 { 427 name: "doesn't add duplicate service ownerrefs", 428 mockExternal: func(mockOpClient *operatorclientmocks.MockClientInterface, fakeLister *operatorlisterfakes.FakeOperatorLister, namespace string, args args) { 429 service := corev1.Service{ 430 ObjectMeta: metav1.ObjectMeta{ 431 Name: "test-service", 432 Namespace: owner.GetNamespace(), 433 Labels: map[string]string{OLMManagedLabelKey: OLMManagedLabelValue}, 434 OwnerReferences: []metav1.OwnerReference{ 435 ownerutil.NonBlockingOwner(owner), 436 }, 437 }, 438 Spec: corev1.ServiceSpec{ 439 Ports: args.ports, 440 Selector: selector(t, "test=label").MatchLabels, 441 }, 442 } 443 portsApplyConfig := []*corev1ac.ServicePortApplyConfiguration{} 444 for _, p := range args.ports { 445 ac := corev1ac.ServicePort(). 446 WithName(p.Name). 447 WithPort(p.Port). 448 WithTargetPort(p.TargetPort) 449 portsApplyConfig = append(portsApplyConfig, ac) 450 } 451 452 svcApplyConfig := corev1ac.Service(service.Name, service.Namespace). 453 WithSpec(corev1ac.ServiceSpec(). 454 WithPorts(portsApplyConfig...). 455 WithSelector(selector(t, "test=label").MatchLabels)). 456 WithOwnerReferences(ownerutil.NonBlockingOwnerApplyConfiguration(owner)). 457 WithLabels(map[string]string{OLMManagedLabelKey: OLMManagedLabelValue}) 458 459 mockOpClient.EXPECT().ApplyService(svcApplyConfig, metav1.ApplyOptions{Force: true, FieldManager: "olm.install"}).Return(&service, nil) 460 461 hosts := []string{ 462 fmt.Sprintf("%s.%s", service.GetName(), namespace), 463 fmt.Sprintf("%s.%s.svc", service.GetName(), namespace), 464 } 465 servingPair, err := certGenerator.Generate(args.rotateAt, Organization, args.ca, hosts) 466 require.NoError(t, err) 467 468 // Create Secret for serving cert 469 certPEM, privPEM, err := servingPair.ToPEM() 470 require.NoError(t, err) 471 472 secret := &corev1.Secret{ 473 ObjectMeta: metav1.ObjectMeta{ 474 Name: "test-service-cert", 475 Namespace: namespace, 476 Annotations: map[string]string{OLMCAHashAnnotationKey: caHash}, 477 Labels: map[string]string{OLMManagedLabelKey: OLMManagedLabelValue}, 478 }, 479 Data: map[string][]byte{ 480 "tls.crt": certPEM, 481 "tls.key": privPEM, 482 OLMCAPEMKey: caPEM, 483 }, 484 Type: corev1.SecretTypeTLS, 485 } 486 mockOpClient.EXPECT().UpdateSecret(secret).Return(secret, nil) 487 488 secretRole := &rbacv1.Role{ 489 ObjectMeta: metav1.ObjectMeta{ 490 Name: secret.GetName(), 491 Namespace: namespace, 492 Labels: map[string]string{OLMManagedLabelKey: OLMManagedLabelValue}, 493 }, 494 Rules: []rbacv1.PolicyRule{ 495 { 496 Verbs: []string{"get"}, 497 APIGroups: []string{""}, 498 Resources: []string{"secrets"}, 499 ResourceNames: []string{secret.GetName()}, 500 }, 501 }, 502 } 503 mockOpClient.EXPECT().UpdateRole(secretRole).Return(secretRole, nil) 504 505 roleBinding := &rbacv1.RoleBinding{ 506 ObjectMeta: metav1.ObjectMeta{ 507 Name: secret.GetName(), 508 Namespace: namespace, 509 Labels: map[string]string{OLMManagedLabelKey: OLMManagedLabelValue}, 510 }, 511 Subjects: []rbacv1.Subject{ 512 { 513 Kind: "ServiceAccount", 514 APIGroup: "", 515 Name: "test-sa", 516 Namespace: namespace, 517 }, 518 }, 519 RoleRef: rbacv1.RoleRef{ 520 APIGroup: "rbac.authorization.k8s.io", 521 Kind: "Role", 522 Name: secretRole.GetName(), 523 }, 524 } 525 mockOpClient.EXPECT().UpdateRoleBinding(roleBinding).Return(roleBinding, nil) 526 527 authDelegatorClusterRoleBinding := &rbacv1.ClusterRoleBinding{ 528 ObjectMeta: metav1.ObjectMeta{ 529 Name: service.GetName() + "-system:auth-delegator", 530 Labels: map[string]string{OLMManagedLabelKey: OLMManagedLabelValue}, 531 }, 532 Subjects: []rbacv1.Subject{ 533 { 534 Kind: "ServiceAccount", 535 APIGroup: "", 536 Name: "test-sa", 537 Namespace: namespace, 538 }, 539 }, 540 RoleRef: rbacv1.RoleRef{ 541 APIGroup: "rbac.authorization.k8s.io", 542 Kind: "ClusterRole", 543 Name: "system:auth-delegator", 544 }, 545 } 546 547 crbLabels := map[string]string{OLMManagedLabelKey: OLMManagedLabelValue} 548 for key, val := range ownerutil.OwnerLabel(owner, owner.GetObjectKind().GroupVersionKind().Kind) { 549 crbLabels[key] = val 550 } 551 crbApplyConfig := rbacv1ac.ClusterRoleBinding(service.GetName() + "-system:auth-delegator"). 552 WithSubjects(rbacv1ac.Subject(). 553 WithKind("ServiceAccount"). 554 WithAPIGroup(""). 555 WithName("test-sa"). 556 WithNamespace(namespace)). 557 WithRoleRef(rbacv1ac.RoleRef(). 558 WithAPIGroup("rbac.authorization.k8s.io"). 559 WithKind("ClusterRole"). 560 WithName("system:auth-delegator")). 561 WithLabels(crbLabels) 562 563 mockOpClient.EXPECT().ApplyClusterRoleBinding(crbApplyConfig, metav1.ApplyOptions{Force: true, FieldManager: "olm.install"}).Return(authDelegatorClusterRoleBinding, nil) 564 565 authReaderRoleBinding := &rbacv1.RoleBinding{ 566 Subjects: []rbacv1.Subject{ 567 { 568 Kind: "ServiceAccount", 569 APIGroup: "", 570 Name: args.depSpec.Template.Spec.ServiceAccountName, 571 Namespace: namespace, 572 }, 573 }, 574 RoleRef: rbacv1.RoleRef{ 575 APIGroup: "rbac.authorization.k8s.io", 576 Kind: "Role", 577 Name: "extension-apiserver-authentication-reader", 578 }, 579 } 580 authReaderRoleBinding.SetName(service.GetName() + "-auth-reader") 581 authReaderRoleBinding.SetNamespace(KubeSystem) 582 authReaderRoleBinding.SetLabels(map[string]string{OLMManagedLabelKey: OLMManagedLabelValue}) 583 584 authReaderRoleBindingApplyConfig := rbacv1ac.RoleBinding(AuthReaderRoleBindingName(service.GetName()), KubeSystem). 585 WithLabels(map[string]string{OLMManagedLabelKey: OLMManagedLabelValue}). 586 WithSubjects(rbacv1ac.Subject(). 587 WithKind("ServiceAccount"). 588 WithAPIGroup(""). 589 WithName(args.depSpec.Template.Spec.ServiceAccountName). 590 WithNamespace(namespace)). 591 WithRoleRef(rbacv1ac.RoleRef(). 592 WithAPIGroup("rbac.authorization.k8s.io"). 593 WithKind("Role"). 594 WithName("extension-apiserver-authentication-reader")) 595 596 mockOpClient.EXPECT().ApplyRoleBinding(authReaderRoleBindingApplyConfig, metav1.ApplyOptions{Force: true, FieldManager: "olm.install"}).Return(authReaderRoleBinding, nil) 597 }, 598 state: fakeState{ 599 existingService: &corev1.Service{ 600 ObjectMeta: metav1.ObjectMeta{ 601 Namespace: owner.GetNamespace(), 602 OwnerReferences: []metav1.OwnerReference{ 603 ownerutil.NonBlockingOwner(owner), 604 }, 605 }, 606 }, 607 existingSecret: &corev1.Secret{ 608 ObjectMeta: metav1.ObjectMeta{}, 609 }, 610 existingRole: &rbacv1.Role{ 611 ObjectMeta: metav1.ObjectMeta{}, 612 }, 613 existingRoleBinding: &rbacv1.RoleBinding{ 614 ObjectMeta: metav1.ObjectMeta{}, 615 }, 616 existingClusterRoleBinding: &rbacv1.ClusterRoleBinding{ 617 ObjectMeta: metav1.ObjectMeta{}, 618 }, 619 }, 620 fields: fields{ 621 owner: owner, 622 previousStrategy: nil, 623 templateAnnotations: nil, 624 initializers: nil, 625 apiServiceDescriptions: []certResource{}, 626 webhookDescriptions: []certResource{}, 627 }, 628 args: args{ 629 deploymentName: "test", 630 ca: ca, 631 rotateAt: time.Now().Add(time.Hour), 632 ports: []corev1.ServicePort{}, 633 depSpec: appsv1.DeploymentSpec{ 634 Selector: selector(t, "test=label"), 635 Template: corev1.PodTemplateSpec{ 636 Spec: corev1.PodSpec{ 637 ServiceAccountName: "test-sa", 638 }, 639 }, 640 }, 641 }, 642 want: &appsv1.DeploymentSpec{ 643 Selector: selector(t, "test=label"), 644 Template: corev1.PodTemplateSpec{ 645 ObjectMeta: metav1.ObjectMeta{ 646 Annotations: map[string]string{OLMCAHashAnnotationKey: caHash}, 647 }, 648 Spec: corev1.PodSpec{ 649 ServiceAccountName: "test-sa", 650 Volumes: []corev1.Volume{ 651 { 652 Name: "apiservice-cert", 653 VolumeSource: corev1.VolumeSource{ 654 Secret: &corev1.SecretVolumeSource{ 655 SecretName: "test-service-cert", 656 Items: []corev1.KeyToPath{ 657 { 658 Key: "tls.crt", 659 Path: "apiserver.crt", 660 }, 661 { 662 Key: "tls.key", 663 Path: "apiserver.key", 664 }, 665 }, 666 }, 667 }, 668 }, 669 { 670 Name: "webhook-cert", 671 VolumeSource: corev1.VolumeSource{ 672 Secret: &corev1.SecretVolumeSource{ 673 SecretName: "test-service-cert", 674 Items: []corev1.KeyToPath{ 675 { 676 Key: "tls.crt", 677 Path: "tls.crt", 678 }, 679 { 680 Key: "tls.key", 681 Path: "tls.key", 682 }, 683 }, 684 }, 685 }, 686 }, 687 }, 688 }, 689 }, 690 }, 691 }, 692 { 693 name: "labels an unlabelled secret if present; creates Service and ClusterRoleBinding if not existing", 694 mockExternal: func(mockOpClient *operatorclientmocks.MockClientInterface, fakeLister *operatorlisterfakes.FakeOperatorLister, namespace string, args args) { 695 service := corev1.Service{ 696 ObjectMeta: metav1.ObjectMeta{ 697 Name: "test-service", 698 Labels: map[string]string{OLMManagedLabelKey: OLMManagedLabelValue}, 699 OwnerReferences: []metav1.OwnerReference{ 700 ownerutil.NonBlockingOwner(&v1alpha1.ClusterServiceVersion{}), 701 }, 702 }, 703 Spec: corev1.ServiceSpec{ 704 Ports: args.ports, 705 Selector: selector(t, "test=label").MatchLabels, 706 }, 707 } 708 709 portsApplyConfig := []*corev1ac.ServicePortApplyConfiguration{} 710 for _, p := range args.ports { 711 ac := corev1ac.ServicePort(). 712 WithName(p.Name). 713 WithPort(p.Port). 714 WithTargetPort(p.TargetPort) 715 portsApplyConfig = append(portsApplyConfig, ac) 716 } 717 718 svcApplyConfig := corev1ac.Service(service.Name, service.Namespace). 719 WithSpec(corev1ac.ServiceSpec(). 720 WithPorts(portsApplyConfig...). 721 WithSelector(selector(t, "test=label").MatchLabels)). 722 WithOwnerReferences(ownerutil.NonBlockingOwnerApplyConfiguration(&v1alpha1.ClusterServiceVersion{})). 723 WithLabels(map[string]string{OLMManagedLabelKey: OLMManagedLabelValue}) 724 725 mockOpClient.EXPECT().ApplyService(svcApplyConfig, metav1.ApplyOptions{Force: true, FieldManager: "olm.install"}).Return(&service, nil) 726 727 hosts := []string{ 728 fmt.Sprintf("%s.%s", service.GetName(), namespace), 729 fmt.Sprintf("%s.%s.svc", service.GetName(), namespace), 730 } 731 servingPair, err := certGenerator.Generate(args.rotateAt, Organization, args.ca, hosts) 732 require.NoError(t, err) 733 734 // Create Secret for serving cert 735 certPEM, privPEM, err := servingPair.ToPEM() 736 require.NoError(t, err) 737 738 secret := &corev1.Secret{ 739 ObjectMeta: metav1.ObjectMeta{ 740 Name: "test-service-cert", 741 Namespace: namespace, 742 Annotations: map[string]string{OLMCAHashAnnotationKey: caHash}, 743 Labels: map[string]string{OLMManagedLabelKey: OLMManagedLabelValue}, 744 OwnerReferences: []metav1.OwnerReference{ 745 ownerutil.NonBlockingOwner(&v1alpha1.ClusterServiceVersion{}), 746 }, 747 }, 748 Data: map[string][]byte{ 749 "tls.crt": certPEM, 750 "tls.key": privPEM, 751 OLMCAPEMKey: caPEM, 752 }, 753 Type: corev1.SecretTypeTLS, 754 } 755 // secret already exists, but without label 756 mockOpClient.EXPECT().CreateSecret(secret).Return(nil, errors.NewAlreadyExists(schema.GroupResource{ 757 Group: "", 758 Resource: "secrets", 759 }, "test-service-cert")) 760 761 // update secret with label 762 mockOpClient.EXPECT().UpdateSecret(secret).Return(secret, nil) 763 764 secretRole := &rbacv1.Role{ 765 ObjectMeta: metav1.ObjectMeta{ 766 Name: secret.GetName(), 767 Namespace: namespace, 768 Labels: map[string]string{OLMManagedLabelKey: OLMManagedLabelValue}, 769 }, 770 Rules: []rbacv1.PolicyRule{ 771 { 772 Verbs: []string{"get"}, 773 APIGroups: []string{""}, 774 Resources: []string{"secrets"}, 775 ResourceNames: []string{secret.GetName()}, 776 }, 777 }, 778 } 779 mockOpClient.EXPECT().UpdateRole(secretRole).Return(secretRole, nil) 780 781 roleBinding := &rbacv1.RoleBinding{ 782 ObjectMeta: metav1.ObjectMeta{ 783 Name: secret.GetName(), 784 Namespace: namespace, 785 Labels: map[string]string{OLMManagedLabelKey: OLMManagedLabelValue}, 786 }, 787 Subjects: []rbacv1.Subject{ 788 { 789 Kind: "ServiceAccount", 790 APIGroup: "", 791 Name: "test-sa", 792 Namespace: namespace, 793 }, 794 }, 795 RoleRef: rbacv1.RoleRef{ 796 APIGroup: "rbac.authorization.k8s.io", 797 Kind: "Role", 798 Name: secretRole.GetName(), 799 }, 800 } 801 mockOpClient.EXPECT().UpdateRoleBinding(roleBinding).Return(roleBinding, nil) 802 803 authDelegatorClusterRoleBinding := &rbacv1.ClusterRoleBinding{ 804 ObjectMeta: metav1.ObjectMeta{ 805 Name: service.GetName() + "-system:auth-delegator", 806 Labels: map[string]string{OLMManagedLabelKey: OLMManagedLabelValue}, 807 }, 808 Subjects: []rbacv1.Subject{ 809 { 810 Kind: "ServiceAccount", 811 APIGroup: "", 812 Name: "test-sa", 813 Namespace: namespace, 814 }, 815 }, 816 RoleRef: rbacv1.RoleRef{ 817 APIGroup: "rbac.authorization.k8s.io", 818 Kind: "ClusterRole", 819 Name: "system:auth-delegator", 820 }, 821 } 822 crbLabels := map[string]string{OLMManagedLabelKey: OLMManagedLabelValue} 823 for key, val := range ownerutil.OwnerLabel(ownerutil.Owner(&v1alpha1.ClusterServiceVersion{}), owner.GetObjectKind().GroupVersionKind().Kind) { 824 crbLabels[key] = val 825 } 826 crbApplyConfig := rbacv1ac.ClusterRoleBinding(AuthDelegatorClusterRoleBindingName(service.GetName())). 827 WithSubjects(rbacv1ac.Subject().WithKind("ServiceAccount"). 828 WithAPIGroup(""). 829 WithName("test-sa"). 830 WithNamespace(namespace)). 831 WithRoleRef(rbacv1ac.RoleRef(). 832 WithAPIGroup("rbac.authorization.k8s.io"). 833 WithKind("ClusterRole"). 834 WithName("system:auth-delegator")). 835 WithLabels(crbLabels) 836 837 mockOpClient.EXPECT().ApplyClusterRoleBinding(crbApplyConfig, metav1.ApplyOptions{Force: true, FieldManager: "olm.install"}).Return(authDelegatorClusterRoleBinding, nil) 838 839 authReaderRoleBinding := &rbacv1.RoleBinding{ 840 Subjects: []rbacv1.Subject{ 841 { 842 Kind: "ServiceAccount", 843 APIGroup: "", 844 Name: args.depSpec.Template.Spec.ServiceAccountName, 845 Namespace: namespace, 846 }, 847 }, 848 RoleRef: rbacv1.RoleRef{ 849 APIGroup: "rbac.authorization.k8s.io", 850 Kind: "Role", 851 Name: "extension-apiserver-authentication-reader", 852 }, 853 } 854 authReaderRoleBinding.SetName(service.GetName() + "-auth-reader") 855 authReaderRoleBinding.SetNamespace(KubeSystem) 856 authReaderRoleBinding.SetLabels(map[string]string{OLMManagedLabelKey: OLMManagedLabelValue}) 857 858 authReaderRoleBindingApplyConfig := rbacv1ac.RoleBinding(AuthReaderRoleBindingName(service.GetName()), KubeSystem). 859 WithLabels(map[string]string{OLMManagedLabelKey: OLMManagedLabelValue}). 860 WithSubjects(rbacv1ac.Subject(). 861 WithKind("ServiceAccount"). 862 WithAPIGroup(""). 863 WithName(args.depSpec.Template.Spec.ServiceAccountName). 864 WithNamespace(namespace)). 865 WithRoleRef(rbacv1ac.RoleRef(). 866 WithAPIGroup("rbac.authorization.k8s.io"). 867 WithKind("Role"). 868 WithName("extension-apiserver-authentication-reader")) 869 870 mockOpClient.EXPECT().ApplyRoleBinding(authReaderRoleBindingApplyConfig, metav1.ApplyOptions{Force: true, FieldManager: "olm.install"}).Return(authReaderRoleBinding, nil) 871 }, 872 state: fakeState{ 873 existingService: nil, 874 // unlabelled secret won't be in cache 875 getSecretError: errors.NewNotFound(schema.GroupResource{ 876 Group: "", 877 Resource: "Secret", 878 }, "nope"), 879 existingRole: &rbacv1.Role{ 880 ObjectMeta: metav1.ObjectMeta{}, 881 }, 882 existingRoleBinding: &rbacv1.RoleBinding{ 883 ObjectMeta: metav1.ObjectMeta{}, 884 }, 885 existingClusterRoleBinding: nil, 886 }, 887 fields: fields{ 888 owner: &v1alpha1.ClusterServiceVersion{}, 889 previousStrategy: nil, 890 templateAnnotations: nil, 891 initializers: nil, 892 apiServiceDescriptions: []certResource{}, 893 webhookDescriptions: []certResource{}, 894 }, 895 args: args{ 896 deploymentName: "test", 897 ca: ca, 898 rotateAt: time.Now().Add(time.Hour), 899 ports: []corev1.ServicePort{}, 900 depSpec: appsv1.DeploymentSpec{ 901 Selector: selector(t, "test=label"), 902 Template: corev1.PodTemplateSpec{ 903 Spec: corev1.PodSpec{ 904 ServiceAccountName: "test-sa", 905 }, 906 ObjectMeta: metav1.ObjectMeta{ 907 Annotations: map[string]string{ 908 "foo": "bar", 909 }, 910 }, 911 }, 912 }, 913 }, 914 want: &appsv1.DeploymentSpec{ 915 Selector: selector(t, "test=label"), 916 Template: corev1.PodTemplateSpec{ 917 ObjectMeta: metav1.ObjectMeta{ 918 Annotations: map[string]string{ 919 "foo": "bar", 920 OLMCAHashAnnotationKey: caHash}, 921 }, 922 Spec: corev1.PodSpec{ 923 ServiceAccountName: "test-sa", 924 Volumes: []corev1.Volume{ 925 { 926 Name: "apiservice-cert", 927 VolumeSource: corev1.VolumeSource{ 928 Secret: &corev1.SecretVolumeSource{ 929 SecretName: "test-service-cert", 930 Items: []corev1.KeyToPath{ 931 { 932 Key: "tls.crt", 933 Path: "apiserver.crt", 934 }, 935 { 936 Key: "tls.key", 937 Path: "apiserver.key", 938 }, 939 }, 940 }, 941 }, 942 }, 943 { 944 Name: "webhook-cert", 945 VolumeSource: corev1.VolumeSource{ 946 Secret: &corev1.SecretVolumeSource{ 947 SecretName: "test-service-cert", 948 Items: []corev1.KeyToPath{ 949 { 950 Key: "tls.crt", 951 Path: "tls.crt", 952 }, 953 { 954 Key: "tls.key", 955 Path: "tls.key", 956 }, 957 }, 958 }, 959 }, 960 }, 961 }, 962 }, 963 }, 964 }, 965 }, 966 } 967 for _, tt := range tests { 968 t.Run(tt.name, func(t *testing.T) { 969 ctrl := gomock.NewController(t) 970 defer ctrl.Finish() 971 972 certGenerator = certs.CertGeneratorFunc(staticCertGenerator) 973 974 mockOpClient := operatorclientmocks.NewMockClientInterface(ctrl) 975 fakeLister := newFakeLister(tt.state) 976 tt.mockExternal(mockOpClient, fakeLister, tt.fields.owner.GetNamespace(), tt.args) 977 978 client := wrappers.NewInstallStrategyDeploymentClient(mockOpClient, fakeLister, tt.fields.owner.GetNamespace()) 979 i := &StrategyDeploymentInstaller{ 980 strategyClient: client, 981 owner: tt.fields.owner, 982 previousStrategy: tt.fields.previousStrategy, 983 templateAnnotations: tt.fields.templateAnnotations, 984 initializers: tt.fields.initializers, 985 apiServiceDescriptions: tt.fields.apiServiceDescriptions, 986 webhookDescriptions: tt.fields.webhookDescriptions, 987 } 988 got, _, err := i.installCertRequirementsForDeployment(tt.args.deploymentName, tt.args.ca, tt.args.rotateAt, tt.args.depSpec, tt.args.ports) 989 if (err != nil) != tt.wantErr { 990 t.Errorf("installCertRequirementsForDeployment() error = %v, wantErr %v", err, tt.wantErr) 991 return 992 } 993 if !reflect.DeepEqual(got, tt.want) { 994 t.Errorf("installCertRequirementsForDeployment() \n got = %v \n want = %v\n diff=%s\n", got, tt.want, cmp.Diff(got, tt.want)) 995 } 996 }) 997 } 998 }