github.com/operator-framework/operator-lifecycle-manager@v0.30.0/pkg/controller/install/certresources.go (about) 1 package install 2 3 import ( 4 "fmt" 5 "strconv" 6 "time" 7 8 log "github.com/sirupsen/logrus" 9 appsv1 "k8s.io/api/apps/v1" 10 corev1 "k8s.io/api/core/v1" 11 rbacv1 "k8s.io/api/rbac/v1" 12 apierrors "k8s.io/apimachinery/pkg/api/errors" 13 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 14 "k8s.io/apimachinery/pkg/util/intstr" 15 "k8s.io/apimachinery/pkg/util/sets" 16 corev1ac "k8s.io/client-go/applyconfigurations/core/v1" 17 rbacv1ac "k8s.io/client-go/applyconfigurations/rbac/v1" 18 19 "github.com/operator-framework/api/pkg/operators/v1alpha1" 20 "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/certs" 21 "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/ownerutil" 22 ) 23 24 var _ certResource = &apiServiceDescriptionsWithCAPEM{} 25 26 var _ certResource = &webhookDescriptionWithCAPEM{} 27 28 // TODO: to keep refactoring minimal for backports, this is factored out here so that it can be replaced 29 // during tests. but it should be properly injected instead. 30 var certGenerator certs.CertGenerator = certs.CertGeneratorFunc(certs.CreateSignedServingPair) 31 32 const ( 33 // DefaultCertMinFresh is the default min-fresh value - 1 day 34 DefaultCertMinFresh = time.Hour * 24 35 // DefaultCertValidFor is the default duration a cert can be valid for - 2 years 36 DefaultCertValidFor = time.Hour * 24 * 730 37 // OLMCAPEMKey is the CAPEM 38 OLMCAPEMKey = "olmCAKey" 39 // OLMCAHashAnnotationKey is the label key used to store the hash of the CA cert 40 OLMCAHashAnnotationKey = "olmcahash" 41 // Organization is the organization name used in the generation of x509 certs 42 Organization = "Red Hat, Inc." 43 // Kubernetes System namespace 44 KubeSystem = "kube-system" 45 // olm managed label 46 OLMManagedLabelKey = "olm.managed" 47 OLMManagedLabelValue = "true" 48 ) 49 50 type certResource interface { 51 getName() string 52 setCAPEM(caPEM []byte) 53 getCAPEM() []byte 54 getServicePort() corev1.ServicePort 55 getDeploymentName() string 56 } 57 58 func getServicePorts(descs []certResource) []corev1.ServicePort { 59 result := []corev1.ServicePort{} 60 for _, desc := range descs { 61 if !containsServicePort(result, desc.getServicePort()) { 62 result = append(result, desc.getServicePort()) 63 } 64 } 65 66 return result 67 } 68 69 func containsServicePort(servicePorts []corev1.ServicePort, targetServicePort corev1.ServicePort) bool { 70 for _, servicePort := range servicePorts { 71 if servicePort == targetServicePort { 72 return true 73 } 74 } 75 76 return false 77 } 78 79 type apiServiceDescriptionsWithCAPEM struct { 80 apiServiceDescription v1alpha1.APIServiceDescription 81 caPEM []byte 82 } 83 84 func (i *apiServiceDescriptionsWithCAPEM) getName() string { 85 return i.apiServiceDescription.Name 86 } 87 88 func (i *apiServiceDescriptionsWithCAPEM) setCAPEM(caPEM []byte) { 89 i.caPEM = caPEM 90 } 91 92 func (i *apiServiceDescriptionsWithCAPEM) getCAPEM() []byte { 93 return i.caPEM 94 } 95 96 func (i *apiServiceDescriptionsWithCAPEM) getDeploymentName() string { 97 return i.apiServiceDescription.DeploymentName 98 } 99 100 func (i *apiServiceDescriptionsWithCAPEM) getServicePort() corev1.ServicePort { 101 containerPort := 443 102 if i.apiServiceDescription.ContainerPort > 0 { 103 containerPort = int(i.apiServiceDescription.ContainerPort) 104 } 105 return corev1.ServicePort{ 106 Name: strconv.Itoa(containerPort), 107 Port: int32(containerPort), 108 TargetPort: intstr.FromInt(containerPort), 109 } 110 } 111 112 type webhookDescriptionWithCAPEM struct { 113 webhookDescription v1alpha1.WebhookDescription 114 caPEM []byte 115 } 116 117 func (i *webhookDescriptionWithCAPEM) getName() string { 118 return i.webhookDescription.GenerateName 119 } 120 121 func (i *webhookDescriptionWithCAPEM) setCAPEM(caPEM []byte) { 122 i.caPEM = caPEM 123 } 124 125 func (i *webhookDescriptionWithCAPEM) getCAPEM() []byte { 126 return i.caPEM 127 } 128 129 func (i *webhookDescriptionWithCAPEM) getDeploymentName() string { 130 return i.webhookDescription.DeploymentName 131 } 132 133 func (i *webhookDescriptionWithCAPEM) getServicePort() corev1.ServicePort { 134 containerPort := 443 135 if i.webhookDescription.ContainerPort > 0 { 136 containerPort = int(i.webhookDescription.ContainerPort) 137 } 138 139 // Before users could set TargetPort in the CSV, OLM just set its 140 // value to the containerPort. This change keeps OLM backwards compatible 141 // if the TargetPort is not set in the CSV. 142 targetPort := intstr.FromInt(containerPort) 143 if i.webhookDescription.TargetPort != nil { 144 targetPort = *i.webhookDescription.TargetPort 145 } 146 return corev1.ServicePort{ 147 Name: strconv.Itoa(containerPort), 148 Port: int32(containerPort), 149 TargetPort: targetPort, 150 } 151 } 152 153 func SecretName(serviceName string) string { 154 return serviceName + "-cert" 155 } 156 157 func ServiceName(deploymentName string) string { 158 return deploymentName + "-service" 159 } 160 161 func HostnamesForService(serviceName, namespace string) []string { 162 return []string{ 163 fmt.Sprintf("%s.%s", serviceName, namespace), 164 fmt.Sprintf("%s.%s.svc", serviceName, namespace), 165 fmt.Sprintf("%s.%s.svc.cluster.local", serviceName, namespace), 166 } 167 } 168 169 func (i *StrategyDeploymentInstaller) getCertResources() []certResource { 170 return append(i.apiServiceDescriptions, i.webhookDescriptions...) 171 } 172 173 func (i *StrategyDeploymentInstaller) certResourcesForDeployment(deploymentName string) []certResource { 174 var result []certResource 175 for _, desc := range i.getCertResources() { 176 if desc.getDeploymentName() == deploymentName { 177 result = append(result, desc) 178 } 179 } 180 return result 181 } 182 183 func (i *StrategyDeploymentInstaller) updateCertResourcesForDeployment(deploymentName string, caPEM []byte) { 184 for _, desc := range i.certResourcesForDeployment(deploymentName) { 185 desc.setCAPEM(caPEM) 186 } 187 } 188 189 func (i *StrategyDeploymentInstaller) installCertRequirements(strategy Strategy) (*v1alpha1.StrategyDetailsDeployment, error) { 190 logger := log.WithFields(log.Fields{}) 191 192 // Assume the strategy is for a deployment 193 strategyDetailsDeployment, ok := strategy.(*v1alpha1.StrategyDetailsDeployment) 194 if !ok { 195 return nil, fmt.Errorf("unsupported InstallStrategy type") 196 } 197 198 // Create the CA 199 i.certificateExpirationTime = CalculateCertExpiration(time.Now()) 200 ca, err := certs.GenerateCA(i.certificateExpirationTime, Organization) 201 if err != nil { 202 logger.Debug("failed to generate CA") 203 return nil, err 204 } 205 206 for n, sddSpec := range strategyDetailsDeployment.DeploymentSpecs { 207 certResources := i.certResourcesForDeployment(sddSpec.Name) 208 209 if len(certResources) == 0 { 210 log.Info("No api or webhook descs to add CA to") 211 continue 212 } 213 214 // Update the deployment for each certResource 215 newDepSpec, caPEM, err := i.installCertRequirementsForDeployment(sddSpec.Name, ca, i.certificateExpirationTime, sddSpec.Spec, getServicePorts(certResources)) 216 if err != nil { 217 return nil, err 218 } 219 220 i.updateCertResourcesForDeployment(sddSpec.Name, caPEM) 221 222 strategyDetailsDeployment.DeploymentSpecs[n].Spec = *newDepSpec 223 } 224 return strategyDetailsDeployment, nil 225 } 226 227 func (i *StrategyDeploymentInstaller) CertsRotateAt() time.Time { 228 return CalculateCertRotatesAt(i.certificateExpirationTime) 229 } 230 231 func (i *StrategyDeploymentInstaller) CertsRotated() bool { 232 return i.certificatesRotated 233 } 234 235 // shouldRotateCerts indicates whether an apiService cert should be rotated due to being 236 // malformed, invalid, expired, inactive or within a specific freshness interval (DefaultCertMinFresh) before expiry. 237 func shouldRotateCerts(certSecret *corev1.Secret, hosts []string) bool { 238 now := metav1.Now() 239 caPEM, ok := certSecret.Data[OLMCAPEMKey] 240 if !ok { 241 // missing CA cert in secret 242 return true 243 } 244 certPEM, ok := certSecret.Data["tls.crt"] 245 if !ok { 246 // missing cert in secret 247 return true 248 } 249 250 ca, err := certs.PEMToCert(caPEM) 251 if err != nil { 252 // malformed CA cert 253 return true 254 } 255 cert, err := certs.PEMToCert(certPEM) 256 if err != nil { 257 // malformed cert 258 return true 259 } 260 261 // check for cert freshness 262 if !certs.Active(ca) || now.Time.After(CalculateCertRotatesAt(ca.NotAfter)) || 263 !certs.Active(cert) || now.Time.After(CalculateCertRotatesAt(cert.NotAfter)) { 264 return true 265 } 266 267 // Check validity of serving cert and if serving cert is trusted by the CA 268 for _, host := range hosts { 269 if err := certs.VerifyCert(ca, cert, host); err != nil { 270 return true 271 } 272 } 273 return false 274 } 275 276 func (i *StrategyDeploymentInstaller) ShouldRotateCerts(s Strategy) (bool, error) { 277 strategy, ok := s.(*v1alpha1.StrategyDetailsDeployment) 278 if !ok { 279 return false, fmt.Errorf("failed to install %s strategy with deployment installer: unsupported deployment install strategy", strategy.GetStrategyName()) 280 } 281 282 hasCerts := sets.New[string]() 283 for _, c := range i.getCertResources() { 284 hasCerts.Insert(c.getDeploymentName()) 285 } 286 for _, sddSpec := range strategy.DeploymentSpecs { 287 if hasCerts.Has(sddSpec.Name) { 288 certSecret, err := i.strategyClient.GetOpLister().CoreV1().SecretLister().Secrets(i.owner.GetNamespace()).Get(SecretName(ServiceName(sddSpec.Name))) 289 if err == nil { 290 if shouldRotateCerts(certSecret, HostnamesForService(ServiceName(sddSpec.Name), i.owner.GetNamespace())) { 291 return true, nil 292 } 293 } else if apierrors.IsNotFound(err) { 294 return true, nil 295 } else { 296 return false, err 297 } 298 } 299 } 300 return false, nil 301 } 302 303 func CalculateCertExpiration(startingFrom time.Time) time.Time { 304 return startingFrom.Add(DefaultCertValidFor) 305 } 306 307 func CalculateCertRotatesAt(certExpirationTime time.Time) time.Time { 308 return certExpirationTime.Add(-1 * DefaultCertMinFresh) 309 } 310 311 func (i *StrategyDeploymentInstaller) installCertRequirementsForDeployment(deploymentName string, ca *certs.KeyPair, expiration time.Time, depSpec appsv1.DeploymentSpec, ports []corev1.ServicePort) (*appsv1.DeploymentSpec, []byte, error) { 312 logger := log.WithFields(log.Fields{}) 313 314 // apply Service 315 serviceName := ServiceName(deploymentName) 316 portsApplyConfig := []*corev1ac.ServicePortApplyConfiguration{} 317 for _, p := range ports { 318 ac := corev1ac.ServicePort(). 319 WithName(p.Name). 320 WithPort(p.Port). 321 WithTargetPort(p.TargetPort) 322 portsApplyConfig = append(portsApplyConfig, ac) 323 } 324 325 svcApplyConfig := corev1ac.Service(serviceName, i.owner.GetNamespace()). 326 WithSpec(corev1ac.ServiceSpec(). 327 WithPorts(portsApplyConfig...). 328 WithSelector(depSpec.Selector.MatchLabels)). 329 WithOwnerReferences(ownerutil.NonBlockingOwnerApplyConfiguration(i.owner)). 330 WithLabels(map[string]string{OLMManagedLabelKey: OLMManagedLabelValue}) 331 332 if _, err := i.strategyClient.GetOpClient().ApplyService(svcApplyConfig, metav1.ApplyOptions{Force: true, FieldManager: "olm.install"}); err != nil { 333 log.Errorf("could not apply service %s: %s", *svcApplyConfig.Name, err.Error()) 334 return nil, nil, err 335 } 336 337 // Create signed serving cert 338 hosts := HostnamesForService(serviceName, i.owner.GetNamespace()) 339 servingPair, err := certGenerator.Generate(expiration, Organization, ca, hosts) 340 if err != nil { 341 logger.Warnf("could not generate signed certs for hosts %v", hosts) 342 return nil, nil, err 343 } 344 345 // Create Secret for serving cert 346 certPEM, privPEM, err := servingPair.ToPEM() 347 if err != nil { 348 logger.Warnf("unable to convert serving certificate and private key to PEM format for Service %s", serviceName) 349 return nil, nil, err 350 } 351 352 // Add olmcahash as a label to the caPEM 353 caPEM, _, err := ca.ToPEM() 354 if err != nil { 355 logger.Warnf("unable to convert CA certificate to PEM format for Service %s", serviceName) 356 return nil, nil, err 357 } 358 caHash := certs.PEMSHA256(caPEM) 359 360 secret := &corev1.Secret{ 361 Data: map[string][]byte{ 362 "tls.crt": certPEM, 363 "tls.key": privPEM, 364 OLMCAPEMKey: caPEM, 365 }, 366 Type: corev1.SecretTypeTLS, 367 } 368 secret.SetName(SecretName(serviceName)) 369 secret.SetNamespace(i.owner.GetNamespace()) 370 secret.SetAnnotations(map[string]string{OLMCAHashAnnotationKey: caHash}) 371 secret.SetLabels(map[string]string{OLMManagedLabelKey: OLMManagedLabelValue}) 372 373 existingSecret, err := i.strategyClient.GetOpLister().CoreV1().SecretLister().Secrets(i.owner.GetNamespace()).Get(secret.GetName()) 374 if err == nil { 375 // Check if the only owners are this CSV or in this CSV's replacement chain 376 if ownerutil.Adoptable(i.owner, existingSecret.GetOwnerReferences()) { 377 ownerutil.AddNonBlockingOwner(secret, i.owner) 378 } 379 380 // Attempt an update 381 // TODO: Check that the secret was not modified 382 if !shouldRotateCerts(existingSecret, HostnamesForService(serviceName, i.owner.GetNamespace())) { 383 logger.Warnf("reusing existing cert %s", secret.GetName()) 384 secret = existingSecret 385 caPEM = existingSecret.Data[OLMCAPEMKey] 386 caHash = certs.PEMSHA256(caPEM) 387 } else { 388 if _, err := i.strategyClient.GetOpClient().UpdateSecret(secret); err != nil { 389 logger.Warnf("could not update secret %s", secret.GetName()) 390 return nil, nil, err 391 } 392 i.certificatesRotated = true 393 } 394 } else if apierrors.IsNotFound(err) { 395 // Create the secret 396 ownerutil.AddNonBlockingOwner(secret, i.owner) 397 if _, err := i.strategyClient.GetOpClient().CreateSecret(secret); err != nil { 398 if !apierrors.IsAlreadyExists(err) { 399 log.Warnf("could not create secret %s: %v", secret.GetName(), err) 400 return nil, nil, err 401 } 402 // if the secret isn't in the cache but exists in the cluster, it's missing the labels for the cache filter 403 // and just needs to be updated 404 if _, err := i.strategyClient.GetOpClient().UpdateSecret(secret); err != nil { 405 log.Warnf("could not update secret %s: %v", secret.GetName(), err) 406 return nil, nil, err 407 } 408 } 409 i.certificatesRotated = true 410 } else { 411 return nil, nil, err 412 } 413 414 // create Role and RoleBinding to allow the deployment to mount the Secret 415 secretRole := &rbacv1.Role{ 416 Rules: []rbacv1.PolicyRule{ 417 { 418 Verbs: []string{"get"}, 419 APIGroups: []string{""}, 420 Resources: []string{"secrets"}, 421 ResourceNames: []string{secret.GetName()}, 422 }, 423 }, 424 } 425 secretRole.SetName(secret.GetName()) 426 secretRole.SetNamespace(i.owner.GetNamespace()) 427 secretRole.SetLabels(map[string]string{OLMManagedLabelKey: OLMManagedLabelValue}) 428 429 existingSecretRole, err := i.strategyClient.GetOpLister().RbacV1().RoleLister().Roles(i.owner.GetNamespace()).Get(secretRole.GetName()) 430 if err == nil { 431 // Check if the only owners are this CSV or in this CSV's replacement chain 432 if ownerutil.Adoptable(i.owner, existingSecretRole.GetOwnerReferences()) { 433 ownerutil.AddNonBlockingOwner(secretRole, i.owner) 434 } 435 436 // Attempt an update 437 if _, err := i.strategyClient.GetOpClient().UpdateRole(secretRole); err != nil { 438 logger.Warnf("could not update secret role %s", secretRole.GetName()) 439 return nil, nil, err 440 } 441 } else if apierrors.IsNotFound(err) { 442 // Create the role 443 ownerutil.AddNonBlockingOwner(secretRole, i.owner) 444 _, err = i.strategyClient.GetOpClient().CreateRole(secretRole) 445 if err != nil { 446 log.Warnf("could not create secret role %s", secretRole.GetName()) 447 return nil, nil, err 448 } 449 } else { 450 return nil, nil, err 451 } 452 453 if depSpec.Template.Spec.ServiceAccountName == "" { 454 depSpec.Template.Spec.ServiceAccountName = "default" 455 } 456 457 secretRoleBinding := &rbacv1.RoleBinding{ 458 Subjects: []rbacv1.Subject{ 459 { 460 Kind: "ServiceAccount", 461 APIGroup: "", 462 Name: depSpec.Template.Spec.ServiceAccountName, 463 Namespace: i.owner.GetNamespace(), 464 }, 465 }, 466 RoleRef: rbacv1.RoleRef{ 467 APIGroup: "rbac.authorization.k8s.io", 468 Kind: "Role", 469 Name: secretRole.GetName(), 470 }, 471 } 472 secretRoleBinding.SetName(secret.GetName()) 473 secretRoleBinding.SetNamespace(i.owner.GetNamespace()) 474 secretRoleBinding.SetLabels(map[string]string{OLMManagedLabelKey: OLMManagedLabelValue}) 475 476 existingSecretRoleBinding, err := i.strategyClient.GetOpLister().RbacV1().RoleBindingLister().RoleBindings(i.owner.GetNamespace()).Get(secretRoleBinding.GetName()) 477 if err == nil { 478 // Check if the only owners are this CSV or in this CSV's replacement chain 479 if ownerutil.Adoptable(i.owner, existingSecretRoleBinding.GetOwnerReferences()) { 480 ownerutil.AddNonBlockingOwner(secretRoleBinding, i.owner) 481 } 482 483 // Attempt an update 484 if _, err := i.strategyClient.GetOpClient().UpdateRoleBinding(secretRoleBinding); err != nil { 485 logger.Warnf("could not update secret rolebinding %s", secretRoleBinding.GetName()) 486 return nil, nil, err 487 } 488 } else if apierrors.IsNotFound(err) { 489 // Create the role 490 ownerutil.AddNonBlockingOwner(secretRoleBinding, i.owner) 491 _, err = i.strategyClient.GetOpClient().CreateRoleBinding(secretRoleBinding) 492 if err != nil { 493 log.Warnf("could not create secret rolebinding with dep spec: %#v", depSpec) 494 return nil, nil, err 495 } 496 } else { 497 return nil, nil, err 498 } 499 500 // apply ClusterRoleBinding to system:auth-delegator Role 501 crbLabels := map[string]string{OLMManagedLabelKey: OLMManagedLabelValue} 502 for key, val := range ownerutil.OwnerLabel(i.owner, i.owner.GetObjectKind().GroupVersionKind().Kind) { 503 crbLabels[key] = val 504 } 505 crbApplyConfig := rbacv1ac.ClusterRoleBinding(AuthDelegatorClusterRoleBindingName(serviceName)). 506 WithSubjects(rbacv1ac.Subject(). 507 WithKind("ServiceAccount"). 508 WithAPIGroup(""). 509 WithName(depSpec.Template.Spec.ServiceAccountName). 510 WithNamespace(i.owner.GetNamespace())). 511 WithRoleRef(rbacv1ac.RoleRef(). 512 WithAPIGroup("rbac.authorization.k8s.io"). 513 WithKind("ClusterRole"). 514 WithName("system:auth-delegator")). 515 WithLabels(crbLabels) 516 517 if _, err = i.strategyClient.GetOpClient().ApplyClusterRoleBinding(crbApplyConfig, metav1.ApplyOptions{Force: true, FieldManager: "olm.install"}); err != nil { 518 log.Errorf("could not apply auth delegator clusterrolebinding %s: %s", *crbApplyConfig.Name, err.Error()) 519 return nil, nil, err 520 } 521 522 // Apply RoleBinding to extension-apiserver-authentication-reader Role in the kube-system namespace. 523 authReaderRoleBindingApplyConfig := rbacv1ac.RoleBinding(AuthReaderRoleBindingName(serviceName), KubeSystem). 524 WithLabels(map[string]string{OLMManagedLabelKey: OLMManagedLabelValue}). 525 WithSubjects(rbacv1ac.Subject(). 526 WithKind("ServiceAccount"). 527 WithAPIGroup(""). 528 WithName(depSpec.Template.Spec.ServiceAccountName). 529 WithNamespace(i.owner.GetNamespace())). 530 WithRoleRef(rbacv1ac.RoleRef(). 531 WithAPIGroup("rbac.authorization.k8s.io"). 532 WithKind("Role"). 533 WithName("extension-apiserver-authentication-reader")) 534 535 if _, err = i.strategyClient.GetOpClient().ApplyRoleBinding(authReaderRoleBindingApplyConfig, metav1.ApplyOptions{Force: true, FieldManager: "olm.install"}); err != nil { 536 log.Errorf("could not apply auth reader rolebinding %s: %s", *authReaderRoleBindingApplyConfig.Name, err.Error()) 537 return nil, nil, err 538 } 539 540 AddDefaultCertVolumeAndVolumeMounts(&depSpec, secret.GetName()) 541 542 // Setting the olm hash label forces a rollout and ensures that the new secret 543 // is used by the apiserver if not hot reloading. 544 SetCAAnnotation(&depSpec, caHash) 545 return &depSpec, caPEM, nil 546 } 547 548 func AuthDelegatorClusterRoleBindingName(serviceName string) string { 549 return serviceName + "-system:auth-delegator" 550 } 551 552 func AuthReaderRoleBindingName(serviceName string) string { 553 return serviceName + "-auth-reader" 554 } 555 556 func SetCAAnnotation(depSpec *appsv1.DeploymentSpec, caHash string) { 557 if len(depSpec.Template.ObjectMeta.GetAnnotations()) == 0 { 558 depSpec.Template.ObjectMeta.SetAnnotations(map[string]string{OLMCAHashAnnotationKey: caHash}) 559 } else { 560 depSpec.Template.Annotations[OLMCAHashAnnotationKey] = caHash 561 } 562 } 563 564 // AddDefaultCertVolumeAndVolumeMounts mounts the CA Cert generated by OLM to the location that OLM expects 565 // APIService certs to be as well as the location that the Operator-SDK and Kubebuilder expect webhook 566 // certs to be. 567 func AddDefaultCertVolumeAndVolumeMounts(depSpec *appsv1.DeploymentSpec, secretName string) { 568 // Update deployment with secret volume mount. 569 volume := corev1.Volume{ 570 Name: "apiservice-cert", 571 VolumeSource: corev1.VolumeSource{ 572 Secret: &corev1.SecretVolumeSource{ 573 SecretName: secretName, 574 Items: []corev1.KeyToPath{ 575 { 576 Key: "tls.crt", 577 Path: "apiserver.crt", 578 }, 579 { 580 Key: "tls.key", 581 Path: "apiserver.key", 582 }, 583 }, 584 }, 585 }, 586 } 587 588 mount := corev1.VolumeMount{ 589 Name: volume.Name, 590 MountPath: "/apiserver.local.config/certificates", 591 } 592 593 addCertVolumeAndVolumeMount(depSpec, volume, mount) 594 595 volume = corev1.Volume{ 596 Name: "webhook-cert", 597 VolumeSource: corev1.VolumeSource{ 598 Secret: &corev1.SecretVolumeSource{ 599 SecretName: secretName, 600 Items: []corev1.KeyToPath{ 601 { 602 Key: "tls.crt", 603 Path: "tls.crt", 604 }, 605 { 606 Key: "tls.key", 607 Path: "tls.key", 608 }, 609 }, 610 }, 611 }, 612 } 613 614 mount = corev1.VolumeMount{ 615 Name: volume.Name, 616 MountPath: "/tmp/k8s-webhook-server/serving-certs", 617 } 618 addCertVolumeAndVolumeMount(depSpec, volume, mount) 619 } 620 621 func addCertVolumeAndVolumeMount(depSpec *appsv1.DeploymentSpec, volume corev1.Volume, volumeMount corev1.VolumeMount) { 622 replaced := false 623 for i, v := range depSpec.Template.Spec.Volumes { 624 if v.Name == volume.Name { 625 depSpec.Template.Spec.Volumes[i] = volume 626 replaced = true 627 break 628 } 629 } 630 if !replaced { 631 depSpec.Template.Spec.Volumes = append(depSpec.Template.Spec.Volumes, volume) 632 } 633 634 for i, container := range depSpec.Template.Spec.Containers { 635 found := false 636 for j, m := range container.VolumeMounts { 637 if m.Name == volumeMount.Name { 638 found = true 639 break 640 } 641 642 // Replace if mounting to the same location. 643 if m.MountPath == volumeMount.MountPath { 644 container.VolumeMounts[j] = volumeMount 645 found = true 646 break 647 } 648 } 649 if !found { 650 container.VolumeMounts = append(container.VolumeMounts, volumeMount) 651 } 652 653 depSpec.Template.Spec.Containers[i] = container 654 } 655 }