Skip to content

K8SPXC-1848: update existing cert-manager Certificate CRs on operator upgrade#2414

Open
larainema wants to merge 8 commits into
percona:mainfrom
larainema:fix/update-cert-manager-certificate-specs
Open

K8SPXC-1848: update existing cert-manager Certificate CRs on operator upgrade#2414
larainema wants to merge 8 commits into
percona:mainfrom
larainema:fix/update-cert-manager-certificate-specs

Conversation

@larainema

Copy link
Copy Markdown
Contributor

Problem

createSSLByCertManager() uses r.client.Create() with an IsAlreadyExists guard for all three cert-manager Certificate resources (CA, ssl, ssl-internal). When the Certificate CR already exists, the function silently skips it — any spec changes introduced in newer operator versions are never applied.

The most significant impact is the CA certificate duration: clusters originally deployed with operator < 1.15.0 still carry duration: 8760h (1 year) instead of the current default 26280h (3 years), because the Certificate CR was never updated after the operator upgrade.

Fixes #2413

Root Cause

err := r.client.Create(ctx, caCert)
if err != nil && !k8serr.IsAlreadyExists(err) {
    return fmt.Errorf("create CA certificate: %v", err)
}

The same pattern is used for all three Certificate CRs. On AlreadyExists, the new desired spec is discarded.

Fix

Add a createOrUpdateCertificate() helper that:

  1. Attempts Create first (fast path for new clusters)
  2. On AlreadyExists: fetches the existing Certificate CR
  3. Compares Spec and Labels using reflect.DeepEqual
  4. If they differ, copies the desired spec/labels onto the existing object and calls Update

All three call sites in createSSLByCertManager now use this helper.

Affected Fields (now reconciled on upgrade)

  • spec.duration — CA validity (most impactful: 1yr → 3yr)
  • spec.renewBefore
  • spec.dnsNames (SANs)
  • spec.commonName
  • labels

Related

@CLAassistant

CLAassistant commented Mar 31, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@it-percona-cla

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@larainema larainema changed the title fix: update existing cert-manager Certificate CRs on operator upgrade K8SPXC: update existing cert-manager Certificate CRs on operator upgrade Mar 31, 2026
Comment thread pkg/controller/pxc/tls.go Outdated
Comment thread pkg/controller/pxc/tls.go
@egegunes egegunes added this to the v1.20.0 milestone Apr 1, 2026
@egegunes egegunes changed the title K8SPXC: update existing cert-manager Certificate CRs on operator upgrade K8SPXC-1848: update existing cert-manager Certificate CRs on operator upgrade Apr 1, 2026
@larainema larainema force-pushed the fix/update-cert-manager-certificate-specs branch from 65f04b9 to 6feb7f0 Compare April 1, 2026 06:55

@egegunes egegunes left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code changes LGTM.

We need to add a case into e2e tests though, probably to tls-issue-cert-manager:

  • Patch PerconaXtraDBCluster to change something under spec.tls, potentially the duration.
  • Check if certificates are updated.
  • Ensure cluster is still available.

@eleo007 wdyt?

@egegunes egegunes left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@larainema please add a case into tls-issue-cert-manager e2e tests to check certificate updates

@pull-request-size pull-request-size Bot added size/L 100-499 lines and removed size/M 30-99 lines labels Apr 6, 2026
@larainema larainema force-pushed the fix/update-cert-manager-certificate-specs branch 4 times, most recently from 41d97bd to d346d57 Compare April 8, 2026 06:33
Comment thread pkg/controller/pxc/tls.go Outdated
Comment thread pkg/controller/pxc/tls.go
Comment thread pkg/controller/pxc/tls.go Outdated
Comment thread pkg/controller/pxc/tls.go
@larainema larainema force-pushed the fix/update-cert-manager-certificate-specs branch from d346d57 to eb96e2e Compare April 9, 2026 11:22
@pull-request-size pull-request-size Bot added size/XL 500-999 lines and removed size/L 100-499 lines labels Apr 9, 2026
@larainema larainema force-pushed the fix/update-cert-manager-certificate-specs branch 3 times, most recently from 3fe1e9b to 2647bfc Compare April 13, 2026 11:14
createSSLByCertManager() uses r.client.Create() with an IsAlreadyExists
guard, so any spec changes (duration, renewBefore, SANs, labels)
introduced in newer operator versions are never applied to Certificate
CRs that were created by an older version.

