Skip to content

Immutable secrets and consumer finalizers for TransportURL credential rotation - #608

Open
lmiccini wants to merge 1 commit into
openstack-k8s-operators:mainfrom
lmiccini:finalize-secret-rotation
Open

Immutable secrets and consumer finalizers for TransportURL credential rotation#608
lmiccini wants to merge 1 commit into
openstack-k8s-operators:mainfrom
lmiccini:finalize-secret-rotation

Conversation

@lmiccini

@lmiccini lmiccini commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Enable safe credential rotation for TransportURL by creating immutable, content-hashed secrets and coordinating cleanup across consuming operators.

Key changes:
- Create immutable transport secrets (rabbitmq-transport-url-{name}-{hash})
  during rotation so consumers cannot accidentally mutate credentials mid-rollout
- Add per-consumer finalizers (turl.openstack.org/t-{name}) on shared
  RabbitMQUser and RabbitMQVhost CRs to track which operators are still
  using the old credentials
- Let consuming operators (e.g. Nova, Heat) signal rollout completion by
  removing their finalizer, so TransportURL knows when it is safe to
  delete the old user
- Gate old user cleanup on both consumer finalizer removal and NodeSet
  secret hash synchronization — if the secret is not tracked by the
  dataplane, release immediately; otherwise wait for a full NodeSet deploy
- Prevent SecretName flip-flop by comparing content hashes before creating
  a new immutable secret
- Auto-delete orphaned RabbitMQUser CRs once all consumers have released
  their finalizers

@openshift-ci
openshift-ci Bot requested review from antonioromito and dciabrin June 22, 2026 11:19
@openshift-ci

openshift-ci Bot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: lmiccini

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@lmiccini

Copy link
Copy Markdown
Contributor Author

/test infra-operator-build-deploy-kuttl

@lmiccini
lmiccini force-pushed the finalize-secret-rotation branch from 7bda5c0 to aca54b9 Compare June 23, 2026 10:07
@centosinfra-prod-github-app

Copy link
Copy Markdown

Build failed (check pipeline). Post recheck (without leading slash)
to rerun all jobs. Make sure the failure cause has been resolved before
you rerun jobs.

https://gateway-cloud-softwarefactory.apps.ocp.cloud.ci.centos.org/zuul/t/rdoproject.org/buildset/4125cf97bf4045b491bcc003d054f746

openstack-k8s-operators-content-provider FAILURE in 11m 20s
⚠️ podified-multinode-edpm-deployment-crc SKIPPED Skipped due to failed job openstack-k8s-operators-content-provider
⚠️ cifmw-crc-podified-edpm-baremetal SKIPPED Skipped due to failed job openstack-k8s-operators-content-provider

@lmiccini

Copy link
Copy Markdown
Contributor Author

recheck

@centosinfra-prod-github-app

Copy link
Copy Markdown

Build failed (check pipeline). Post recheck (without leading slash)
to rerun all jobs. Make sure the failure cause has been resolved before
you rerun jobs.

https://gateway-cloud-softwarefactory.apps.ocp.cloud.ci.centos.org/zuul/t/rdoproject.org/buildset/a3133523ffb44b82a68d14705892e330

openstack-k8s-operators-content-provider FAILURE in 10m 14s
⚠️ podified-multinode-edpm-deployment-crc SKIPPED Skipped due to failed job openstack-k8s-operators-content-provider
⚠️ cifmw-crc-podified-edpm-baremetal SKIPPED Skipped due to failed job openstack-k8s-operators-content-provider

@lmiccini

Copy link
Copy Markdown
Contributor Author

recheck

@lmiccini
lmiccini force-pushed the finalize-secret-rotation branch from aca54b9 to 98d388a Compare July 14, 2026 15:20
@centosinfra-prod-github-app

Copy link
Copy Markdown

Build failed (check pipeline). Post recheck (without leading slash)
to rerun all jobs. Make sure the failure cause has been resolved before
you rerun jobs.

https://gateway-cloud-softwarefactory.apps.ocp.cloud.ci.centos.org/zuul/t/rdoproject.org/buildset/bcf6c4934a4d4ea2aef87e126f81d1cd

openstack-k8s-operators-content-provider FAILURE in 11m 48s
⚠️ podified-multinode-edpm-deployment-crc SKIPPED Skipped due to failed job openstack-k8s-operators-content-provider
⚠️ cifmw-crc-podified-edpm-baremetal SKIPPED Skipped due to failed job openstack-k8s-operators-content-provider

