Skip to content

Commit 78bb41b

Browse files
Merge pull request #1761 from awgreene/reusue-active-certs
Bug 1868712: OLM should reuse existing CA if they have not expired
2 parents 6d26c16 + 6132df3 commit 78bb41b

File tree

4 files changed

+111
-56
lines changed

4 files changed

+111
-56
lines changed

pkg/controller/install/certresources.go

Lines changed: 58 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ const (
2727
DefaultCertMinFresh = time.Hour * 24
2828
// DefaultCertValidFor is the default duration a cert can be valid for - 2 years
2929
DefaultCertValidFor = time.Hour * 24 * 730
30+
// OLMCAPEMKey is the CAPEM
31+
OLMCAPEMKey = "olmCAKey"
3032
// OLMCAHashAnnotationKey is the label key used to store the hash of the CA cert
3133
OLMCAHashAnnotationKey = "olmcahash"
3234
// Organization is the organization name used in the generation of x509 certs
@@ -185,25 +187,28 @@ func (i *StrategyDeploymentInstaller) installCertRequirements(strategy Strategy)
185187
}
186188

187189
// Update the deployment for each certResource
188-
newDepSpec, err := i.installCertRequirementsForDeployment(sddSpec.Name, ca, rotateAt, sddSpec.Spec, getServicePorts(certResources))
190+
newDepSpec, caPEM, err := i.installCertRequirementsForDeployment(sddSpec.Name, ca, rotateAt, sddSpec.Spec, getServicePorts(certResources))
189191
if err != nil {
190192
return nil, err
191193
}
192194

193-
caPEM, _, err := ca.ToPEM()
194-
if err != nil {
195-
logger.Warnf("unable to convert CA certificate to PEM format for Deployment %s", sddSpec.Name)
196-
return nil, err
197-
}
198-
199195
i.updateCertResourcesForDeployment(sddSpec.Name, caPEM)
200196

201197
strategyDetailsDeployment.DeploymentSpecs[n].Spec = *newDepSpec
202198
}
203199
return strategyDetailsDeployment, nil
204200
}
205201