The most significant impact is the CA certificate duration: clusters
originally deployed with operator < 1.15.0 still carry duration=8760h
(1 year) instead of the current default of 26280h (3 years).

Replace the create-and-ignore pattern with a createOrUpdateCertificate
helper that attempts Create first, and on AlreadyExists fetches the
existing Certificate, compares Spec and Labels, and issues an Update
when they differ.

Fixes percona#2413
@larainema larainema force-pushed the fix/update-cert-manager-certificate-specs branch from 2647bfc to 3c5b880 Compare April 13, 2026 23:00
@larainema

Copy link
Copy Markdown
Contributor Author

@larainema please add a case into tls-issue-cert-manager e2e tests to check certificate updates

Added. The test now:

Deploys the cluster with certValidityDuration: 1160h and caValidityDuration: 16280h
Patches [spec.tls] to change durations and add a new SAN (new-san.example.com)
Verifies both CA and SSL Certificate CRs are updated via compare_kubectl
Confirms cluster is still available via wait_cluster_consistency and check_verify_identity

@hors hors added the community label Apr 28, 2026
@mayankshah1607

Copy link
Copy Markdown
Member

Hi @larainema please resolve the conflicts on this PR

Resolve conflicts with percona#2412 (CA rotation) which has been merged to main:
- pkg/controller/pxc/tls.go: combine reconcileCARotation + naming helpers
  from percona#2412 with reconcileCertManagerCertificateSpecs + build helpers
  from this PR. Build helpers now use the shared naming constants.
- e2e-tests/tls-issue-cert-manager/run: keep cert-manager readiness
  check, inline cluster creation, and full-pod restart for CA rotation
  from percona#2412; preserve certificate spec update test from this PR.
@gkech

gkech commented May 12, 2026

Copy link
Copy Markdown
Contributor

hello @larainema can you please resolve conflicts and check e2e tests results?

Copilot AI review requested due to automatic review settings May 14, 2026 00:37

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

This PR makes the operator reconcile the spec of cert-manager Certificate CRs on subsequent reconciles (e.g. after operator upgrade or CR TLS changes), rather than only creating them once. The cert creation logic is also refactored into smaller builder helpers.

Changes:

  • Refactor createSSLByCertManager to use new helpers (resolveTLSCertConfig, buildCACertificate, buildSSLCertificate, buildSSLInternalCertificate) and a generic createOrUpdateCertificate based on controllerutil.CreateOrUpdate.
  • Add reconcileCertManagerCertificateSpecs invoked when the credentials secret already exists, to update existing Certificate CR specs in place.
  • Add an e2e test variant (*-updated.yml config + new compare files) that changes durations / SANs and asserts the Certificate CRs are updated.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
pkg/controller/pxc/tls.go Refactors cert-manager certificate creation and adds in-place spec reconciliation.
e2e-tests/tls-issue-cert-manager/run Adds an "update TLS spec" step and asserts CA/SSL Certificate CRs are updated.
e2e-tests/tls-issue-cert-manager/conf/some-name-tls-issue-updated.yml New CR variant with changed durations and an extra SAN.
e2e-tests/tls-issue-cert-manager/compare/certificate_some-name-tls-issue-ssl-updated.yml Expected SSL Certificate after update (gen=2, new SAN, new duration).
e2e-tests/tls-issue-cert-manager/compare/certificate_some-name-tls-issue-ca-cert-updated.yml Expected CA Certificate after update (gen=2, new duration).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread pkg/controller/pxc/tls.go Outdated
Comment on lines +60 to +64
if cr.Spec.TLS != nil && cr.Spec.TLS.IssuerConf != nil {
return fmt.Errorf("reconcile cert-manager certificates: %w", err)
}
log := logf.FromContext(ctx)
log.Info("Failed to reconcile cert-manager certificates, skipping (certs may be manually managed)", "error", err)
Comment thread pkg/controller/pxc/tls.go
Comment on lines +276 to +290
_, err := controllerutil.CreateOrUpdate(ctx, r.client, existing, func() error {
existing.Labels = desired.Labels

// Preserve IssuerRef.Group if the API server defaulted it
// (cert-manager >= 1.19 defaults empty group to "cert-manager.io").
// Without this, every reconcile would see a diff and trigger
// an unnecessary Update, which increments the Certificate's
// generation and may cause cert-manager to re-issue.
if desired.Spec.IssuerRef.Group == "" && existing.Spec.IssuerRef.Group != "" {
desired.Spec.IssuerRef.Group = existing.Spec.IssuerRef.Group
}

existing.Spec = desired.Spec
return nil
})
Comment thread pkg/controller/pxc/tls.go
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
Comment thread e2e-tests/tls-issue-cert-manager/run Outdated
Comment on lines +143 to +144
sleep 30

