Skill 32 · Managing Workflow Secrets
Subchapter 32.1
references/actions.mdMarkdown8 KBView on GitHub
The three centralized composite/node actions live in bitwarden/gh-actions and are always
referenced at @main. Their contracts below are the source of truth for what inputs are valid and
what outputs you can consume.
bitwarden/gh-actions/azure-login@main — composite action that performs an OIDC federated login
to Azure, with built-in retry.
| Input | Required | Default | Notes |
|---|---|---|---|
tenant_id | yes | — | ${{ secrets.AZURE_TENANT_ID }} |
client_id | yes | — | An AZURE_CLIENT_ID* secret (see identities below) |
subscription_id | no | "" | ${{ secrets.AZURE_SUBSCRIPTION_ID }}; omit only with allow_no_subscriptions |
allow_no_subscriptions | no | "false" | Set true for Partner Center (no subscription) |
retry_base_delay | no | "5" | Seconds; base for the exponential backoff between attempts |
Behavior worth knowing:
Azure/login in up to three attempts with exponential backoff
(retry_base_delay × 1, then × 2), so transient OIDC failures self-heal. You do not need to add
your own retry around it.id-token: write on the job. Without it the federated login cannot mint a
token and the step fails. This is the most common cause of “login works locally / on one job but
not another”.environment: resolves the secrets.AZURE_* expressions to empty strings and the login
fails. You cannot read the scope from the workflow — ask rather than assume.subscription-id needed for tenant-only auth. With allow_no_subscriptions: true, drop
subscription_id entirely (used only for Partner Center tenants with no subscription).bitwarden/gh-actions/azure-logout@main — composite action, no inputs. It runs
az logout || true, so it is safe to call even if the session is already gone.
azure-login.get-keyvault-secrets (secret step outputs persist past logout),
unless a later step needs the live session (az acr login, azcopy, az keyvault secret show/set), in which case it moves to just after that step.if: condition on the corresponding azure-login.bitwarden/gh-actions/get-keyvault-secrets@main — node action that reads named secrets from a
Key Vault and exposes each as a step output.
| Input | Required | Notes |
|---|---|---|
keyvault | yes | Name of the Key Vault (not a URL); provided per task — e.g. KEY-VAULT |
secrets | yes | Comma-separated list of secret names (inline for ≤2, folded block scalar for ≥3) |
Outputs:
${{ steps.<id>.outputs.<SECRET-NAME> }}.outputs.SECRET-NAME-1 and
outputs.secret-name-1 resolve to the same value. Both casings appear in real workflows; match
the requested name for readability.azure-login has already run in the same job (it uses the active session).Usage notes:
Use id: secrets. It reads clearly downstream (steps.secrets.outputs.<NAME>) and stays
the same across files. Older workflows use get-kv-secrets / retrieve-secrets /
retrieve-secret / get-secrets; prefer secrets for new work. For a second vault in the same
job, disambiguate by purpose (secrets, secrets-2) rather than reverting to a mechanism-named
id.
Format the secrets: list by size. One or two secrets can stay inline as a quoted string;
three or more go in a folded block scalar, one per line, so the list is readable and diffs
cleanly:
secrets: >-
SECRET-NAME-1,
SECRET-NAME-2,
SECRET-NAME-3A single call can fetch many secrets. To pull from two vaults, use two steps (one login/logout
brackets both) with a distinct id: per retrieval.
The secrets: list can be built from inputs or workflow-level env: rather than hard-coded.
Retrieve only what the job uses, and consume via step-scoped env:. The retrieved values
are masked in logs, but do not echo/cat/log them, write them to files, or pass them as CLI
args — masking is a backstop, not a license. An exposed token is a critical incident.
Only the OIDC triad is stored as GitHub Actions secrets; everything else lives in Key Vault.
| GitHub secret | Purpose |
|---|---|
AZURE_SUBSCRIPTION_ID | Azure subscription for OIDC |
AZURE_TENANT_ID | Azure AD tenant for OIDC |
AZURE_CLIENT_ID | Default app registration for OIDC federated login |
The client_id sometimes uses a purpose-specific identity for least privilege rather than the
default AZURE_CLIENT_ID — for example a read-only Key-Vault identity, a write-back identity used
only for secret rotation (az keyvault secret set), a Partner Center identity paired with
allow_no_subscriptions: true, or a per-environment identity selected dynamically in a deploy.
These are named as AZURE_CLIENT_ID_<PURPOSE> GitHub secrets and each corresponds to a real Azure
app registration.
Pick the narrowest identity that fits the job. Use the identity you were given for the task; do
not introduce a new AZURE_CLIENT_ID_* on your own — if the right one is unclear, confirm with
the user rather than guessing or inventing a name.
The keyvault value and every entry in the secrets: list are supplied per task. Never infer
them from the repository, the workflow’s contents, or the surrounding steps, and never invent
one. If a name is missing or you are unsure, flag it to the user and ask.
KEY-VAULT for the vault and SECRET-NAME-1,
SECRET-NAME-2, … for the secret names, and swap in the real values only once they are confirmed.get-keyvault-secrets section above regardless of the name.This skill contains no catalogue of real vault or secret names. Confirm names with the user or read them from the file you are editing.