Skip to content

Commit 8083e10

Browse files
committed
perform operator apiService certificate validity checks directly
Signed-off-by: Ankita Thomas <[email protected]>
1 parent 4fc8920 commit 8083e10

File tree

8 files changed

+288
-70
lines changed

8 files changed

+288
-70
lines changed

pkg/controller/install/certresources.go

Lines changed: 71 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,13 @@ func ServiceName(deploymentName string) string {
157157
return deploymentName + "-service"
158158
}
159159

160+
func HostnamesForService(serviceName, namespace string) []string {
161+
return []string{
162+
fmt.Sprintf("%s.%s", serviceName, namespace),
163+
fmt.Sprintf("%s.%s.svc", serviceName, namespace),
164+
}
165+
}
166+
160167
func (i *StrategyDeploymentInstaller) getCertResources() []certResource {
161168
return append(i.apiServiceDescriptions, i.webhookDescriptions...)
162169
}
@@ -223,15 +230,74 @@ func (i *StrategyDeploymentInstaller) CertsRotated() bool {
223230
return i.certificatesRotated
224231
}
225232

226-
func ShouldRotateCerts(csv *v1alpha1.ClusterServiceVersion) bool {
233+
// shouldRotateCerts indicates whether an apiService cert should be rotated due to being
234+
// malformed, invalid, expired, inactive or within a specific freshness interval (DefaultCertMinFresh) before expiry.
235+
func shouldRotateCerts(certSecret *corev1.Secret, hosts []string) bool {
227236
now := metav1.Now()
228-
if !csv.Status.CertsRotateAt.IsZero() && csv.Status.CertsRotateAt.Before(&now) {
237+
caPEM, ok := certSecret.Data[OLMCAPEMKey]
238+
if !ok {
239+
// missing CA cert in secret
240+
return true
241+
}
242+
certPEM, ok := certSecret.Data["tls.crt"]
243+
if !ok {
244+
// missing cert in secret
245+
return true
246+
}
247+
248+
ca, err := certs.PEMToCert(caPEM)
249+
if err != nil {
250+
// malformed CA cert
251+
return true
252+
}
253+
cert, err := certs.PEMToCert(certPEM)
254+
if err != nil {
255+
// malformed cert
229256
return true
230257
}
231258

259+
// check for cert freshness
260+
if !certs.Active(ca) || now.Time.After(CalculateCertRotatesAt(ca.NotAfter)) ||
261+
!certs.Active(cert) || now.Time.After(CalculateCertRotatesAt(cert.NotAfter)) {
262+
return true
263+
}
264+
265+
// Check validity of serving cert and if serving cert is trusted by the CA
266+
for _, host := range hosts {
267+
if err := certs.VerifyCert(ca, cert, host); err != nil {
268+
return true
269+
}
270+
}
232271
return false
233272
}
234273

274+
func (i *StrategyDeploymentInstaller) ShouldRotateCerts(s Strategy) (bool, error) {
275+
strategy, ok := s.(*v1alpha1.StrategyDetailsDeployment)
276+
if !ok {
277+
return false, fmt.Errorf("attempted to install %s strategy with deployment installer", strategy.GetStrategyName())
278+
}
279+
280+
hasCerts := map[string]struct{}{}
281+
for _, c := range i.getCertResources() {
282+
hasCerts[c.getDeploymentName()] = struct{}{}
283+
}
284+
for _, sddSpec := range strategy.DeploymentSpecs {
285+
if _, ok := hasCerts[sddSpec.Name]; ok {
286+
certSecret, err := i.strategyClient.GetOpLister().CoreV1().SecretLister().Secrets(i.owner.GetNamespace()).Get(SecretName(ServiceName(sddSpec.Name)))
287+
if err == nil {
288+
if shouldRotateCerts(certSecret, HostnamesForService(ServiceName(sddSpec.Name), i.owner.GetNamespace())) {
289+
return true, nil
290+
}
291+
} else if apierrors.IsNotFound(err) {
292+
return true, nil
293+
} else {
294+
return false, err
295+
}
296+
}
297+
}
298+
return false, nil
299+
}
300+
235301
func CalculateCertExpiration(startingFrom time.Time) time.Time {
236302
return startingFrom.Add(DefaultCertValidFor)
237303
}
@@ -267,10 +333,7 @@ func (i *StrategyDeploymentInstaller) installCertRequirementsForDeployment(deplo
267333
}
268334

269335
// Create signed serving cert
270-
hosts := []string{
271-
fmt.Sprintf("%s.%s", serviceName, i.owner.GetNamespace()),
272-
fmt.Sprintf("%s.%s.svc", serviceName, i.owner.GetNamespace()),
273-
}
336+
hosts := HostnamesForService(serviceName, i.owner.GetNamespace())
274337
servingPair, err := certGenerator.Generate(expiration, Organization, ca, hosts)
275338
if err != nil {
276339
logger.Warnf("could not generate signed certs for hosts %v", hosts)
@@ -314,10 +377,10 @@ func (i *StrategyDeploymentInstaller) installCertRequirementsForDeployment(deplo
314377

315378
// Attempt an update
316379
// TODO: Check that the secret was not modified
317-
if existingCAPEM, ok := existingSecret.Data[OLMCAPEMKey]; ok && !ShouldRotateCerts(i.owner.(*v1alpha1.ClusterServiceVersion)) {
380+
if !shouldRotateCerts(existingSecret, HostnamesForService(serviceName, i.owner.GetNamespace())) {
318381
logger.Warnf("reusing existing cert %s", secret.GetName())
319382
secret = existingSecret
320-
caPEM = existingCAPEM
383+
caPEM = existingSecret.Data[OLMCAPEMKey]
321384
caHash = certs.PEMSHA256(caPEM)
322385
} else {
323386
if _, err := i.strategyClient.GetOpClient().UpdateSecret(secret); err != nil {

pkg/controller/install/resolver.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ type Strategy interface {
2121
type StrategyInstaller interface {
2222
Install(strategy Strategy) error
2323
CheckInstalled(strategy Strategy) (bool, error)
24+
ShouldRotateCerts(strategy Strategy) (bool, error)
2425
CertsRotateAt() time.Time
2526
CertsRotated() bool
2627
}
@@ -79,3 +80,7 @@ func (i *NullStrategyInstaller) CertsRotateAt() time.Time {
7980
func (i *NullStrategyInstaller) CertsRotated() bool {
8081
return false
8182
}
83+
84+
func (i *NullStrategyInstaller) ShouldRotateCerts(s Strategy) (bool, error) {
85+
return false, nil
86+
}

pkg/controller/operators/olm/apiservices.go

Lines changed: 2 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -93,17 +93,12 @@ func (a *Operator) checkAPIServiceResources(csv *v1alpha1.ClusterServiceVersion,
9393

9494
// Check if CA is Active
9595
caBundle := apiService.Spec.CABundle
96-
ca, err := certs.PEMToCert(caBundle)
96+
_, err = certs.PEMToCert(caBundle)
9797
if err != nil {
9898
logger.Warnf("could not convert APIService CA bundle to x509 cert")
9999
errs = append(errs, err)
100100
continue
101101
}
102-
if !certs.Active(ca) {
103-
logger.Warnf("CA cert not active")
104-
errs = append(errs, fmt.Errorf("found the CA cert is not active"))
105-
continue
106-
}
107102

108103
// Check if serving cert is active
109104
secretName := install.SecretName(serviceName)
@@ -113,17 +108,12 @@ func (a *Operator) checkAPIServiceResources(csv *v1alpha1.ClusterServiceVersion,
113108
errs = append(errs, err)
114109
continue
115110
}
116-
cert, err := certs.PEMToCert(secret.Data["tls.crt"])
111+
_, err = certs.PEMToCert(secret.Data["tls.crt"])
117112
if err != nil {
118113
logger.Warnf("could not convert serving cert to x509 cert")
119114
errs = append(errs, err)
120115
continue
121116
}
122-
if !certs.Active(cert) {
123-
logger.Warnf("serving cert not active")
124-
errs = append(errs, fmt.Errorf("found the serving cert not active"))
125-
continue
126-
}
127117

128118
// Check if CA hash matches expected
129119
caHash := hashFunc(caBundle)
@@ -133,18 +123,6 @@ func (a *Operator) checkAPIServiceResources(csv *v1alpha1.ClusterServiceVersion,
133123
continue
134124
}
135125

136-
// Check if serving cert is trusted by the CA
137-
hosts := []string{
138-
fmt.Sprintf("%s.%s", service.GetName(), csv.GetNamespace()),
139-
fmt.Sprintf("%s.%s.svc", service.GetName(), csv.GetNamespace()),
140-
}
141-
for _, host := range hosts {
142-
if err := certs.VerifyCert(ca, cert, host); err != nil {
143-
errs = append(errs, fmt.Errorf("could not verify cert: %s", err.Error()))
144-
continue
145-
}
146-
}
147-
148126
// Ensure the existing Deployment has a matching CA hash annotation
149127
deployment, err := a.lister.AppsV1().DeploymentLister().Deployments(csv.GetNamespace()).Get(desc.DeploymentName)
150128
if apierrors.IsNotFound(err) || err != nil {

pkg/controller/operators/olm/operator.go

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2207,18 +2207,18 @@ func (a *Operator) transitionCSVState(in v1alpha1.ClusterServiceVersion) (out *v
22072207
return
22082208
}
22092209
if installErr := a.updateInstallStatus(out, installer, strategy, v1alpha1.CSVPhaseInstalling, v1alpha1.CSVReasonWaiting); installErr != nil {
2210-
// Re-sync if kube-apiserver was unavailable
2211-
if apierrors.IsServiceUnavailable(installErr) {
2212-
logger.WithError(installErr).Info("could not update install status")
2213-
syncError = installErr
2214-
return
2215-
}
22162210
// Set phase to failed if it's been a long time since the last transition (5 minutes)
22172211
if out.Status.LastTransitionTime != nil && a.now().Sub(out.Status.LastTransitionTime.Time) >= 5*time.Minute {
22182212
logger.Warn("install timed out")
22192213
out.SetPhaseWithEvent(v1alpha1.CSVPhaseFailed, v1alpha1.CSVReasonInstallCheckFailed, "install timeout", now, a.recorder)
22202214
return
22212215
}
2216+
// Re-sync if kube-apiserver was unavailable
2217+
if apierrors.IsServiceUnavailable(installErr) {
2218+
logger.WithError(installErr).Info("could not update install status")
2219+
syncError = installErr
2220+
return
2221+
}
22222222
}
22232223
logger.WithField("strategy", out.Spec.InstallStrategy.StrategyName).Infof("install strategy successful")
22242224

@@ -2242,7 +2242,10 @@ func (a *Operator) transitionCSVState(in v1alpha1.ClusterServiceVersion) (out *v
22422242
}
22432243

22442244
// Check if it's time to refresh owned APIService certs
2245-
if install.ShouldRotateCerts(out) {
2245+
if shouldRotate, err := installer.ShouldRotateCerts(strategy); err != nil {
2246+
logger.WithError(err).Info("cert validity check")
2247+
return
2248+
} else if shouldRotate {
22462249
logger.Debug("CSV owns resources that require a cert refresh")
22472250
out.SetPhaseWithEvent(v1alpha1.CSVPhasePending, v1alpha1.CSVReasonNeedsCertRotation, "CSV owns resources that require a cert refresh", now, a.recorder)
22482251
return
@@ -2352,7 +2355,10 @@ func (a *Operator) transitionCSVState(in v1alpha1.ClusterServiceVersion) (out *v
23522355
}
23532356

23542357
// Check if it's time to refresh owned APIService certs
2355-
if install.ShouldRotateCerts(out) {
2358+
if shouldRotate, err := installer.ShouldRotateCerts(strategy); err != nil {
2359+
logger.WithError(err).Info("cert validity check")
2360+
return
2361+
} else if shouldRotate {
23562362
logger.Debug("CSV owns resources that require a cert refresh")
23572363
out.SetPhaseWithEvent(v1alpha1.CSVPhasePending, v1alpha1.CSVReasonNeedsCertRotation, "owned APIServices need cert refresh", now, a.recorder)
23582364
return

pkg/controller/operators/olm/operator_test.go

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,10 @@ func (i *TestInstaller) CheckInstalled(s install.Strategy) (bool, error) {
101101
return true, nil
102102
}
103103

104+
func (i *TestInstaller) ShouldRotateCerts(s install.Strategy) (bool, error) {
105+
return false, nil
106+
}
107+
104108
func (i *TestInstaller) CertsRotateAt() time.Time {
105109
return time.Time{}
106110
}
@@ -1956,26 +1960,26 @@ func TestTransitionCSV(t *testing.T) {
19561960
},
19571961
clientObjs: []runtime.Object{defaultOperatorGroup},
19581962
apis: []runtime.Object{
1959-
apiService("a1", "v1", "v1-a1", namespace, "a1", expiredCAPEM, apiregistrationv1.ConditionTrue, ownerLabelFromCSV("csv1", namespace)),
1963+
apiService("a1", "v1", install.ServiceName("a1"), namespace, "a1", expiredCAPEM, apiregistrationv1.ConditionTrue, ownerLabelFromCSV("csv1", namespace)),
19601964
},
19611965
objs: []runtime.Object{
19621966
deployment("a1", namespace, "sa", addAnnotations(defaultTemplateAnnotations, map[string]string{
19631967
install.OLMCAHashAnnotationKey: expiredCAHash,
19641968
})),
1965-
withAnnotations(keyPairToTLSSecret("v1.a1-cert", namespace, signedServingPair(time.Now().Add(24*time.Hour), expiredCA, []string{"v1-a1.ns", "v1-a1.ns.svc"})), map[string]string{
1969+
withAnnotations(keyPairToTLSSecret(install.SecretName(install.ServiceName("a1")), namespace, signedServingPair(time.Now().Add(24*time.Hour), expiredCA, install.HostnamesForService(install.ServiceName("a1"), "ns"))), map[string]string{
19661970
install.OLMCAHashAnnotationKey: expiredCAHash,
19671971
}),
1968-
service("v1-a1", namespace, "a1", 80),
1972+
service(install.ServiceName("a1"), namespace, "a1", 80),
19691973
serviceAccount("sa", namespace),
1970-
role("v1.a1-cert", namespace, []rbacv1.PolicyRule{
1974+
role(install.SecretName(install.ServiceName("a1")), namespace, []rbacv1.PolicyRule{
19711975
{
19721976
Verbs: []string{"get"},
19731977
APIGroups: []string{""},
19741978
Resources: []string{"secrets"},
1975-
ResourceNames: []string{"v1.a1-cert"},
1979+
ResourceNames: []string{install.SecretName(install.ServiceName("a1"))},
19761980
},
19771981
}),
1978-
roleBinding("v1.a1-cert", namespace, "v1.a1-cert", "sa", namespace),
1982+
roleBinding(install.SecretName(install.ServiceName("a1")), namespace, install.SecretName(install.ServiceName("a1")), "sa", namespace),
19791983
role("extension-apiserver-authentication-reader", "kube-system", []rbacv1.PolicyRule{
19801984
{
19811985
Verbs: []string{"get"},
@@ -1984,7 +1988,7 @@ func TestTransitionCSV(t *testing.T) {
19841988
ResourceNames: []string{"extension-apiserver-authentication"},
19851989
},
19861990
}),
1987-
roleBinding("v1.a1-auth-reader", "kube-system", "extension-apiserver-authentication-reader", "sa", namespace),
1991+
roleBinding(install.AuthReaderRoleBindingName(install.ServiceName("a1")), "kube-system", "extension-apiserver-authentication-reader", "sa", namespace),
19881992
clusterRole("system:auth-delegator", []rbacv1.PolicyRule{
19891993
{
19901994
Verbs: []string{"create"},
@@ -1997,15 +2001,15 @@ func TestTransitionCSV(t *testing.T) {
19972001
Resources: []string{"subjectaccessreviews"},
19982002
},
19992003
}),
2000-
clusterRoleBinding("v1.a1-system:auth-delegator", "system:auth-delegator", "sa", namespace),
2004+
clusterRoleBinding(install.AuthDelegatorClusterRoleBindingName(install.ServiceName("a1")), "system:auth-delegator", "sa", namespace),
20012005
},
20022006
crds: []runtime.Object{
20032007
crd("c1", "v1", "g1"),
20042008
},
20052009
},
20062010
expected: expected{
20072011
csvStates: map[string]csvState{
2008-
"csv1": {exists: true, phase: v1alpha1.CSVPhaseFailed, reason: v1alpha1.CSVReasonAPIServiceResourceIssue},
2012+
"csv1": {exists: true, phase: v1alpha1.CSVPhaseFailed, reason: v1alpha1.CSVReasonNeedsCertRotation},
20092013
},
20102014
},
20112015
},
@@ -2025,26 +2029,26 @@ func TestTransitionCSV(t *testing.T) {
20252029
},
20262030
clientObjs: []runtime.Object{defaultOperatorGroup},
20272031
apis: []runtime.Object{
2028-
apiService("a1", "v1", "v1-a1", namespace, "a1", expiredCAPEM, apiregistrationv1.ConditionTrue, ownerLabelFromCSV("csv1", namespace)),
2032+
apiService("a1", "v1", install.ServiceName("a1"), namespace, "a1", expiredCAPEM, apiregistrationv1.ConditionTrue, ownerLabelFromCSV("csv1", namespace)),
20292033
},
20302034
objs: []runtime.Object{
20312035
deployment("a1", namespace, "sa", addAnnotations(defaultTemplateAnnotations, map[string]string{
20322036
install.OLMCAHashAnnotationKey: expiredCAHash,
20332037
})),
2034-
withAnnotations(keyPairToTLSSecret("v1.a1-cert", namespace, signedServingPair(time.Now().Add(24*time.Hour), expiredCA, []string{"v1-a1.ns", "v1-a1.ns.svc"})), map[string]string{
2038+
withAnnotations(keyPairToTLSSecret(install.SecretName(install.ServiceName("a1")), namespace, signedServingPair(time.Now().Add(24*time.Hour), expiredCA, install.HostnamesForService(install.ServiceName("a1"), "ns"))), map[string]string{
20352039
install.OLMCAHashAnnotationKey: expiredCAHash,
20362040
}),
2037-
service("v1-a1", namespace, "a1", 80),
2041+
service(install.ServiceName("a1"), namespace, "a1", 80),
20382042
serviceAccount("sa", namespace),
2039-
role("v1.a1-cert", namespace, []rbacv1.PolicyRule{
2043+
role(install.SecretName(install.ServiceName("a1")), namespace, []rbacv1.PolicyRule{
20402044
{
20412045
Verbs: []string{"get"},
20422046
APIGroups: []string{""},
20432047
Resources: []string{"secrets"},
2044-
ResourceNames: []string{"v1.a1-cert"},
2048+
ResourceNames: []string{install.SecretName(install.ServiceName("a1"))},
20452049
},
20462050
}),
2047-
roleBinding("v1.a1-cert", namespace, "v1.a1-cert", "sa", namespace),
2051+
roleBinding(install.SecretName(install.ServiceName("a1")), namespace, install.SecretName(install.ServiceName("a1")), "sa", namespace),
20482052
role("extension-apiserver-authentication-reader", "kube-system", []rbacv1.PolicyRule{
20492053
{
20502054
Verbs: []string{"get"},
@@ -2053,7 +2057,7 @@ func TestTransitionCSV(t *testing.T) {
20532057
ResourceNames: []string{"extension-apiserver-authentication"},
20542058
},
20552059
}),
2056-
roleBinding("v1.a1-auth-reader", "kube-system", "extension-apiserver-authentication-reader", "sa", namespace),
2060+
roleBinding(install.AuthReaderRoleBindingName(install.ServiceName("a1")), "kube-system", "extension-apiserver-authentication-reader", "sa", namespace),
20572061
clusterRole("system:auth-delegator", []rbacv1.PolicyRule{
20582062
{
20592063
Verbs: []string{"create"},
@@ -2066,15 +2070,15 @@ func TestTransitionCSV(t *testing.T) {
20662070
Resources: []string{"subjectaccessreviews"},
20672071
},
20682072
}),
2069-
clusterRoleBinding("v1.a1-system:auth-delegator", "system:auth-delegator", "sa", namespace),
2073+
clusterRoleBinding(install.AuthDelegatorClusterRoleBindingName(install.ServiceName("a1")), "system:auth-delegator", "sa", namespace),
20702074
},
20712075
crds: []runtime.Object{
20722076
crd("c1", "v1", "g1"),
20732077
},
20742078
},
20752079
expected: expected{
20762080
csvStates: map[string]csvState{
2077-
"csv1": {exists: true, phase: v1alpha1.CSVPhasePending, reason: v1alpha1.CSVReasonAPIServiceResourcesNeedReinstall},
2081+
"csv1": {exists: true, phase: v1alpha1.CSVPhasePending, reason: v1alpha1.CSVReasonNeedsCertRotation},
20782082
},
20792083
},
20802084
},

0 commit comments

Comments
 (0)