Copilot AI review requested due to automatic review settings May 15, 2026 09:24

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.

Comment thread pkg/controller/pxc/tls.go Outdated
Comment on lines +275 to +284
// Preserve IssuerRef.Group if the API server defaulted it
// (cert-manager >= 1.19 defaults empty group to "cert-manager.io").
// Without this, every reconcile would see a diff and trigger
// an unnecessary Update, which increments the Certificate's
// generation and may cause cert-manager to re-issue.
desiredSpec := desired.Spec
if desiredSpec.PrivateKey != nil {
privateKey := *desiredSpec.PrivateKey
desiredSpec.PrivateKey = &privateKey
}
Comment thread pkg/controller/pxc/tls.go Outdated
Comment on lines +289 to +296
// A CA rotation is triggered by patching rotationPolicy=Always.
// Keep that explicit override instead of resetting it on the next reconcile.
if existing.Spec.PrivateKey != nil && desiredSpec.PrivateKey != nil &&
desiredSpec.PrivateKey.RotationPolicy == cm.RotationPolicyNever &&
existing.Spec.PrivateKey.RotationPolicy != "" &&
existing.Spec.PrivateKey.RotationPolicy != desiredSpec.PrivateKey.RotationPolicy {
desiredSpec.PrivateKey.RotationPolicy = existing.Spec.PrivateKey.RotationPolicy
}
Comment thread pkg/controller/pxc/tls.go
Comment on lines +468 to +480
func (r *ReconcilePerconaXtraDBCluster) reconcileCertManagerCertificateSpecs(ctx context.Context, cr *api.PerconaXtraDBCluster) error {
// Only reconcile if the SSL certificate was created by cert-manager.
sslCert := &cm.Certificate{}
if err := r.client.Get(ctx, types.NamespacedName{
Namespace: cr.Namespace,
Name: naming.SSLCertificateName(cr),
}, sslCert); err != nil {
if k8serr.IsNotFound(err) {
// Certificate CR doesn't exist — certs were created manually.
return nil
}
return fmt.Errorf("get ssl certificate: %w", err)
}
Comment thread pkg/controller/pxc/tls.go
}
_, err := controllerutil.CreateOrUpdate(ctx, r.client, existing, func() error {
existing.Labels = mergeCertificateLabels(existing.Labels, desired.Labels)

@larainema

Copy link
Copy Markdown
Contributor Author

Follow-up pushed in 9106e17.

Resolved the latest review feedback:

  • return real cert-manager reconciliation errors instead of swallowing them; manual cert detection still returns nil inside reconcileCertManagerCertificateSpecs for missing Certificate/Issuer objects
  • merge Certificate labels, preserve defaulted issuerRef.group, and keep an explicit privateKey.rotationPolicy override so CA rotation is not immediately reverted
  • replace the fixed sleep in tls-issue-cert-manager e2e with bounded Certificate generation polling

Local checks passed:

  • go test -run TestCreateOrUpdateCertificatePreservesExistingFields ./pkg/controller/pxc
  • DISABLE_TELEMETRY=true KUBEBUILDER_ASSETS=<envtest 1.34.1> go test -timeout 10m ./pkg/controller/pxc
  • git diff --check
  • bash -n e2e-tests/tls-issue-cert-manager/run

GitHub Actions and Jenkins restarted on the new head; Jenkins is PR-2414/18.

Copilot AI review requested due to automatic review settings May 18, 2026 12:51

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review is ineligible. To be eligible to request a review, you need a paid Copilot license, or your organization must enable Copilot code review.

@JNKPercona

Copy link
Copy Markdown
Collaborator
Test Name Result Time
auto-tuning-8-0 failure 01:09:18
allocator-8-0 failure 01:09:45
allocator-8-4 failure 01:09:44
backup-storage-tls-8-0 passed 00:00:00
cross-site-8-0 failure 00:54:02
cross-site-proxysql-8-0 failure 00:54:53
cross-site-proxysql-8-4 failure 00:54:27
custom-users-8-0 failure 01:06:58
demand-backup-cloud-8-0 failure 00:11:51
demand-backup-cloud-8-4 failure 00:11:20
demand-backup-cloud-pxb-8-0 failure 00:10:42
demand-backup-encrypted-with-tls-5-7 failure 01:09:57
demand-backup-encrypted-with-tls-8-0 failure 01:10:32
demand-backup-encrypted-with-tls-8-4 failure 01:06:33
demand-backup-encrypted-with-tls-pxb-5-7 failure 01:06:50
demand-backup-encrypted-with-tls-pxb-8-0 failure 01:05:28
demand-backup-encrypted-with-tls-pxb-8-4 failure 00:50:09
demand-backup-8-0 failure 00:22:13
demand-backup-flow-control-8-0 failure 00:36:33
demand-backup-flow-control-8-4 failure 00:39:07
demand-backup-parallel-8-0 failure 00:39:21
demand-backup-parallel-8-4 failure 00:39:21
demand-backup-without-passwords-8-0 failure 00:39:47
demand-backup-without-passwords-8-4 failure 00:39:42
extra-pvc-8-0 failure 00:44:45
haproxy-5-7 failure 00:45:23
haproxy-8-0 failure 00:47:31
haproxy-8-4 failure 00:43:57
init-deploy-5-7 passed 00:00:00
init-deploy-8-0 passed 00:00:00
limits-8-0 failure 00:45:18
monitoring-2-0-8-0 failure 00:01:22
monitoring-pmm3-8-0 failure 00:16:27
monitoring-pmm3-8-4 failure 00:10:49
one-pod-5-7 failure 00:10:01
one-pod-8-0 failure 00:10:01
pitr-8-0 failure 00:01:23
pitr-8-4 failure 00:01:34
pitr-pxb-8-0 failure 00:01:23
pitr-pxb-8-4 failure 00:01:19
pitr-gap-errors-8-0 failure 00:01:23
pitr-gap-errors-8-4 failure 00:01:26
proxy-protocol-8-0 passed 00:00:00
proxy-switch-8-0 failure 00:01:18
proxysql-sidecar-res-limits-8-0 passed 00:00:00
proxysql-scheduler-8-0 failure 00:00:58
pvc-resize-5-7 failure 00:01:26
pvc-resize-8-0 failure 00:02:31
pvc-auto-resize-5-7 failure 00:54:06
pvc-auto-resize-8-0 passed 00:19:45
recreate-8-0 passed 00:00:00
restore-to-encrypted-cluster-8-0 failure 00:55:30
restore-to-encrypted-cluster-8-4 failure 00:45:56
restore-to-encrypted-cluster-pxb-8-0 failure 00:45:16
restore-to-encrypted-cluster-pxb-8-4 passed 00:00:00
scaling-proxysql-8-0 passed 00:00:00
scaling-8-0 passed 00:00:00
scheduled-backup-5-7 passed 00:00:00
scheduled-backup-8-0 passed 00:00:00
scheduled-backup-8-4 passed 00:00:00
security-context-8-0 passed 00:00:00
smart-update1-8-0 passed 00:00:00
smart-update1-8-4 passed 00:00:00
smart-update2-8-0 passed 00:00:00
smart-update2-8-4 passed 00:00:00
smart-update3-8-0 passed 00:00:00
sst-retry-limit-8-0 failure 00:14:57
sst-retry-limit-8-4 failure 00:42:24
storage-8-0 passed 00:00:00
tls-issue-cert-manager-ref-8-0 passed 00:00:00
tls-issue-cert-manager-8-0 failure 00:09:49
tls-issue-self-8-0 failure 00:47:19
upgrade-consistency-8-0 failure 00:36:46
upgrade-consistency-8-4 passed 00:15:25
upgrade-haproxy-5-7 passed 00:00:00
upgrade-haproxy-8-0 passed 00:00:00
upgrade-proxysql-5-7 passed 00:00:00
upgrade-proxysql-8-0 passed 00:20:55
users-5-7 failure 00:42:43
users-8-0 passed 00:34:30
users-scheduler-8-4 failure 00:40:44
validation-hook-8-0 passed 00:00:00
Summary Value
Tests Run 82/82
Job Duration 05:45:21
Total Test Time 32:15:22

commit: 5e68ee7
image: perconalab/percona-xtradb-cluster-operator:PR-2414-5e68ee7d

@egegunes egegunes modified the milestones: v1.20.0, v1.21.0 Jun 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

cert-manager Certificate CRs are never updated after initial creation (create-only semantics)

9 participants