@lmiccini

Copy link
Copy Markdown
Contributor Author

recheck

@lmiccini

Copy link
Copy Markdown
Contributor Author

/test infra-operator-build-deploy-kuttl

2 similar comments
@lmiccini

Copy link
Copy Markdown
Contributor Author

/test infra-operator-build-deploy-kuttl

@lmiccini

Copy link
Copy Markdown
Contributor Author

/test infra-operator-build-deploy-kuttl

@lmiccini
lmiccini force-pushed the finalize-secret-rotation branch from 98d388a to 668c7bf Compare July 31, 2026 05:11

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.

The NodeSetSynced field is still present in the status type and is still cleared in releasePendingUser, but the two-phase logic that set it to "false" has been removed. It's only ever set to "" now. Is it still needed?


if err := r.Create(ctx, secret); err != nil {
if !k8s_errors.IsAlreadyExists(err) {
return nil, "", fmt.Errorf("failed to create immutable transport secret %s: %w", secret.Name, err)

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.

When r.Create(ctx, secret) returns AlreadyExists, the function returns the local secret object (no server metadata) and the locally-computed contentHash. If a prior reconcile already created the secret but the status patch failed, this works by accident because the name is deterministic. However, it would be cleaner to Get the existing secret on AlreadyExists to confirm the data matches, avoiding a silent mismatch if hash collisions ever occur:

  if err := r.Create(ctx, secret); err != nil {
      if !k8s_errors.IsAlreadyExists(err) {
          return nil, "", fmt.Errorf("failed to create immutable transport secret %s: %w", secret.Name, err)
      }
      // Fetch the existing secret to confirm content matches
      if err := r.Get(ctx, types.NamespacedName{Name: secret.Name, Namespace: secret.Namespace}, secret); err != nil {
          return nil, "", fmt.Errorf("failed to get existing immutable transport secret %s: %w", secret.Name, err)
      }
  }

Comment on lines +581 to +585
if instance.Status.SecretName != "" && instance.Status.SecretName != secret.Name {
instance.Status.PreviousSecretName = instance.Status.SecretName
}
instance.Status.SecretName = secret.Name
instance.Status.SecretHash = hash

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.

This sets PreviousSecretName without a corresponding PreviousRabbitmqUserRef, which then triggers the orphan cleanup block at line 624. This block checks for consumer finalizers on a secret that was never part of a consumer-driven rotation. The state machine has two independent cleanup paths (lines 608-618 for rotation, lines 624-642 for orphans) that overlap and can interact in unexpected ways. Should we either:

  • Not setting PreviousSecretName in the normal path (only set it during rotation), or
  • Adding a comment explaining this is intentional for cleaning up stale immutable secrets post-rotation?

… rotation

Enable safe credential rotation for TransportURL by creating immutable,
content-hashed secrets and coordinating cleanup across consuming operators.

Key changes:
- Create immutable transport secrets (rabbitmq-transport-url-{name}-{hash})
  during rotation so consumers cannot accidentally mutate credentials mid-rollout
- Add per-consumer finalizers (turl.openstack.org/t-{name}) on shared
  RabbitMQUser and RabbitMQVhost CRs to track which operators are still
  using the old credentials
- Let consuming operators (e.g. Nova, Heat) signal rollout completion by
  removing their finalizer, so TransportURL knows when it is safe to
  delete the old user
- Gate old user cleanup on both consumer finalizer removal and NodeSet
  secret hash synchronization — if the secret is not tracked by the
  dataplane, release immediately; otherwise wait for a full NodeSet deploy
- Prevent SecretName flip-flop by comparing content hashes before creating
  a new immutable secret
- Auto-delete orphaned RabbitMQUser CRs once all consumers have released
  their finalizers

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@lmiccini
lmiccini force-pushed the finalize-secret-rotation branch from 668c7bf to 43f4553 Compare August 5, 2026 08:16
@openshift-ci

openshift-ci Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

@lmiccini: The following test failed, say /retest to rerun all failed tests or /retest-required to rerun all mandatory failed tests:

Test name Commit Details Required Rerun command
ci/prow/precommit-check 43f4553 link true /test precommit-check

Full PR test history. Your PR dashboard.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. I understand the commands that are listed here.

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.

2 participants