206-
func (i *StrategyDeploymentInstaller) installCertRequirementsForDeployment(deploymentName string, ca *certs.KeyPair, rotateAt time.Time, depSpec appsv1.DeploymentSpec, ports []corev1.ServicePort) (*appsv1.DeploymentSpec, error) {
202+
func ShouldRotateCerts(csv *v1alpha1.ClusterServiceVersion) bool {
203+
now := metav1.Now()
204+
if !csv.Status.CertsRotateAt.IsZero() && csv.Status.CertsRotateAt.Before(&now) {
205+
return true
206+
}
207+
208+
return false
209+
}
210+
211+
func (i *StrategyDeploymentInstaller) installCertRequirementsForDeployment(deploymentName string, ca *certs.KeyPair, rotateAt time.Time, depSpec appsv1.DeploymentSpec, ports []corev1.ServicePort) (*appsv1.DeploymentSpec, []byte, error) {
207212
logger := log.WithFields(log.Fields{})
208213

209214
// Create a service for the deployment
@@ -215,27 +220,27 @@ func (i *StrategyDeploymentInstaller) installCertRequirementsForDeployment(deplo
215220
}
216221
service.SetName(ServiceName(deploymentName))
217222
service.SetNamespace(i.owner.GetNamespace())
223+
ownerutil.AddNonBlockingOwner(service, i.owner)
218224

219225
existingService, err := i.strategyClient.GetOpLister().CoreV1().ServiceLister().Services(i.owner.GetNamespace()).Get(service.GetName())
220226
if err == nil {
221227
if !ownerutil.Adoptable(i.owner, existingService.GetOwnerReferences()) {
222-
return nil, fmt.Errorf("service %s not safe to replace: extraneous ownerreferences found", service.GetName())
228+
return nil, nil, fmt.Errorf("service %s not safe to replace: extraneous ownerreferences found", service.GetName())
223229
}
224230
service.SetOwnerReferences(existingService.GetOwnerReferences())
225231

226232
// Delete the Service to replace
227233
deleteErr := i.strategyClient.GetOpClient().DeleteService(service.GetNamespace(), service.GetName(), &metav1.DeleteOptions{})
228234
if err != nil && !k8serrors.IsNotFound(deleteErr) {
229-
return nil, fmt.Errorf("could not delete existing service %s", service.GetName())
235+
return nil, nil, fmt.Errorf("could not delete existing service %s", service.GetName())
230236
}
231237
}
232-
ownerutil.AddNonBlockingOwner(service, i.owner)
233238

234239
// Attempt to create the Service
235240
_, err = i.strategyClient.GetOpClient().CreateService(service)
236241
if err != nil {
237242
logger.Warnf("could not create service %s", service.GetName())
238-
return nil, fmt.Errorf("could not create service %s: %s", service.GetName(), err.Error())
243+
return nil, nil, fmt.Errorf("could not create service %s: %s", service.GetName(), err.Error())
239244
}
240245

241246
// Create signed serving cert
@@ -246,33 +251,34 @@ func (i *StrategyDeploymentInstaller) installCertRequirementsForDeployment(deplo
246251
servingPair, err := certs.CreateSignedServingPair(rotateAt, Organization, ca, hosts)
247252
if err != nil {
248253
logger.Warnf("could not generate signed certs for hosts %v", hosts)
249-
return nil, err
254+
return nil, nil, err
250255
}
251256

252257
// Create Secret for serving cert
253258
certPEM, privPEM, err := servingPair.ToPEM()
254259
if err != nil {
255260
logger.Warnf("unable to convert serving certificate and private key to PEM format for Service %s", service.GetName())
256-
return nil, err
261+
return nil, nil, err
257262
}
258263

264+
// Add olmcahash as a label to the caPEM
265+
caPEM, _, err := ca.ToPEM()
266+
if err != nil {
267+
logger.Warnf("unable to convert CA certificate to PEM format for Service %s", service)
268+
return nil, nil, err
269+
}
270+
caHash := certs.PEMSHA256(caPEM)
271+
259272
secret := &corev1.Secret{
260273
Data: map[string][]byte{
261-
"tls.crt": certPEM,
262-
"tls.key": privPEM,
274+
"tls.crt": certPEM,
275+
"tls.key": privPEM,
276+
OLMCAPEMKey: caPEM,
263277
},
264278
Type: corev1.SecretTypeTLS,
265279
}
266280
secret.SetName(SecretName(service.GetName()))
267281
secret.SetNamespace(i.owner.GetNamespace())
268-
269-
// Add olmcahash as a label to the caPEM
270-
caPEM, _, err := ca.ToPEM()
271-
if err != nil {
272-
logger.Warnf("unable to convert CA certificate to PEM format for Service %s", service)
273-
return nil, err
274-
}
275-
caHash := certs.PEMSHA256(caPEM)
276282
secret.SetAnnotations(map[string]string{OLMCAHashAnnotationKey: caHash})
277283

278284
existingSecret, err := i.strategyClient.GetOpLister().CoreV1().SecretLister().Secrets(i.owner.GetNamespace()).Get(secret.GetName())
@@ -283,20 +289,27 @@ func (i *StrategyDeploymentInstaller) installCertRequirementsForDeployment(deplo
283289
}
284290

285291
// Attempt an update
286-
if _, err := i.strategyClient.GetOpClient().UpdateSecret(secret); err != nil {
292+
// TODO: Check that the secret was not modified
293+
if existingCAPEM, ok := existingSecret.Data[OLMCAPEMKey]; ok && !ShouldRotateCerts(i.owner.(*v1alpha1.ClusterServiceVersion)) {
294+
logger.Warnf("reusing existing cert %s", secret.GetName())
295+
secret = existingSecret
296+
caPEM = existingCAPEM
297+
caHash = certs.PEMSHA256(caPEM)
298+
} else if _, err := i.strategyClient.GetOpClient().UpdateSecret(secret); err != nil {
287299
logger.Warnf("could not update secret %s", secret.GetName())
288-
return nil, err
300+
return nil, nil, err
289301
}
302+
290303
} else if k8serrors.IsNotFound(err) {
291304
// Create the secret
292305
ownerutil.AddNonBlockingOwner(secret, i.owner)
293306
_, err = i.strategyClient.GetOpClient().CreateSecret(secret)
294307
if err != nil {
295308
log.Warnf("could not create secret %s", secret.GetName())
296-
return nil, err
309+
return nil, nil, err
297310
}
298311
} else {
299-
return nil, err
312+
return nil, nil, err
300313
}
301314

302315
// create Role and RoleBinding to allow the deployment to mount the Secret
@@ -323,18 +336,18 @@ func (i *StrategyDeploymentInstaller) installCertRequirementsForDeployment(deplo
323336
// Attempt an update
324337
if _, err := i.strategyClient.GetOpClient().UpdateRole(secretRole); err != nil {
325338
logger.Warnf("could not update secret role %s", secretRole.GetName())
326-
return nil, err
339+
return nil, nil, err
327340
}
328341
} else if k8serrors.IsNotFound(err) {
329342
// Create the role
330343
ownerutil.AddNonBlockingOwner(secretRole, i.owner)
331344
_, err = i.strategyClient.GetOpClient().CreateRole(secretRole)
332345
if err != nil {
333346
log.Warnf("could not create secret role %s", secretRole.GetName())
334-
return nil, err
347+
return nil, nil, err
335348
}
336349
} else {
337-
return nil, err
350+
return nil, nil, err
338351
}
339352

340353
if depSpec.Template.Spec.ServiceAccountName == "" {
@@ -369,18 +382,18 @@ func (i *StrategyDeploymentInstaller) installCertRequirementsForDeployment(deplo
369382
// Attempt an update
370383
if _, err := i.strategyClient.GetOpClient().UpdateRoleBinding(secretRoleBinding); err != nil {
371384
logger.Warnf("could not update secret rolebinding %s", secretRoleBinding.GetName())
372-
return nil, err
385+
return nil, nil, err
373386
}
374387
} else if k8serrors.IsNotFound(err) {
375388
// Create the role
376389
ownerutil.AddNonBlockingOwner(secretRoleBinding, i.owner)
377390
_, err = i.strategyClient.GetOpClient().CreateRoleBinding(secretRoleBinding)
378391
if err != nil {
379392
log.Warnf("could not create secret rolebinding with dep spec: %#v", depSpec)
380-
return nil, err
393+
return nil, nil, err
381394
}
382395
} else {
383-
return nil, err
396+
return nil, nil, err
384397
}
385398

386399
// create ClusterRoleBinding to system:auth-delegator Role
@@ -407,27 +420,27 @@ func (i *StrategyDeploymentInstaller) installCertRequirementsForDeployment(deplo
407420
if ownerutil.AdoptableLabels(existingAuthDelegatorClusterRoleBinding.GetLabels(), true, i.owner) {
408421
logger.WithFields(log.Fields{"obj": "authDelegatorCRB", "labels": existingAuthDelegatorClusterRoleBinding.GetLabels()}).Debug("adopting")
409422
if err := ownerutil.AddOwnerLabels(authDelegatorClusterRoleBinding, i.owner); err != nil {
410-
return nil, err
423+
return nil, nil, err
411424
}
412425
}
413426

414427
// Attempt an update.
415428
if _, err := i.strategyClient.GetOpClient().UpdateClusterRoleBinding(authDelegatorClusterRoleBinding); err != nil {
416429
logger.Warnf("could not update auth delegator clusterrolebinding %s", authDelegatorClusterRoleBinding.GetName())
417-
return nil, err
430+
return nil, nil, err
418431
}
419432
} else if k8serrors.IsNotFound(err) {
420433
// Create the role.
421434
if err := ownerutil.AddOwnerLabels(authDelegatorClusterRoleBinding, i.owner); err != nil {
422-
return nil, err
435+
return nil, nil, err
423436
}
424437
_, err = i.strategyClient.GetOpClient().CreateClusterRoleBinding(authDelegatorClusterRoleBinding)
425438
if err != nil {
426439
log.Warnf("could not create auth delegator clusterrolebinding %s", authDelegatorClusterRoleBinding.GetName())
427-
return nil, err
440+
return nil, nil, err
428441
}
429442
} else {
430-
return nil, err
443+
return nil, nil, err
431444
}
432445

433446
// Create RoleBinding to extension-apiserver-authentication-reader Role in the kube-system namespace.
@@ -455,26 +468,26 @@ func (i *StrategyDeploymentInstaller) installCertRequirementsForDeployment(deplo
455468
if ownerutil.AdoptableLabels(existingAuthReaderRoleBinding.GetLabels(), true, i.owner) {
456469
logger.WithFields(log.Fields{"obj": "existingAuthReaderRB", "labels": existingAuthReaderRoleBinding.GetLabels()}).Debug("adopting")
457470
if err := ownerutil.AddOwnerLabels(authReaderRoleBinding, i.owner); err != nil {
458-
return nil, err
471+
return nil, nil, err
459472
}
460473
}
461474
// Attempt an update.
462475
if _, err := i.strategyClient.GetOpClient().UpdateRoleBinding(authReaderRoleBinding); err != nil {
463476
logger.Warnf("could not update auth reader role binding %s", authReaderRoleBinding.GetName())
464-
return nil, err
477+
return nil, nil, err
465478
}
466479
} else if k8serrors.IsNotFound(err) {
467480
// Create the role.
468481
if err := ownerutil.AddOwnerLabels(authReaderRoleBinding, i.owner); err != nil {
469-
return nil, err
482+
return nil, nil, err
470483
}
471484
_, err = i.strategyClient.GetOpClient().CreateRoleBinding(authReaderRoleBinding)
472485
if err != nil {
473486
log.Warnf("could not create auth reader role binding %s", authReaderRoleBinding.GetName())
474-
return nil, err
487+
return nil, nil, err
475488
}
476489
} else {
477-
return nil, err
490+
return nil, nil, err
478491
}
479492

480493
// Update deployment with secret volume mount.
@@ -539,5 +552,5 @@ func (i *StrategyDeploymentInstaller) installCertRequirementsForDeployment(deplo
539552
// is used by the apiserver if not hot reloading.
540553
depSpec.Template.ObjectMeta.SetAnnotations(map[string]string{OLMCAHashAnnotationKey: caHash})
541554

542-
return &depSpec, nil
555+
return &depSpec, caPEM, nil
543556
}

pkg/controller/operators/olm/apiservices.go

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,6 @@ const (
2525
PackageserverName = "v1.packages.operators.coreos.com"
2626
)
2727

28-
func (a *Operator) shouldRotateCerts(csv *v1alpha1.ClusterServiceVersion) bool {
29-
now := metav1.Now()
30-
if !csv.Status.CertsRotateAt.IsZero() && csv.Status.CertsRotateAt.Before(&now) {
31-
return true
32-
}
33-
34-
return false
35-
}
36-
3728
// apiServiceResourceErrorActionable returns true if OLM can do something about any one
3829
// of the apiService errors in errs; otherwise returns false
3930
//

pkg/controller/operators/olm/operator.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1564,7 +1564,7 @@ func (a *Operator) transitionCSVState(in v1alpha1.ClusterServiceVersion) (out *v
15641564
}
15651565

15661566
// Check if it's time to refresh owned APIService certs
1567-
if a.shouldRotateCerts(out) {
1567+
if install.ShouldRotateCerts(out) {
15681568
out.SetPhaseWithEvent(v1alpha1.CSVPhasePending, v1alpha1.CSVReasonNeedsCertRotation, "owned APIServices need cert refresh", now, a.recorder)
15691569
return
15701570
}
@@ -1654,7 +1654,7 @@ func (a *Operator) transitionCSVState(in v1alpha1.ClusterServiceVersion) (out *v
16541654
}
16551655

16561656
// Check if it's time to refresh owned APIService certs
1657-
if a.shouldRotateCerts(out) {
1657+
if install.ShouldRotateCerts(out) {
16581658
out.SetPhaseWithEvent(v1alpha1.CSVPhasePending, v1alpha1.CSVReasonNeedsCertRotation, "owned APIServices need cert refresh", now, a.recorder)
16591659
return
16601660
}

test/e2e/webhook_e2e_test.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,57 @@ var _ = Describe("CSVs with a Webhook", func() {
177177
return true
178178
}, time.Minute, 5*time.Second).Should(BeTrue())
179179
})
180+
It("Reuses existing valid certs", func() {
181+
sideEffect := admissionregistrationv1.SideEffectClassNone
182+
webhook := v1alpha1.WebhookDescription{
183+
GenerateName: webhookName,
184+
Type: v1alpha1.ValidatingAdmissionWebhook,
185+
DeploymentName: genName("webhook-dep-"),
186+
ContainerPort: 443,
187+
AdmissionReviewVersions: []string{"v1beta1", "v1"},
188+
SideEffects: &sideEffect,
189+
}
190+
csv := createCSVWithWebhook(namespace.GetName(), webhook)
191+
192+
var err error
193+
cleanupCSV, err = createCSV(c, crc, csv, namespace.GetName(), false, false)
194+
Expect(err).Should(BeNil())
195+
196+
_, err = fetchCSV(crc, csv.Name, namespace.GetName(), csvSucceededChecker)
197+
Expect(err).Should(BeNil())
198+
199+
// Get the existing secret
200+
webhookSecretName := webhook.DeploymentName + "-service-cert"
201+
existingSecret, err := c.KubernetesInterface().CoreV1().Secrets(namespace.GetName()).Get(context.TODO(), webhookSecretName, metav1.GetOptions{})
202+
require.NoError(GinkgoT(), err)
203+
204+
// Modify the phase
205+
Eventually(func() bool {
206+
fetchedCSV, err := crc.OperatorsV1alpha1().ClusterServiceVersions(namespace.GetName()).Get(context.TODO(), csv.GetName(), metav1.GetOptions{})
207+
if err != nil {
208+
return false
209+
}
210+
211+
fetchedCSV.Status.Phase = v1alpha1.CSVPhasePending
212+
213+
_, err = crc.OperatorsV1alpha1().ClusterServiceVersions(namespace.GetName()).UpdateStatus(context.TODO(), fetchedCSV, metav1.UpdateOptions{})
214+
if err != nil {
215+
return false
216+
}
217+
return true
218+
}).Should(BeTrue(), "Unable to set CSV phase to Pending")
219+
220+
// Wait for webhook-operator to succeed
221+
_, err = awaitCSV(GinkgoT(), crc, namespace.GetName(), csv.GetName(), csvSucceededChecker)
222+
require.NoError(GinkgoT(), err)
223+
224+
// Get the updated secret
225+
updatedSecret, err := c.KubernetesInterface().CoreV1().Secrets(namespace.GetName()).Get(context.TODO(), webhookSecretName, metav1.GetOptions{})
226+
require.NoError(GinkgoT(), err)
227+
228+
require.Equal(GinkgoT(), existingSecret.GetAnnotations()[install.OLMCAHashAnnotationKey], updatedSecret.GetAnnotations()[install.OLMCAHashAnnotationKey])
229+
require.Equal(GinkgoT(), existingSecret.Data[install.OLMCAPEMKey], updatedSecret.Data[install.OLMCAPEMKey])
230+
})
180231
It("Fails to install a CSV if multiple Webhooks share the same name", func() {
181232
sideEffect := admissionregistrationv1.SideEffectClassNone
182233
webhook := v1alpha1.WebhookDescription{

0 commit comments

Comments
 (0)