Retrieves a project/repository specific credential from a 1Password vault.
This action automates the process of loading credentials from 1Password vaults based on project-specific mappings. It looks up the appropriate vault using a JSON mapping keyed on the calling repository's owner, then loads the credential belonging to the calling repository from 1Password.
By default, the action derives the credential path from trusted GitHub
context (the repository owner selects the vault, the repository name selects
the item). The optional
credential_name input can select a differently named item within that same
vault when the stored credential name differs from the repository name (for
example Gerrit-mirrored repositories). Overrides naming anything other than
the repository's own name require a matching grant in the
administrator-controlled allowlist (see
Override Grants). The vault always comes from the owner
mapping, so a repository cannot reach a vault belonging to another project
that shares the same service account.
The action needs jq and openssl on the runner. GitHub-hosted
runners include both tools; self-hosted runners must provide them. The
action checks for them up front and fails with a clear error naming
any missing tool.
steps:
- name: "Load project credentials"
id: credential-load
uses: lfreleng-actions/credential-load-action@main
with:
vault_mapping_json: ${{ secrets.VAULT_MAPPING_JSON }}
op_service_account_token: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}| Name | Required | Default | Description |
|---|---|---|---|
| op_service_account_token | True | n/a | 1Password service account token |
| vault_mapping_json | True | n/a | JSON mapping repository owner to 1Password vault; base64 encoded (preferred) or plain JSON |
| credential_name | False | '' | Explicit 1Password item name; overrides the derived repository name (grant required when it differs from the repository's own name) |
| credential_grants_json | False | '' | JSON array of item names this repository may load via credential_name; wire from the CREDENTIAL_LOAD_GRANTS repository variable |
| export_env | False | false | Export credential as the CREDENTIAL environment variable for all later steps |
| checkout | False | false | Check out the repository as part of this action; opt in with true when a later step needs the default branch checked out |
The action refuses to run and exits with an error in trigger contexts where untrusted fork code could execute with secret access:
- The
pull_request_targetevent, which runs in the base repository's privileged context (with secret access) while operating on fork-controlled code. - Any pull request whose head branch originates from a fork
(
github.event.pull_request.head.repo.fork == true).
Load credentials from a trusted trigger such as push, workflow_dispatch,
or a Gerrit-replicated event instead.
The action derives the vault from trusted GitHub context (the repository owner); the calling workflow cannot override the vault selection.
First, the action calls another action:
- name: "Get 1Password vault from JSON lookup table"
uses: lfreleng-actions/json-key-value-lookup-action@v0.1.0
id: vault_lookup
with:
json: ${{ inputs.vault_mapping_json }}
key: ${{ github.repository_owner }}
exit_on_failure: 'true'This requires JSON, typically provided from a GitHub secret. The JSON maps a given repository owner to a 1Password vault.
Here is an example:
[
{"key": "project", "value": "egyqjgwp6qqavqvgodjbiiaqd4" },
{ "key": "second_project", "value": "44bxjvtzkole2oinpahk9km2hy" },
{ "key": "third_project", "value": "9m2ub1pxfbtiniieuxncabx9we" }
]If the github.repository_owner (the repository owner) is "project", then the vault to query will be "egyqjgwp6qqavqvgodjbiiaqd4".
Store the vault mapping secret base64 encoded. Raw JSON in GitHub secrets degrades workflow log redaction: JSON structural characters, such as braces and brackets, cause the masking engine to mis-fire, which renders console logs illegible. Encode the mapping like this:
openssl base64 -A < vault_mapping.jsonThe openssl invocation produces a single unwrapped line and works on
both GNU/Linux and macOS/BSD systems (GNU base64 -w0 achieves the
same result on Linux).
Store the resulting single-line string as the secret value. The action
accepts either form transparently: it first attempts a base64 decode
and, when that does not produce valid JSON, validates the secret content
directly as plain JSON. Whichever form validates, the normalised JSON
(decoded when base64, verbatim when plain) is what feeds the vault
lookup step. Plain JSON secrets keep working, and emit a
notice annotation recommending migration to base64. This fallback
provides a non-disruptive migration path: upgrade calling workflows to a
release of this action that contains this feature, then re-encode the
secrets without breakage.
Regardless of the input encoding, the action masks each vault identifier
in the mapping (via ::add-mask::) so vault identifiers never appear in
workflow logs.
This is then combined with the credential item name to form the full path to the password item:
op://${{ steps.vault_lookup.outputs.value }}/<item-name>/password
The action resolves the item name as follows:
- When the calling workflow sets the
credential_nameinput:- a value equal to the repository's own derived name loads without further checks (it matches the default derivation)
- any other value requires a matching grant in
credential_grants_json(see Override Grants); without one, the action fails
- Otherwise, the action uses the repository's own derived name:
github.event.repository.namewhen present (absent for some triggers, such asworkflow_call), falling back to the repository name from the built-inGITHUB_REPOSITORY(owner/repo), which GitHub populates for every event
The explicit credential_name override supports Gerrit-mirrored
repositories, where the stored credential name can differ from the mirror
repository name (for example, Gerrit project sdc/onap-ui-common maps to
credential name sdc-onap-ui-common).
The action validates both the vault identifier and the resolved item name
against a strict character set before use, preventing manipulation of the
op:// path structure. Each value must match [A-Za-z0-9._-]+ (ASCII
letters, digits, dot, underscore, and hyphen); any other character, including
whitespace or an embedded newline, causes the action to fail.
Loading a credential item other than the repository's own name requires an
explicit grant. Grants are a JSON array of permitted item names, supplied
via the credential_grants_json input. The canonical wiring sources the
grants from a repository variable with the fixed name
CREDENTIAL_LOAD_GRANTS:
- name: "Load project credentials"
id: credential-load
uses: lfreleng-actions/credential-load-action@main
with:
vault_mapping_json: ${{ secrets.VAULT_MAPPING_JSON }}
op_service_account_token: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}
credential_name: 'sdc-onap-ui-common'
credential_grants_json: ${{ vars.CREDENTIAL_LOAD_GRANTS }}With the repository variable set to, for example:
["sdc-onap-ui-common"]This design leverages the Linux Foundation operational model: project teams
do not hold repository administration rights, so repository variables are an
administrator-controlled channel. The Release Engineering team grants access
to extra credential items per repository, on demand, through the GitHub
portal. Reusable workflows in this organisation wire credential_grants_json
from vars.CREDENTIAL_LOAD_GRANTS internally and do not expose it as a
caller-facing input.
Every active override emits a notice annotation in the run log and a line
in the step summary, providing an audit trail.
Note the residual trust boundary: the 1Password service account token grants
vault-wide read access, so code that holds the token can read any item in
the vault without this action. The grants mechanism provides guardrails and
auditability within the maintained actions estate, but cannot substitute
for token scoping. For release jobs, prefer binding
OP_SERVICE_ACCOUNT_TOKEN to a protected GitHub environment (with branch
restrictions and/or required reviewers) to gate token access itself.
By default (export_env: 'false') the action does not write the credential
to the job environment. Instead it sets the credential output, scoping access
to steps that explicitly reference it.
- name: "Load project credentials"
id: credential-load
uses: lfreleng-actions/credential-load-action@main
with:
vault_mapping_json: ${{ secrets.VAULT_MAPPING_JSON }}
op_service_account_token: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}
- name: Run Maven
uses: lfreleng-actions/maven-make-build-action@v0.1.0
env:
NEXUS_PASSWORD: ${{ steps.credential-load.outputs.credential }}Set export_env: 'true' when you need the credential available to every later
step in the job as the CREDENTIAL environment variable. This broadens
exposure, so prefer the output where practical.
- name: "Load project credentials"
uses: lfreleng-actions/credential-load-action@main
with:
vault_mapping_json: ${{ secrets.VAULT_MAPPING_JSON }}
op_service_account_token: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}
export_env: 'true'
- name: Run Maven
uses: lfreleng-actions/maven-make-build-action@v0.1.0
env:
NEXUS_PASSWORD: ${{ env.CREDENTIAL }}| Name | Description |
|---|---|
| credential | The loaded credential, populated when export_env is false |
When export_env is true, the credential is instead exported into the job
environment as CREDENTIAL and the credential output is empty.
- Trigger Guard: Refuses to run on
pull_request_targetor fork pull requests before using the service account token - Checkout: Optionally checks out the repository with
persist-credentials: false(requirescheckout: 'true'; skipped by default) - Mapping Normalisation: Decodes the vault mapping from base64 (falling back to plain JSON), validates it, and masks each vault identifier
- Vault Lookup: Uses the repository owner as a key to look up the vault from the JSON mapping
- Path Derivation: Builds and validates a repository-scoped
op://path from trusted GitHub context, withcredential_nameas an optional item-name override (grant-gated when it names a different item) andGITHUB_REPOSITORYas a fallback whengithub.event.repository.nameis absent - Credential Loading: Loads the credential from 1Password using the derived vault and item name
- The action loads credentials using the pattern:
op://{vault}/{item-name}/password, where the item name defaults to the repository name - The vault mapping JSON should map repository owner names to their corresponding 1Password vaults; store it base64 encoded to preserve log redaction
- By default the action exposes the credential via the
credentialoutput; setexport_env: 'true'to use theCREDENTIALenvironment variable instead - The action needs no repository files itself, so it performs no checkout
by default: a checkout resets and cleans the working tree, discarding
untracked files (such as build output) and replacing any non-default
checkout (such as a Gerrit change, or artefacts downloaded from earlier
jobs) with the default GitHub context. Set
checkout: 'true'when a later step needs the default branch present and the job performs no checkout of its own (versions before v2 checked out unconditionally)