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  }