This document provides a brief overview of the Keystone ApplicationCredential (AC) controller.
AC controller watches KeystoneApplicationCredential custom resources (CR) and performs these actions:
- Create or rotate ACs in Keystone
- Store the generated AC ID and AC secret in a k8s
Secret - Reconcile the CR’s status (AC ID, creation/expiry time, conditions)
Note: AC controller is not responsible for generating AC CR for a service, that responsibility falls under the openstack-operator. AC controller merely watches/updates AC CR.
spec:
# Secret containing service user password (default: osp-secret)
secret: osp-secret
# PasswordSelector for extracting the service password
passwordSelector: ServicePassword
# UserName - the Keystone user under which this ApplicationCredential is created
userName: barbican
# ExpirationDays sets the lifetime in days (default: 365, minimum: 2)
expirationDays: 365
# GracePeriodDays sets rotation window (default: 182, minimum: 1)
# Must be smaller than expirationDays
gracePeriodDays: 182
# Roles to assign to the ApplicationCredential (minimum: 1 role)
roles:
- service
# Unrestricted indicates whether AC may create/destroy other credentials
unrestricted: false
# AccessRules defines which services the AC is permitted to access
accessRules:
- service: compute
path: /servers
method: GET
- service: image
path: /images
method: GETstatus:
# ACID - the ID in Keystone for this ApplicationCredential
ACID: "7b23dbac20bc4f048f937415c84bb329"
# SecretName - name of the k8s Secret storing the ApplicationCredential secret
# Format: ac-<service>-<first5ofACID>-secret
secretName: "ac-barbican-7b23d-secret"
# CreatedAt - timestamp of creation
createdAt: "2025-05-29T09:02:28Z"
# ExpiresAt - time of validity expiration
expiresAt: "2026-05-29T09:02:28Z"
# RotationEligibleAt - when rotation becomes eligible (ExpiresAt - GracePeriodDays)
rotationEligibleAt: "2025-11-29T09:02:28Z"
# LastRotated - timestamp when credentials were last rotated (only set after first rotation)
lastRotated: "2025-05-29T09:02:28Z"
# Conditions
conditions:
- type: Ready
status: "True"
- type: KeystoneAPIReady
status: "True"
- type: KeystoneApplicationCredentialReady
status: "True"When the openstack-operator generates a new AC CR for Barbican oc get appcred ac-barbican -n openstack:
apiVersion: keystone.openstack.org/v1beta1
kind: KeystoneApplicationCredential
metadata:
name: ac-barbican
namespace: openstack
spec:
expirationDays: 365
gracePeriodDays: 182
passwordSelector: BarbicanPassword
roles:
- service
secret: osp-secret
unrestricted: false
userName: barbicanthe AC controller:
-
Reconcile() invoked
- Controller-runtime calls
Reconcile()withac-barbican
- Controller-runtime calls
-
Finalizer & Conditions
- Adds
openstack.org/applicationcredentialfinalizer - Initializes status conditions (
KeystoneAPIReady,KeystoneApplicationCredentialReady,Ready)
- Adds
-
Wait for KeystoneAPI
- If missing or not ready, marks condition and requeues
-
Determine Rotation Need
needsRotation()returnstruebecause no AC ID exists yet
-
Service‐Scoped Client
- Retrieves
BarbicanPasswordfromosp-secret(or any other specified in the AC CR) - Authenticates as
barbicanuser
- Retrieves
-
Extract User ID from Token
- Extracts the user ID from the service user's own authentication token
- Uses the authenticated service user's token to get its own Keystone user ID
-
Create AC in Keystone
- Calls gophercloud's
applicationcredentials.Create(...) - Includes access rules if specified in the CR
- Calls gophercloud's
-
Store Secret
- Creates a new immutable k8s
Secretwith a unique name:ac-<service>-<first5ofACID>-secret - The name includes the first 5 characters of the Keystone AC ID for uniqueness
- Adds
openstack.org/ac-secret-protectionfinalizer to the Secret - Sets owner reference to the AC CR (for garbage collection on CR deletion)
- Creates a new immutable k8s
apiVersion: v1
kind: Secret
metadata:
name: ac-barbican-7b23d-secret
namespace: openstack
labels:
application-credentials: "true"
application-credential-service: barbican
finalizers:
- openstack.org/ac-secret-protection
ownerReferences:
- apiVersion: keystone.openstack.org/v1beta1
kind: KeystoneApplicationCredential
name: ac-barbican
controller: true
blockOwnerDeletion: true
immutable: true
data:
AC_ID: <base64-of-AC-ID>
AC_SECRET: <base64-of-AC-secret>- Update CR status
- Sets
.status.ACID,.status.secretName,.status.createdAt,.status.expiresAt,.status.rotationEligibleAt - Sets
.status.lastRotatedand emitsApplicationCredentialRotatedevent (only during rotation, not initial creation) - Marks AC CR ready
- Sets
AC in Keystone side:
openstack application credential show 7b23dbac20bc4f048f937415c84bb329
+--------------+------------------------------------------------------------------------+
| Field | Value |
+--------------+------------------------------------------------------------------------+
| description | Created by keystone-operator for AC CR openstack/ac-barbican (user... |
| expires_at | 2026-05-29T09:02:28.705012 |
| id | 7b23dbac20bc4f048f937415c84bb329 |
| name | ac-barbican-29090 |
| project_id | 24f05745bc2145c6a625b528ce21d7a3 |
| roles | service |
| system | None |
| unrestricted | False |
| user_id | 2ecd25b38f0d432388ad8b838e46f36d |
+--------------+------------------------------------------------------------------------+
Note: The actual AC name in Keystone includes a 5-character random suffix (e.g. -29090) to avoid collisions during rotation.
When the next reconcile hits the grace window (now ≥ expiresAt - gracePeriodDays), needsRotation() returns true again and the controller:
- Service-Scoped Client - authenticates as the service user
- Extract User ID from the service user's token
- Create New AC
- Generates a new Keystone AC with a fresh 5-char suffix
- Uses the same roles, unrestricted flag, access rules, and expirationDays
- Does not revoke the old AC, the old credential naturally expires
- Create New Immutable Secret
- Creates a new immutable Secret with a unique name (e.g.
ac-barbican-d38dc-secret) - The previous Secret (e.g.
ac-barbican-7b23d-secret) is retained — it is not deleted - Both secrets are owned by the AC CR and will be garbage-collected when the CR is deleted
- Creates a new immutable Secret with a unique name (e.g.
- Update Status
- Sets
.status.secretNameto the new Secret name - Replaces
.status.ACID,.status.createdAt,.status.expiresAt, and.status.rotationEligibleAtwith the new values - Sets
.status.lastRotatedto current timestamp - Re-marks AC CR ready
- Emits
ApplicationCredentialRotatedevent for EDPM visibility
- Sets
- Propagation
- The openstack-operator
Ownsthe AC CR, so the status change triggers re-reconciliation - It reads the new
.status.secretNameand updates the service CR'sApplicationCredentialSecret - The service operator detects the spec change and reads credentials from the new Secret
- The openstack-operator
Manual rotation can be triggered by patching the AC CR with an expiration timestamp in the past:
oc patch -n openstack keystoneapplicationcredential ac-barbican \
--type=merge --subresource=status \
-p '{"status":{"expiresAt":"2001-05-19T00:00:00Z"}}'This triggers seamless rotation with one pod restart and no authentication fallback.
Note: Deleting the AC CR itself also triggers rotation, but causes a brief fallback to password authentication while the openstack-operator recreates the AC CR. This results in two pod restarts instead of one, because the service authentication type is based on the presence of AC Secret.
ApplicationCredentials in Keystone are not automatically deleted by the controller. This design decision prevents disrupting running services, especially EDPM nodes that actively use these credentials.
Cleanup behavior:
- During rotation: The old AC remains in Keystone and expires naturally based on its
expiresAttimestamp. The old K8s Secret is also retained (immutable). A new AC and a new immutable Secret are created. - When AC CR is deleted: The controller removes the
openstack.org/ac-secret-protectionfinalizer from all AC Secrets for the service (found by label), allowing owner-reference garbage collection to delete them. The ApplicationCredential in Keystone remains valid until natural expiration. - Manual cleanup: If immediate cleanup is required, operators can manually delete the AC from Keystone:
openstack application credential delete <ac-id>This approach ensures that deleting the AC CR (intentionally or accidentally) does not cause immediate authentication failures across the control plane and EDPM deployments.
The keystone-operator/api/v1beta1 package exports the following helpers for use by other operators:
import keystonev1 "github.com/openstack-k8s-operators/keystone-operator/api/v1beta1"
// Get standard AC CR name for a service
crName := keystonev1.GetACCRName("barbican") // Returns "ac-barbican"
// Secret data keys
keystonev1.ACIDSecretKey // "AC_ID"
keystonev1.ACSecretSecretKey // "AC_SECRET"Service operators read AC data directly from the Secret referenced by the service CR's ApplicationCredentialSecret field, using ACIDSecretKey and ACSecretSecretKey as the data keys.
The API includes validation constraints:
gracePeriodDaysmust be smaller thanexpirationDaysexpirationDaysminimum value: 2gracePeriodDaysminimum value: 1rolesmust contain at least 1 role