cki_tools.credentials
Manage credentials for service accounts and their metadata. Two CLI tools
provide access (secrets) and lifecycle management (manager). Metadata is
stored in a git-tracked YAML file; actual secret values are stored in a
pluggable backend (currently HashiCorp Vault, recorded per-secret as
backend: hv).
Where a service supports ephemeral credentials — OIDC federation, JWT-based delegated auth, dynamic secret issuance — prefer those; no rotation machinery is needed. This system is for long-lived shared credentials where rotation is unavoidable.
Core concepts
Secrets file and backends
Every secret has a YAML entry in the secrets file (pointed to by
CKI_SECRETS_FILE). This entry holds metadata — token type, timestamps,
flags — but never the secret value itself. Secret values live in a
backend; the backend is recorded in the YAML:
MY_TOKEN/1:
backend: hv
meta:
token_type: ssh_private_key
active: true
deployed: true
created_at: '2025-01-22T10:54:04+00:00'
comment: my-server
key_size: 4096
Currently the only backend is HashiCorp Vault (hv). The backend is set
automatically when a secret value is written.
Token groups and versions
Secrets are organized as token groups with versions. MY_TOKEN is the
group; MY_TOKEN/1 is a versioned entry. The version string is arbitrary —
the rotation tooling generates unix timestamps, but a simple index like 1
works fine. Best practice is to always use a version, even for single-version
secrets (MY_TOKEN/1 rather than the bare MY_TOKEN).
active and deployed flags
Every version carries two boolean metadata flags:
active— the token is valid on the remote service (not revoked/expired).deployed— this is the version currently consumed by services.
Exactly one version per group must be deployed at any time.
Provider / consumer pattern
When deploying rotatable secrets, two perspectives matter:
- Provider (the service using the credential to authenticate): should
use the
deployedversion. - Consumer (the service accepting the credential): should trust all
activeversions.
This enables zero-downtime rotation: a new version is created and accepted before the provider switches to it.
Lifecycle models
Multiple workloads consume the same credential. They load it at startup, cache it in memory, or refresh on their own schedule — there is no deterministic moment when all consumers have switched to a new value. Revoking the old credential immediately after creating a replacement breaks any consumer that has not yet switched.
The tooling addresses this by ensuring overlapping validity during rotation: old and new credentials both work during the cutover window. It does this either by explicitly keeping both versions active or by relying on the service’s built-in grace period. It does not require application-level retries or coordinated rollouts/restarts as part of the rotation contract; those remain optional operational complements.
The specific rotation model depends on what the remote service supports. The key differences are how many versions coexist in steady state (1 or 2) and how many steps a rotation takes (1, 2, or 3).
Two-slot rotation (no native rotation)
The service has no rotation API — creating a new token does not touch existing ones (e.g. SSH keys, AWS keys, deploy tokens). In steady state only one version exists. Rotation temporarily creates a second version: first create a new non-deployed version, then flip which one is deployed, then revoke the old one — 3 steps across 3 commits. Between the first and last step, both versions are active and consumers must trust both.
| step | version 1 | version 2 |
|---|---|---|
| deployed | missing | |
| MR 1: create a new non-deployed version | deployed | active |
| MR 2: flip the deployed flag | active | deployed |
| MR 3: revoke the old version | revoked | deployed |
The manager CLI automates these as prepare, switch, and clean.
Service-side rotation (native rotation API)
The service provides an atomic rotation API that replaces a token in a single
call — the old token is invalidated and a new one is returned (e.g. GitLab
rotate() API). Because the call immediately invalidates the old token, you
cannot rotate the deployed version directly — that would kill the live token
before services can switch. Instead, two versions are kept in steady
state: one deployed (live), one spare. Rotate the spare (which atomically
revokes it and creates a new one), then flip deployed to the new version.
Because the atomic call already revoked the old spare, no separate revocation
step is needed — 2 steps across 2 commits.
| step | version 1 | version 2 | version 3 |
|---|---|---|---|
| active | deployed | missing | |
| MR 1: rotate the non-deployed one | revoked | deployed | active |
| MR 2: flip the deployed flag | revoked | active | deployed |
The manager CLI automates these as prepare and switch.
Grace-period rotation
Like service-side rotation, the service replaces the token in a single call — but unlike it, the old key remains valid for a configured grace period (e.g. Testing Farm keeps old keys valid for 7 days). This removes the constraint that blocked rotating the live token: since the old one still works, you can rotate the deployed version directly. No spare slot is needed — one version in steady state, single step. The new version must be deployed before the grace period expires.
| step | version 1 | version 2 |
|---|---|---|
| deployed | missing | |
| MR 1: rotate the deployed version | revoked | deployed |
The manager CLI automates this as prepare.
Token age and rotation window
Tokens should be rotated before they expire. A common default is 365 days,
with rotation flagged 30 days before the limit to give a buffer. The
manager CLI exposes this via --token-age (default: 365 days).
Token types (managed vs unmanaged)
Managed secrets have a token_type in their metadata that determines which
lifecycle operations the manager CLI can automate (create, destroy, rotate,
update, validate) and what metadata fields are required — see the Manager CLI
reference for the full list. Unmanaged secrets have no token type;
they follow the same rotation patterns but each step is performed manually
with cki_edit_secret.
How-to guides
Reading secrets and metadata
Retrieve a secret value (printed as a raw string, no quoting):
$ cki_secret MY_TOKEN/1
s3cr3t-value
Retrieve metadata — a single field returns its value, an empty field
(#) returns all metadata as key-value pairs, one per line:
$ cki_secret MY_TOKEN/1#token_type
ssh_private_key
$ cki_secret MY_TOKEN/1#
ssh_private_key
True
True
2025-01-22T10:54:04+00:00
...
Retrieve the raw secret data from the backend — same pattern with ::
cki_secret MY_TOKEN/1:private_key # single field
cki_secret MY_TOKEN/1: # all key-value pairs
By default, dict and list results are flattened with one value per line
(keys are not shown). Use --json for structured output that preserves
keys, types, and nesting:
$ cki_secret --json MY_TOKEN/1#
{"token_type": "ssh_private_key", "active": true, "deployed": true, ...}
Location syntax
| Location | Description |
|---|---|
some/path# |
dictionary of all key-value meta pairs |
some/path#field |
value for the given meta field |
some/path: |
dictionary of all key-value data pairs |
some/path:field |
value for the given data field |
some/path |
value for the value data field |
some/path[cond1,cond2] |
list of the above for all matching secrets |
Conditions
Conditions filter multiple secrets via path[meta-key-1,!meta-key-2,...].
For all secrets starting with the given path (delimited by /), metadata
fields are checked with a default of False. A secret is included only if all
conditions match: True for meta-key-1, False for !meta-key-2.
The result is always a list — one entry per matching secret. This wraps each value in an extra level compared to the non-condition form:
$ cki_secret MY_TOKEN/1 # single string
s3cr3t-value
$ cki_secret MY_TOKEN[deployed] # list of strings (one per match)
s3cr3t-value
$ cki_secret --json MY_TOKEN[deployed]
["s3cr3t-value"]
Combined with # or :, each entry is the corresponding dict or field:
$ cki_secret --json MY_TOKEN[active]#
[{"token_type": "ssh_private_key", "active": true, ...}, {"token_type": ...}]
$ cki_secret --json MY_TOKEN[active]:
[{"private_key": "...", "public_key": "..."}, {"private_key": "...", ...}]
Without --json, the list is flattened and each value is printed on its own
line (dicts print as their Python repr — use --json when querying structured
data with conditions).
Adding an unmanaged secret
For secrets without a token_type (not automated by the manager CLI):
-
Add the secret interactively (opens
$EDITOR):cki_edit_secret MY_SECRETThis creates the value in the backend and adds default metadata to the secrets file:
MY_SECRET: backend: hv meta: active: true created_at: '2025-01-22T10:54:04+00:00' deployed: true -
Add additional metadata if needed:
cki_edit_secret MY_SECRET#some_field some-value -
Commit the secrets file changes.
Removing an unmanaged secret
For managed secrets, use manager destroy instead (see below).
-
Remove all metadata and the backend reference:
cki_edit_secret MY_SECRET# '' cki_edit_secret MY_SECRET: '' -
Commit the secrets file changes.
-
Optionally, delete the secret from HashiCorp Vault:
vault kv metadata delete apps/cki/MY_SECRET
Creating a managed secret
For secrets with a token_type that supports create:
-
Check the token type reference for required fields.
-
Add the required metadata to the secrets file. For example, an SSH key:
SERVER_SSH_KEY/1: meta: token_type: ssh_private_key comment: server-name key_size: 4096 -
Create the secret:
python3 -m cki_tools.credentials.manager create --token-name SERVER_SSH_KEY/1 -
Commit the secrets file changes.
Destroying a managed secret
python3 -m cki_tools.credentials.manager destroy --token-name SERVER_SSH_KEY/1
This revokes the token on the remote service (if applicable) and sets
active: false in the metadata. Destroying a deployed version requires
--force.
Checking rotation status
Show which managed tokens need attention:
python3 -m cki_tools.credentials.manager status
python3 -m cki_tools.credentials.manager table
Use --token-name to check a single token, --token-type-prefix to scope
to a token type, or --token-age to override the age threshold:
python3 -m cki_tools.credentials.manager status --token-type-prefix gitlab_project
python3 -m cki_tools.credentials.manager status --token-age 180d
The table command shows a matrix of token health. Column abbreviations:
| Abbr | Meaning |
|---|---|
| NDTV | No deployed token version |
| TMDV | Too many deployed versions |
| PTVD | Previous token version deployed |
| TMAV | Too many active versions |
| DVTO | Deployed version too old |
| DVES | Deployed version expiring soon |
| AVTO | Active version too old |
| AVES | Active version expiring soon |
Rotating tokens (three-step)
Before starting: the prepare step may create new tokens and revoke old
non-deployed ones. This can cause downstream validation to fail, so coordinate
with your team to merge rotation changes quickly.
When operating on multiple tokens (no --token-name), prepare, switch,
and clean prompt for confirmation before proceeding. Use
--token-type-prefix to scope to a specific token type, or --force to
bypass the age check (tokens not yet due for rotation are skipped by default).
Step 1: prepare
python3 -m cki_tools.credentials.manager prepare --token-name MY_TOKEN
Creates a new version (or rotates the spare slot). The secrets file changes:
# Before:
MY_TOKEN/1:
meta: {token_type: gitlab_project_token, active: true, deployed: true, ...}
# After:
MY_TOKEN/1:
meta: {token_type: gitlab_project_token, active: true, deployed: true, ...}
MY_TOKEN/1748123456:
meta: {token_type: gitlab_project_token, active: true, deployed: false, ...}
Commit and deploy the secrets file before proceeding.
Step 2: switch
python3 -m cki_tools.credentials.manager switch --token-name MY_TOKEN
Flips deployed to the newest version:
MY_TOKEN/1:
meta: {..., deployed: false, ...}
MY_TOKEN/1748123456:
meta: {..., deployed: true, ...}
Commit and deploy before proceeding.
Step 3: clean
python3 -m cki_tools.credentials.manager clean --token-name MY_TOKEN
Destroys excess non-deployed versions and marks them active: false.
Commit and deploy.
Optional: purge
python3 -m cki_tools.credentials.manager purge --token-name MY_TOKEN
Removes metadata and backend entries for inactive versions.
Rotating tokens (grace period)
For token types that support grace-period rotation (e.g. Testing Farm), only
the prepare step is needed — it rotates the deployed version directly:
python3 -m cki_tools.credentials.manager prepare --token-name MY_TF_TOKEN
The old key remains valid for the grace period (default: 7 days). No switch
or clean step is needed.
Validating tokens
manager validate checks that active managed tokens are still valid on
the remote service (API call per token):
python3 -m cki_tools.credentials.manager validate
python3 -m cki_tools.credentials.manager validate --token-type-prefix gitlab_project
cki_secrets_validate checks consistency between the secrets file and the
backend for all secrets (managed and unmanaged, no remote service calls):
cki_secrets_validate
Exit codes: 0 = fully consistent, 1 = secrets exist in the backend without
a metadata entry (informational — this is normal, e.g. after purging
metadata), 2 = metadata references a secret missing from the backend, or
required metadata (created_at) is absent.
Troubleshooting
validate reports missing secrets in Vault
The token was purged or never created. Check if the metadata still references
it; if so, re-run create to recreate it.
prepare says “not preparing without –force”
The token is not old enough to need rotation. Use --token-age to lower the
threshold, or --force to override.
Rotation errors mid-way (meta/error field)
When a creation or rotation fails after writing to the backend, the error is
saved in meta/error. Inspect the error message, fix the underlying issue
(expired auth, network failure, permission denied), and re-run the operation.
The secret data may already be in the backend — check before re-creating.
Invariant violations
- “No deployed token”: no version has
deployed: true. Manually setdeployed: trueon the correct version in the secrets file. - “More than one deployed token”: multiple versions have
deployed: true. Set all but the intended version todeployed: false.
Authentication errors
- GitLab 401 Unauthorized: you are likely using a personal token instead
of a service account token. Re-check
GITLAB_TOKENS. - Vault permission denied: re-run
cki_secrets_login --oidc.
Secrets CLI reference
$ python3 -m cki_tools.credentials.secrets --help
usage: secrets.py [-h] {secret,variable,edit,validate,login,logout} ...
Access CKI variables and secrets
positional arguments:
{secret,variable,edit,validate,login,logout}
secret Retrieve a secret value
variable Retrieve a variable value
edit Edit a secret value
validate Validate stored secrets
login Log into secrets storage
logout Log out of secrets storage
-h, --help show this help message and exit
--json output in json format
CLI aliases
| Alias | Equivalent |
|---|---|
cki_secret |
python3 -m cki_tools.credentials.secrets secret |
cki_variable |
python3 -m cki_tools.credentials.secrets variable |
cki_edit_secret |
python3 -m cki_tools.credentials.secrets edit |
cki_secrets_validate |
python3 -m cki_tools.credentials.secrets validate |
cki_secrets_login |
python3 -m cki_tools.credentials.secrets login |
cki_secrets_logout |
python3 -m cki_tools.credentials.secrets logout |
cki_secret
usage: cki_secret [-h] [--json] key
Retrieve a secret value using the location syntax.
cki_variable
usage: cki_variable [-h] [--json] key
Retrieve an unencrypted variable from the variables file (CKI_VARS_FILE).
Values are printed as-is by default. With --json, output is properly
JSON-encoded (strings quoted, dicts serialized, etc.).
cki_edit_secret
usage: cki_edit_secret [-h] key [value]
Edit secrets and metadata. All location syntax forms are supported except conditions. When editing metadata, the secrets file is rewritten. When editing secret values, the backend is updated.
If value is omitted, $EDITOR (or vi) is opened. Quit with a nonzero
exit code to abort (e.g. :cq in vi).
cki_secrets_validate
usage: cki_secrets_validate [-h]
Compare the secrets available in the backend with the secrets metadata. Exit
codes: 0 = fully consistent, 1 = secrets exist in the backend without a
metadata entry (informational), 2 = metadata references a secret missing from
the backend, or required metadata (created_at) is absent.
cki_secrets_login
usage: cki_secrets_login [-h] [--duration DURATION] (--oidc | --jwtrole JWTROLE | --approle APPROLE)
Log into HashiCorp Vault. Authentication methods:
--oidc: for human users (requires a Kerberos ticket for SSO).--jwtrole ROLE: for service accounts (requiresVAULT_ID_TOKEN).--approle ROLE: for service accounts (requiresVAULT_APPROLE_SECRET_ID).
The --duration parameter optionally extends the token validity beyond its
default lifetime.
The client token is stored in ~/.vault-token (compatible with the official
vault CLI).
cki_secrets_logout
usage: cki_secrets_logout [-h]
Revoke the client token and remove ~/.vault-token.
Manager CLI reference
usage: python3 -m cki_tools.credentials.manager
[--token-name TOKEN_NAME]
[--token-type-prefix TOKEN_TYPE_PREFIX]
[--token-age TOKEN_AGE]
[--force]
{create,destroy,rotate,update,validate,status,table,prepare,switch,clean}
Low-level commands
| command | description |
|---|---|
create |
create a new token as specified by the required metadata |
destroy |
destroy an existing token, e.g. by revoking it |
rotate |
create a new version of an existing token, which might invalidate the original version |
Three-step rotation commands
| command | description |
|---|---|
status |
show which tokens need rotation/preparation/cleanup |
table |
show a table and summary of detected problems |
prepare |
prepare token versions for rotation |
switch |
switch the deployed token version |
clean |
clean token versions after rotation |
Miscellaneous commands
| command | description |
|---|---|
update |
update token metadata from the remote service |
validate |
confirm with the remote services that active token versions are still valid |
purge |
remove secrets and metadata for inactive token versions |
The validate command also checks these invariants:
- all versions have
active,deployedandcreated_atfields - only one version is marked as
deployed
Atlassian API tokens
- Operations: (none — externally managed)
- Features: validate
- Steady-state versions: 1
- Grace-period rotation: no
Validates that the API token works and the associated Atlassian account is
active via the Jira /rest/api/2/myself endpoint.
Atlassian API tokens required fields
| name | description |
|---|---|
token_type |
atlassian_api_token |
instance_url |
Jira instance URL |
emailAddress |
account email address (used for auth) |
The secret value (API token) must be stored in the backend manually.
Atlassian API tokens managed fields
| name | description |
|---|---|
active |
whether the token is still active |
deployed |
whether the token is actually used |
GitHub personal access tokens
- Operations: (none — externally managed)
- Features: update, validate
- Steady-state versions: 1
- Grace-period rotation: no
Validates that the token works via the GitHub /user
endpoint. Cross-checks user_name if present in metadata. Update populates
scopes from the X-OAuth-Scopes response header and expires_at from the
GitHub-Authentication-Token-Expiration header.
GitHub personal access tokens required fields
| name | description |
|---|---|
token_type |
github_personal_token |
The secret value (personal access token) must be stored in the backend manually.
GitHub personal access tokens managed fields
| name | description |
|---|---|
user_name |
GitHub login (cross-checked on validate) |
scopes |
OAuth scopes granted to the token |
expires_at |
token expiration (if set) |
active |
whether the token is still active |
deployed |
whether the token is actually used |
GitLab project access tokens
- Operations: create, destroy, rotate
- Features: three-step rotation, update, validate
- Steady-state versions: 2
- Grace-period rotation: no
See the API description for details.
Validate authenticates with the token and checks that it is not revoked.
GitLab project access tokens required fields
| name | description |
|---|---|
token_type |
gitlab_project_token |
project_url |
project URL |
scopes |
access scope |
access_level |
access level 1 |
token_name |
name of the token |
GitLab project access tokens managed fields
| name | description |
|---|---|
| (secret) | secret token |
token_id |
project access token ID |
created_at |
ISO8601 timestamp of creation |
expires_at |
ISO8601 expiry date |
revoked |
whether the token is already revoked |
active |
whether the token is still active |
user_id |
ID of associated user |
user_name |
name of associated user |
deployed |
whether the token is actually used |
GitLab group access tokens
- Operations: create, destroy, rotate
- Features: three-step rotation, update, validate
- Steady-state versions: 2
- Grace-period rotation: no
See the API description for details.
Validate authenticates with the token and checks that it is not revoked.
GitLab group access tokens required fields
| name | description |
|---|---|
token_type |
gitlab_group_token |
group_url |
group URL |
scopes |
access scope |
access_level |
access level 1 |
token_name |
name of the token |
GitLab group access tokens managed fields
| name | description |
|---|---|
| (secret) | secret token |
token_id |
group access token ID |
created_at |
ISO8601 timestamp of creation |
expires_at |
ISO8601 expiry date |
revoked |
whether the token is already revoked |
active |
whether the token is still active |
user_id |
ID of associated user |
user_name |
name of associated user |
deployed |
whether the token is actually used |
GitLab personal access tokens
- Operations: destroy, rotate
- Features: three-step rotation, update, validate
- Steady-state versions: 2
- Grace-period rotation: no
See the API description for details.
Validate authenticates with the token and checks that it is not revoked.
Dependency: rotation requires another personal access token on the same
instance and user with api or read_api scope. The tool finds a suitable
token automatically from the secrets file.
GitLab personal access tokens required fields
| name | description |
|---|---|
token_type |
gitlab_personal_token |
instance_url |
GitLab instance URL |
token_id |
access token ID |
user_id |
ID of associated user |
GitLab personal access tokens managed fields
| name | description |
|---|---|
| (secret) | secret token |
scopes |
access scope |
token_name |
name of the token |
created_at |
ISO8601 timestamp of creation |
expires_at |
ISO8601 expiry date |
revoked |
whether the token is already revoked |
active |
whether the token is still active |
user_name |
name of associated user |
deployed |
whether the token is actually used |
GitLab project deploy tokens
- Operations: create, destroy
- Features: three-step rotation, update, validate
- Steady-state versions: 1
- Grace-period rotation: no
See the API description for details.
Validate checks that the token is not revoked or expired.
GitLab project deploy tokens required fields
| name | description |
|---|---|
token_type |
gitlab_project_deploy_token |
project_url |
project URL |
scopes |
access scope |
token_name |
name of the token |
GitLab project deploy tokens managed fields
| name | description |
|---|---|
| (secret) | secret token |
token_id |
project deploy token ID |
created_at |
ISO8601 timestamp of creation |
expires_at |
ISO8601 expiry date (optional) |
revoked |
whether the token is already revoked |
active |
whether the token is still active |
user_name |
associated user name |
deployed |
whether the token is actually used |
GitLab group deploy tokens
- Operations: create, destroy
- Features: three-step rotation, update, validate
- Steady-state versions: 1
- Grace-period rotation: no
See the API description for details.
Validate checks that the token is not revoked or expired.
GitLab group deploy tokens required fields
| name | description |
|---|---|
token_type |
gitlab_group_deploy_token |
group_url |
group URL |
scopes |
access scope |
token_name |
name of the token |
GitLab group deploy tokens managed fields
| name | description |
|---|---|
| (secret) | secret token |
token_id |
group deploy token ID |
created_at |
ISO8601 timestamp of creation |
expires_at |
ISO8601 expiry date (optional) |
revoked |
whether the token is already revoked |
active |
whether the token is still active |
user_name |
associated user name |
deployed |
whether the token is actually used |
GitLab runner authentication tokens
- Operations: rotate, destroy
- Features: three-step rotation, update, validate
- Steady-state versions: 2
- Grace-period rotation: no
See the API description for details.
Validate calls the runner verify endpoint.
GitLab runner authentication tokens required fields
| name | description |
|---|---|
token_type |
gitlab_runner_authentication_token |
instance_url |
GitLab instance URL |
The secret value (runner token) must be stored in the backend initially.
GitLab runner authentication tokens managed fields
| name | description |
|---|---|
| (secret) | secret token |
token_id |
runner ID |
description |
runner description |
expires_at |
ISO8601 expiry date (optional) |
active |
whether the token is still active |
deployed |
whether the token is actually used |
LDAP keytabs
- Operations: (none — externally managed)
- Features: update
- Steady-state versions: 1
- Grace-period rotation: no
Update fetches LDAP object attributes from the directory server.
LDAP keytabs required fields
| name | description |
|---|---|
token_type |
ldap_keytab |
ldap_server |
LDAP server |
dn |
LDAP distinguished name |
The secret value (keytab, base64-encoded) must be stored in the backend manually.
LDAP keytabs managed fields
| name | description |
|---|---|
| (LDAP attributes) | LDAP object attributes |
active |
whether the key is still valid |
deployed |
whether the key is actually used |
LDAP passwords
- Operations: (none — externally managed)
- Features: update, validate
- Steady-state versions: 1
- Grace-period rotation: no
Update fetches LDAP object attributes. Validate performs a simple bind to the LDAP server.
LDAP passwords required fields
| name | description |
|---|---|
token_type |
ldap_password |
ldap_server |
LDAP server |
dn |
LDAP distinguished name |
The secret value (password) must be stored in the backend manually.
LDAP passwords managed fields
| name | description |
|---|---|
| (LDAP attributes) | LDAP object attributes |
active |
whether the key is still valid |
deployed |
whether the key is actually used |
AWS secret access keys
- Operations: create, destroy
- Features: three-step rotation, update
- Steady-state versions: 1
- Grace-period rotation: no
Note: the optional profile_name field selects the AWS profile to use. If
missing, prepare and rotate silently skip the token.
AWS secret access keys required fields
| name | description |
|---|---|
token_type |
aws_secret_access_key |
access_key_id |
access key ID |
user_name |
service account user name |
account |
AWS account number |
AWS secret access keys managed fields
| name | description |
|---|---|
| (secret) | secret access key |
endpoint_url |
endpoint URL if not AWS |
arn |
service account user ARN |
created_at |
ISO8601 timestamp of creation |
active |
whether the token is still active |
deployed |
whether the token is actually used |
profile_name |
AWS profile (optional) |
Dogtag certificates
- Operations: create
- Features: three-step rotation, update, validate
- Steady-state versions: 1
- Grace-period rotation: no
Validate verifies that the private key matches the certificate.
Dependency: authentication uses an ldap_password secret matching the
uid field. This LDAP password must exist and be valid.
Dogtag certificates required fields
| name | description |
|---|---|
token_type |
dogtag_certificate |
server_url |
Dogtag server URL |
ProfileID |
certificate profile |
uid |
uid of LDAP user for authentication |
SubjectDN |
subject DN |
Dogtag certificates managed fields
| name | description |
|---|---|
| (secret):private_key | secret key |
| (secret):certificate | certificate |
| (secret):chain | full certificate chain |
id |
certificate serial number |
IssuerDN |
issuer DN |
created_at |
ISO8601 timestamp of creation |
expires_at |
ISO8601 timestamp of expiry |
active |
whether the certificate is still valid |
deployed |
whether the certificate is actually used |
Splunk HEC tokens
- Operations: (none — externally managed)
- Features: validate
- Steady-state versions: 1
- Grace-period rotation: no
Validate POSTs to the HEC endpoint; response codes 5 (no data) and 12 (event field required) are treated as success, confirming the token is valid.
Splunk HEC tokens required fields
| name | description |
|---|---|
token_type |
splunk_hec_token |
endpoint_url |
Splunk HEC endpoint base URL |
The secret value (HEC token) must be stored in the backend manually.
Splunk HEC tokens managed fields
| name | description |
|---|---|
index |
Splunk index (optional) |
active |
whether the token is still active |
deployed |
whether the token is actually used |
SSH keys
- Operations: create
- Features: three-step rotation, update, validate
- Steady-state versions: 1
- Grace-period rotation: no
Validate verifies that the private and public key form a matching pair.
key_size must be at least 4096.
SSH keys required fields
| name | description |
|---|---|
token_type |
ssh_private_key |
comment |
public key comment (name) |
key_size |
RSA key size (minimum 4096) |
SSH keys managed fields
| name | description |
|---|---|
| (secret):private_key | secret key |
| (secret):public_key | public key |
created_at |
ISO8601 timestamp of creation |
active |
whether the key is still valid |
deployed |
whether the key is actually used |
Kubernetes ServiceAccount tokens
- Operations: create, destroy
- Features: three-step rotation, update, validate
- Steady-state versions: 1
- Grace-period rotation: no
Creates long-lived ServiceAccount tokens using Kubernetes Secrets with automatic token population. The ServiceAccount must exist beforehand.
Since Kubernetes 1.24, Secrets are no longer auto-created for ServiceAccounts.
Creating an annotated kubernetes.io/service-account-token Secret is the
explicit way to obtain a long-lived token. Managing these Secrets through the
credentials manager (rather than just templating them directly) adds lifecycle
management on top: three-step rotation via prepare/switch/clean,
validation against the cluster API via SelfSubjectReview, and created_at
tracking exposed as cki_token_created_at Prometheus metrics.
Validate performs a SelfSubjectReview against the cluster API to confirm the token is accepted.
Kubernetes ServiceAccount tokens required fields
| name | description |
|---|---|
token_type |
k8s_service_account_token |
server_url |
Kubernetes API server URL |
namespace |
ServiceAccount namespace |
service_account |
ServiceAccount name (must exist beforehand) |
Kubernetes ServiceAccount tokens managed fields
| name | description |
|---|---|
| (secret) | ServiceAccount token (JWT) |
secret_name |
generated Secret name containing the token |
created_at |
ISO8601 timestamp of creation |
active |
whether the token is still valid |
deployed |
whether the token is actually used |
Generic passwords
- Operations: create
- Features: three-step rotation
- Steady-state versions: 1
- Grace-period rotation: no
Passwords are created via diceware.
Generic passwords required fields
| name | description |
|---|---|
token_type |
password |
Generic passwords managed fields
| name | description |
|---|---|
| (secret) | password |
created_at |
ISO8601 timestamp of creation |
active |
whether the key is still valid |
deployed |
whether the key is actually used |
HashiCorp Vault secret IDs
- Operations: create, destroy
- Features: three-step rotation, validate
- Steady-state versions: 1
- Grace-period rotation: no
Only AppRole auth secret IDs are supported. Validate attempts a login with the secret ID.
HashiCorp Vault secret IDs required fields
| name | description |
|---|---|
token_type |
hv_approle_secret_id |
vault_addr |
HashiCorp Vault URL |
role_id |
role name |
HashiCorp Vault secret IDs managed fields
| name | description |
|---|---|
| (secret) | secret ID |
secret_id_accessor |
secret ID accessor |
created_at |
ISO8601 timestamp of creation |
active |
whether the secret ID is still valid |
deployed |
whether the secret ID is actually used |
RabbitMQ passwords
- Operations: create, destroy
- Features: three-step rotation, update, validate
- Steady-state versions: 1
- Grace-period rotation: no
See the API description for details.
Validate establishes an actual AMQP connection (not the management API) to verify the credentials work end-to-end.
Dependency: management operations authenticate using another
rabbitmq_password secret with administrator tags. At least one admin
RabbitMQ user must exist.
RabbitMQ passwords required fields
| name | description |
|---|---|
token_type |
rabbitmq_password |
management_url |
RabbitMQ Management API server URL |
amqp_host |
whitespace-delimited list of AMQP host names |
amqp_port |
AMQP port |
virtual_host |
RabbitMQ virtual host |
user_group |
user base name |
RabbitMQ passwords managed fields
| name | description |
|---|---|
| (secret) | password |
user_name |
user name based on user_group and token version |
tags |
list of user tags |
limits |
dictionary of user limits |
permissions |
list of permissions |
topic_permissions |
list of topic permissions |
created_at |
ISO8601 timestamp of creation |
active |
whether the password is still valid |
deployed |
whether the password is actually used |
Testing Farm tokens
- Operations: create, destroy, rotate
- Features: single-step rotation (grace period), update, validate
- Steady-state versions: 1
- Grace-period rotation: yes (default 7 days)
Requires the requests-gssapi optional extra. Authenticates via
Kerberos/SPNEGO using the ldap_password secret matching the uid derived from
principal.
The Testing Farm API supports a grace period during token regeneration: after
the API key is regenerated, the previous key remains valid for the configured
duration (default: 7 days). This allows single-step rotation via prepare —
see Grace-period rotation.
Validate calls /whoami, checks that the token is enabled and the token ID
matches.
See the API description for details.
Dependency: authentication uses an ldap_password secret matching the uid
derived from principal. This LDAP password must exist and be valid.
Testing Farm tokens required fields
| name | description |
|---|---|
token_type |
testing_farm_token |
login_url |
Testing Farm Red Hat SSO login URL |
principal |
Kerberos principal for authentication (e.g. user@REALM) |
token_name |
token name in Testing Farm |
ranch |
Testing Farm ranch (public or redhat) |
role |
Testing Farm role (user or admin) |
Testing Farm tokens managed fields
| name | description |
|---|---|
| (secret) | API key |
token_id |
Testing Farm token UUID |
created_at |
ISO8601 timestamp of creation |
expires_at |
ISO8601 expiry date (from Testing Farm expiration) |
active |
whether the token is enabled |
deployed |
whether the token is actually used |
Metrics
python3 -m cki_tools.credentials.metrics
Outputs Prometheus metrics about stored credentials:
| name | labels | description |
|---|---|---|
cki_token_created_at |
name, token_type, active, deployed |
token creation timestamp |
cki_token_expires_at |
name, token_type, active, deployed |
token expiration timestamp |
Environment variables
| Name | Secret | Required | Description |
|---|---|---|---|
CKI_SECRETS_FILE |
no | yes | path to the secrets metadata file |
CKI_VARS_FILE |
no | yes | path to the variables file |
VAULT_ADDR |
no | yes | address of the Vault server expressed as a URL, including port |
VAULT_AUTH_PATH |
no | no | override the path where the authentication method is mounted |
VAULT_MOUNT_POINT |
no | no | mount point of the KV2 secrets engine |
VAULT_PATH_PREFIX |
no | no | path prefix for secrets in Vault, defaults to cki |
VAULT_ID_TOKEN |
yes | no | JWT token (service account) |
VAULT_APPROLE_SECRET_ID |
yes | no | secret ID of the approle (service account) |
VAULT_TOKEN |
yes | no | Vault client token, falls back to ~/.vault-token if not set |
GITLAB_TOKENS |
no | yes | URL/environment variable pairs of GitLab instances and private tokens |
GITLAB_TOKEN |
yes | yes | GitLab private tokens as configured in GITLAB_TOKENS above |
CKI_LOGGING_LEVEL |
no | no | logging level for CKI modules, defaults to WARN; set to INFO for meaningful command line output |
-
Role for the token. Possible values: 10 (Guest), 15 (Planner), 20 (Reporter), 30 (Developer), 40 (Maintainer), and 50 (Owner). Read more at https://docs.gitlab.com/user/permissions/. ↩︎ ↩︎