Application Credentials (ACs) provide an alternative authentication mechanism for OpenStack services. Instead of using the service user's password from osp-secret, services authenticate using a Keystone Application Credential: a time-restricted, project-scoped credential with its own ID and secret.
This serves two goals:
-
Scoped, rotatable credentials: ACs have a defined expiration and can be automatically rotated without human intervention. Each AC can be restricted to specific roles and access rules.
-
Password rotation enabler: Once AC is enabled for a service, the service authenticates via
v3applicationcredentialauth type and no longer uses the password fromosp-secretat runtime. This means the password inosp-secretcan be rotated without disrupting running services. The keystone-operator AC controller still reads the password fromosp-secretwhen creating or rotating ACs (to authenticate as the service user in Keystone), but it fetches it on-demand at reconcile time, so a rotated password is picked up automatically.
Three layers of operators are involved:
┌─────────────────────────────────────────────────────┐
│ openstack-operator │
│ - Owns KeystoneApplicationCredential CRs │
│ - Creates/patches AC CRs │
│ - Reads Status.SecretName from AC CR │
│ - Sets ApplicationCredentialSecret on service CRs │
└──────────────────────────┬──────────────────────────┘
│ creates AC CR
▼
┌─────────────────────────────────────────────────────┐
│ keystone-operator │
│ - Reconciles KeystoneApplicationCredential CRs │
│ - Creates AC in Keystone (API call) │
│ - Creates K8s Secret with AC_ID and AC_SECRET │
│ - Manages rotation │
│ - Sets Status on AC CRs │
└──────────────────────────┬──────────────────────────┘
│ AC secret name flows to service CR
▼
┌─────────────────────────────────────────────────────┐
│ service operators │
│ - Watch the named AC secret via field indexer │
│ - Read AC_ID and AC_SECRET from the secret │
│ - Generate service config with auth_type = │
│ v3applicationcredential │
└─────────────────────────────────────────────────────┘
Set on the OpenStackControlPlane CR under spec.applicationCredential:
spec:
applicationCredential:
enabled: true
expirationDays: 730 # default: 730 (min 2)
gracePeriodDays: 364 # default: 364 (min 1, must be < expirationDays)
roles: # default: [admin, service]
- admin
- service
unrestricted: false # default: falseEach service has an applicationCredential section that can override global defaults. In order to enable AC for a service, both global and service-specific fields must be enabled. Omitted fields inherit from the global config:
spec:
barbican:
applicationCredential:
enabled: true
expirationDays: 365 # override: shorter expiry for barbican
roles: # override: service role only
- service- Telemetry has three independent sub-services (Aodh, Ceilometer, CloudKitty), each with its own AC CR and config section (
applicationCredentialAodh,applicationCredentialCeilometer,applicationCredentialCloudKitty). - Ironic creates two AC CRs: one for
ironicand one forironic-inspector. - Glance creates a single AC CR shared across all GlanceAPI instances.
- EDPM AC consumers are Nova and Ceilometer. These services have an additional operational gap because the credentials are deployed to EDPM nodes as static configuration and are not picked up automatically after rotation, so the manual redeployment during grace period window is neccesary.
openstack-operatorreconciles theOpenStackControlPlaneCR.- For each service with AC enabled, it calls
EnsureApplicationCredentialForService. - If the service is ready and no AC CR exists,
reconcileApplicationCredentialcreates aKeystoneApplicationCredentialCR (for example,ac-barbican) with the merged config. The CR is owned by theOpenStackControlPlane. keystone-operatorreconciles the AC CR:- Authenticates as the service user using the password from
osp-secret. - Creates an Application Credential in Keystone with the specified roles, expiration, and access rules.
- Creates a K8s Secret named
<ac-cr-name>-secret(for example,ac-barbican-secret) containingAC_IDandAC_SECRET. - Sets
Status.SecretName,Status.ACID, andStatus.ExpiresAton the AC CR. - Adds a protection finalizer (
openstack.org/ac-secret-protection) to the Secret.
- Authenticates as the service user using the password from
- The
openstack-operatorOwnsthe AC CR, so whenkeystone-operatorupdates its status (sets Ready, populatesStatus.SecretName), controller-runtime triggers a reconcile of the owningOpenStackControlPlane. Theopenstack-operatorsees the AC CR is ready, readsStatus.SecretName, and sets it on the service CR (for example,barbican.spec.auth.applicationCredentialSecret). - The service operator (for example,
barbican-operator) watches the named Secret via a field indexer, readsAC_IDandAC_SECRET, and generates config withauth_type = v3applicationcredential.
Rotation is triggered by the keystone-operator AC controller during reconcile.
The controller first evaluates needsRotation(), which returns true in these cases:
- No AC exists yet —
Status.ACIDis empty (initial creation). - Security-critical fields changed —
SecurityHash(computed fromroles,accessRules,unrestricted) differs from the stored hash. This triggers immediate rotation. - Grace period reached —
time.Now()is pastExpiresAt - GracePeriodDays.
In addition, reconcileNormal() performs a Secret existence check:
4. AC Secret missing — if Status.SecretName is set but the referenced Secret does not exist, the controller forces rotation.
On rotation:
- A new AC is created in Keystone with a fresh random suffix in the name.
- The existing K8s Secret name stays the same (
<ac-cr-name>-secret) and is updated with the newAC_IDandAC_SECRET. - The old AC in Keystone is not revoked, it expires naturally.
- The Secret content change triggers the service operator to reconcile and regenerate config.
- When rotating an existing AC (not initial create), the controller emits a Kubernetes event on the
KeystoneApplicationCredentialCR:ApplicationCredentialRotated.
For engineering and testing purposes, the following approaches can be used to trigger rotation or recreation:
Change one of the security-critical fields in the AppCred spec:
rolesaccessRulesunrestricted
This changes the computed securityHash and causes a normal immediate rotation on the next reconcile.
Patching status.expiresAt to a timestamp in the past can be used to make the controller consider the credential inside the grace window and perform a normal rotation path on the next reconcile.
This differs from deleting the Secret because:
- the existing Secret object remains present,
- service operators do not transiently reconcile into
ErrACSecretNotFound, - the rotation proceeds through the standard in-place Secret update path.
The referenced AC Secret is protected by the openstack.org/ac-secret-protection finalizer, so it is not normally removable with a simple delete.
If the Secret does become absent (for example, after intentionally removing/bypassing the protection finalizer), keystone-operator detects that Status.SecretName points to a missing Secret and forces rotation/recreation.
Because this introduces a window where service operators may reconcile into ErrACSecretNotFound, this is better treated as an engineering recovery path than as a normal rotation trigger.
Deleting the KeystoneApplicationCredential CR is a different path.
During delete, keystone-operator:
- removes the Secret protection finalizer,
- removes its own AppCred CR finalizer,
- does not revoke the Keystone-side application credential.
If the service remains enabled, openstack-operator recreates the AppCred CR on a later reconcile.
This is closer to delete-and-recreate than to a normal in-place rotation.
AC CRs are deleted in two scenarios:
- AC disabled — When
applicationCredential.enabledis set tofalse(globally or per-service),EnsureApplicationCredentialForServicedetects!isACEnabledand deletes the AC CR if it exists. - Service disabled — When a service is disabled (for example,
spec.barbican.enabled: false),CleanupApplicationCredentialForServiceunconditionally deletes the AC CR regardless of the AC enabled flag.
When the AC CR is deleted, the keystone-operator:
- Removes the protection finalizer from the Secret.
- Removes its own finalizer from the CR.
- Does not revoke the AC in Keystone; it expires naturally.
Because the Secret is owned by the AC CR (owner reference), it is garbage-collected after the AC CR is deleted and the protection finalizer is removed.
Note: If immediate Keystone-side cleanup is needed (for example, a suspected credential leak), the AC can be manually deleted from Keystone:
openstack application credential delete <AC_ID>Be careful: deleting an AC that is still in use, especially by EDPM, will cause authentication failures.
The service falls back to password auth on the next reconcile when ApplicationCredentialSecret is cleared.
Service operators follow a consistent pattern:
- Field indexer on
.spec.auth.applicationCredentialSecretallows lookup of which service CRs reference a given Secret. - Secret watch with
ResourceVersionChangedPredicatetriggers reconciliation when the AC Secret content changes. - Config generation reads
AC_IDandAC_SECRETfrom the Secret and renders the service config withauth_type = v3applicationcredential. - Two distinct states to distinguish:
- AC not configured (
ApplicationCredentialSecretis empty on the service CR) — the config template renders withauth_type = password. This is the normal state when AC is disabled or was never enabled. - AC Secret missing (
ApplicationCredentialSecretis set but the referenced Secret doesn't exist) — the service operator returnsErrACSecretNotFoundand the reconcile fails. Existing pods continue running with old config until they are restarted.
- AC not configured (
List all AppCred CRs in the control plane namespace:
oc get appcred -n openstackExample output:
NAME ACID SECRETNAME LASTROTATED ROTATIONELIGIBLE STATUS MESSAGE
ac-barbican d38dc4310fbf4601bbe9f4234eb24114 ac-barbican-secret 2026-03-12T08:23:58Z 2026-03-15T08:23:58Z True Setup complete
ac-ceilometer 0e0e7c8f243c4ce8913fd709d44c0104 ac-ceilometer-secret 2028-02-29T13:35:06Z True Setup complete
ac-cinder b8c7fb9d3abc4ce18727a56f870c9a18 ac-cinder-secret 2027-03-03T13:04:41Z True Setup complete
ac-glance da20e5b59d0f4227938046c60857cb62 ac-glance-secret 2026-03-12T08:31:42Z 2027-03-13T08:31:42Z True Setup complete
ac-ironic ec6bbfb36b8041cbad96f4360a84cd71 ac-ironic-secret 2027-03-03T13:04:42Z True Setup complete
ac-ironic-inspector 81aebe21c2b94a2b89caec4b1fe652c1 ac-ironic-inspector-secret 2027-03-03T13:04:45Z True Setup complete
ac-octavia 0dd0b1b7247743b980d909334a4ea8ad ac-octavia-secret 2027-03-07T09:39:19Z True Setup complete
The main columns are:
NAME— AppCred CR nameACID— Keystone Application Credential IDSECRETNAME— Kubernetes Secret currently holdingAC_IDandAC_SECRETLASTROTATED— last successful rotation timeROTATIONELIGIBLE— time when automatic rotation becomes eligibleSTATUS/MESSAGE— summarized readiness state
oc describe appcred -n openstack ac-barbican
oc get appcred -n openstack ac-barbican -o yamlExample CR:
apiVersion: keystone.openstack.org/v1beta1
kind: KeystoneApplicationCredential
metadata:
name: ac-barbican
namespace: openstack
finalizers:
- openstack.org/applicationcredential
ownerReferences:
- apiVersion: core.openstack.org/v1beta1
kind: OpenStackControlPlane
name: openstack-galera-network-isolation
controller: true
blockOwnerDeletion: true
spec:
expirationDays: 5
gracePeriodDays: 2
passwordSelector: BarbicanPassword
roles:
- service
secret: osp-secret
unrestricted: false
userName: barbican
status:
acID: d38dc4310fbf4601bbe9f4234eb24114
secretName: ac-barbican-secret
createdAt: "2026-03-12T08:23:58Z"
expiresAt: "2026-03-17T08:23:58Z"
lastRotated: "2026-03-12T08:23:58Z"
rotationEligibleAt: "2026-03-15T08:23:58Z"
observedGeneration: 1
conditions:
- type: Ready
status: "True"
reason: Ready
message: Setup complete
- type: KeystoneAPIReady
status: "True"
reason: Ready
message: KeystoneAPI ready
- type: KeystoneApplicationCredentialReady
status: "True"
reason: Ready
message: ApplicationCredential readyOn rotation, keystone-operator emits a Kubernetes event on the AppCred CR with reason ApplicationCredentialRotated.
Examples:
oc get events -n openstack --sort-by=.lastTimestamp | grep ApplicationCredentialRotatedOutput:
8s Normal ApplicationCredentialRotated keystoneapplicationcredential/ac-barbican ApplicationCredential 'ac-barbican' (user: barbican) rotated - EDPM nodes may need credential updates. Previous expiration: 2001-05-19T00:00:00Z, New expiration: 2026-03-18T08:24:18ZFor EDPM consumers such as Nova and Ceilometer, these events are particularly important because the rotated credential still needs to be propagated to the data plane through redeployment.
The AppCred CR does not expose a dedicated phase/status enum such as Creating, Rotating, or Failed. Instead, it exposes conditions and timestamps.
The main conditions are:
ReadyKeystoneAPIReadyKeystoneApplicationCredentialReady
Typical interpretations are:
-
Ready / healthy
Ready=TrueKeystoneAPIReady=TrueKeystoneApplicationCredentialReady=True
-
Waiting for Keystone
KeystoneAPIReady=False- indicates KeystoneAPI is not yet ready or reachable
-
Application credential setup failed
KeystoneApplicationCredentialReady=False- inspect the condition reason/message and controller logs
-
Rotated
- there is no separate
Rotatedcondition - use
lastRotated,rotationEligibleAt,acID, and events such asApplicationCredentialRotated
- there is no separate
-
Not reconciled to latest spec yet
- compare
metadata.generationwithstatus.observedGeneration
- compare
-
Mutable secrets — Rotation overwrites the existing Secret in place. If propagation fails mid-rotation, the old credential is gone and there is no rollback path.
-
No service-side finalizers — Service operators do not add finalizers to the AC Secret. The
keystone-operatoradds its own protection finalizer (openstack.org/ac-secret-protection) to prevent accidental deletion, but there is no service-side finalizer to coordinate credential handoff during rotation. On the control plane this is self-healing — if an AC CR or its Secret is deleted, theopenstack-operatordetects the change and recreates it almost immediately. The real gap is that the existing Secret is overwritten in place before the service has confirmed it picked up the new credentials, with no previous version to fall back to. -
No Keystone revocation — Old ACs are not revoked in Keystone after rotation or CR deletion. They persist until they expire, which can be a long time.
-
EDPM gap — EDPM nodes have AC credentials deployed as static config files via Ansible. There is no watch or reconcile loop on the node — if the AC rotates, EDPM nodes continue using the old credentials silently until a manual
OpenStackDataPlaneNodeSetdeployment is triggered. If the old AC expires in Keystone before the nodes are updated, services on those nodes will start getting authentication errors. This currently applies to Nova and Ceilometer.
The following changes have been agreed upon for the next iteration:
Each rotation will create a new immutable K8s Secret with a unique name that includes the first 5 characters of the Keystone AC ID (for example, ac-barbican-a1b2c-secret). The old Secret is preserved until all consumers confirm they have switched to the new credential. This enables safe rollback — if propagation fails, the old Secret and credential remain valid and in use.
When a service operator starts using an AC Secret, it adds a service-specific finalizer to the Secret. The keystone-operator only deletes the old Secret and revokes the old AC in Keystone after all service finalizers have been removed, confirming that every consumer has switched to the new credential.
AC Secrets will get a label identifying the owning service (for example, app-credential-service: barbican). This allows easy discovery and cleanup of all AC Secrets for a specific service.
With the finalizer handshake in place, it becomes safe to revoke old ACs in Keystone after all consumers have confirmed the switch. This closes the credential leakage gap where old ACs persist for a long period of time.
The OpenStackDataPlaneNodeSet controller will add a finalizer to the AC Secret it is currently using. The finalizer is removed only after all nodes have been redeployed with the new Secret. This ensures credentials are not revoked while EDPM nodes are still using them. This work depends on the secret-tracking mechanism from PR #1781.
1. keystone-operator detects rotation needed (grace period, security hash change, or missing Secret)
2. Creates new AC in Keystone and a new immutable Secret with a unique name
3. Updates AC CR Status.SecretName to point to the new Secret
4. openstack-operator watches the AC CR (via Owns), sees the new SecretName, and updates the service CR
5. Service operator watches the named Secret field, picks up the new Secret, and reconfigures
6. Service operator removes its finalizer from the old Secret
7. keystone-operator sees the old Secret has no service finalizers, deletes it, and revokes the old AC
For EDPM services, step 6 will require OpenStackDataPlaneNodeSet level tracking of which secret version is deployed on each node. Automatic finalizer removal after all nodes have adopted the new credential depends on the per-node secret tracking work proposed in PR #1781.