GitHub Actions cloud authentication standard
Summary
To be effective, GitHub Actions workflows that authenticate to a cloud provider must do so without storing long-lived static credentials. This standard defines how all MoJ teams using GitHub Actions in the ministryofjustice organisation must authenticate to cloud providers (AWS, Azure, GCP, etc.).
This will enable
- organisations to have reliable, auditable assurance over its cloud credential posture
- teams have a single clear answer to ‘how should my pipeline authenticate to the cloud?’
Requirements
This standard is made up of the following requirements
- Authenticating via OIDC
- Distinguishing the identity provider from the credential mechanism
- Workflows using
id-token: writewhen using OIDC - Exceptions for long-lived credentials
- Identifying high-blast-radius repositories
- Credential and variable naming convention
- Flagged repositories and the verification process
- Continuous monitoring of cloud authentication posture
- Related standards
- Appendix A - migration walkthrough (AWS)
- References
Authenticating via OIDC
GitHub Actions workflows that authenticate to a cloud provider must use OpenID Connect (OIDC) federated identity. Workflows must not use long-lived static credentials (AWS access keys, Azure client secrets, GCP service account keys) stored as repository or organisation secrets. Any long-lived static credentials must have logged exceptions detailing their usage, as set out below.
OIDC exchanges a short-lived signed token for cloud credentials at the moment they are needed. The cloud provider validates the token’s repository, branch, environment and other claims before issuing a session that typically expires within an hour.
This is preferred over long-lived credentials for five reasons.
No persistent secret to leak
Nothing static is stored in GitHub, so a log dump, malicious dependency, or compromised contributor cannot exfiltrate what does not exist.
Automatic expiry
A leaked token has minutes of utility, not months.
Scoped trust
The cloud-side trust policy can require specific repository, branch, ref or environment claims. A token issued to a feature branch cannot assume a role intended for main.
No rotation burden
There are no keys to rotate, audit, or accidentally let expire.
Cleaner audit trail
Each token exchange is logged with the originating repository and run.
This aligns with the
- National Cyber Security Centre (NCSC) cloud security guidance principle of minimising the lifetime and blast radius of credentials
- Government Digital Service (GDS) service standard requirement to manage information risk
Continued use of long-lived credentials exposes MOJ to
- credential exfiltration through logs and supply-chain dependencies
- stale and over-privileged secrets
- lateral movement from leaked keys usable anywhere on the internet
- a detection gap where keys surface publicly before they are noticed
- dormant-credential risk, where a key left in repository settings after migration remains fully exploitable until deleted
Distinguishing the identity provider from the credential mechanism
Both concern authentication (AuthN) — proving which workload is calling the cloud — as distinct from authorisation (AuthZ). Authz governs what that workload may then do (covered here only by the least-privilege requirements). Per Microsoft’s identity platform guidance, OIDC is the protocol used for authentication, which is precisely the mechanism this standard mandates. Within AuthN, the identity provider and the credential mechanism are independent decisions, and teams must be able to state the credential mechanism, not only the identity provider.
Identity provider (who the workload is)
For nearly all MoJ teams this is Entra ID (Azure AD), the org-wide SSO. This is true regardless of how any individual workflow authenticates, so “we use Entra ID” does not describe compliance.
Credential mechanism (how the workflow proves it at runtime)
This is what this standard governs. It MUST be one of: a short-lived OIDC federated token (compliant), or a stored long-lived secret (non-compliant, exception required).
An Entra ID app registration can use either mechanism: a registration authenticating via a federated credential is compliant OIDC, while the same registration authenticating via a client secret is a long-lived credential. ‘We use Entra ID’ and ‘this is long-lived’ are frequently both true at once.
When establishing a workflow’s posture, teams must ask questions that name the mechanism directly.
- AWS
- does the workflow assume an IAM role via aws-actions/configure-aws-credentials with role-to-assume and
id-token: write? - does the workflow use a stored AWS access key and secret in GitHub secrets?
- does the workflow assume an IAM role via aws-actions/configure-aws-credentials with role-to-assume and
- Azure
- does the Entra app registration use a federated credential (no stored secret), or a client secret / certificate
- does the workflow use azure/login with
id-token: write?
- is the flagged workflow file actively used in the repository, or is it a dormant workflow that has been left in the repo?
- are there any long-lived secrets stored in the repository, even if it’s using OIDC for some workflows? (e.g., a stored AWS access key or Azure client secret)
Workflows using id-token: write when using OIDC
A workflow is performing OIDC if and only if the job
- sets permissions:
id-token: write; and - uses a cloud-side role or identity assumption, such as:
aws-actions/configure-aws-credentialswithrole-to-assumeazure/loginwith a federated client-idgoogle-github-actions/auth
A workflow without id-token: write must not be treated as OIDC, because the runner cannot mint an OIDC token without it.
Where such a workflow also references AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY, AZURE_CLIENT_SECRET, or GCP_SA_KEY, it is long-lived.
This is the authoritative signal and is reported by the central audit tooling (moj-github-discovery) as the has_id_token_write field.
Exceptions for long-lived credentials
Before logging an exception, teams must first confirm the workflow is still in use. Obsolete workflow files must be deleted rather than migrated or excepted. Long-lived credentials may be retained only where one of the following applies.
The target service does not support OIDC federation. OIDC is technically possible but not yet enabled by the platform team (temporary only; remediation must be tracked). There is a lack of project support, ownership, or capacity to migrate. This is an organisational blocker and the exception MUST name the blocker, an accountable owner, and a target review date. The credential is for a non-cloud-provider service, in which case it is out of scope for this standard but must follow general MOJ secret management guidance.
Where an exception applies, the team must
- record it in the team risk log with a justification and review date
- restrict the credential to least-privilege permissions
- use the naming convention below so audit tooling can discover it
- rotate it on a defined schedule of no longer than 90 days unless the platform team specifies otherwise
- an unrotated long-lived credential is a finding in its own right and MUST NOT be treated as a steady state
- review the exception at least annually
Identifying high-blast-radius repositories
Where a single repository controls a disproportionate share of an estate, for example a DNS repository managing approximately 99.99% of MOJ DNS, it must be classified as a risk asset and its migration prioritised, even where its keys are least-privilege scoped. Scope reduces the permissions blast radius but not the operational blast radius of the systems the repository governs. For such repositories, ‘never rotated’ combined with ‘no clear migration owner’ must be escalated to team leadership rather than left in the risk log.
Credential and variable naming convention
A standardised naming convention is required so that credential usage is discoverable by moj-github-discovery and workflows are readable across teams. Non-standard names defeat automated scanning and must not be used.
OIDC role ARNs and regions are configuration, not secrets, and must be stored as GitHub Actions variables.
- AWS
AWS_ROLE_TO_ASSUMEAWS_REGION
- Azure
AZURE_CLIENT_IDAZURE_TENANT_IDAZURE_SUBSCRIPTION_ID
- GCP
GCP_WORKLOAD_IDENTITY_PROVIDERGCP_SERVICE_ACCOUNT
Where long-lived credentials are retained under an exception, they must be stored as GitHub Actions secrets using these canonical names.
- AWS
AWS_ACCESS_KEY_IDAWS_SECRET_ACCESS_KEYAWS_SESSION_TOKEN(optional)
- Azure
AZURE_CREDENTIALSAZURE_CLIENT_SECRET
- GCP
GCP_SA_KEY
Environment-specific values must be prefixed in upper case, for example PROD_AWS_ROLE_TO_ASSUME or DEV_AWS_ACCESS_KEY_ID.
The following must not be used
- custom or abbreviated names (
MY_AWS_KEY,ACCESS_TOKEN,CLOUD_KEY,K,SECRET1) - personal or team-prefixed names (
JOHN_AWS_KEY,TEAM_X_AWS_SECRET) credentials stored as
- plaintext in workflow files
- repository variables
.envfiles
Any occurrences of the above must be treated as a security incident rather than a compliance deviation.
Flagged repositories and the verification process
A long_lived_credentials classification from moj-github-discovery is a candidate for review, not a confirmed finding. The scan cannot determine whether a flagged file is still in use, or whether a stored secret is live or dormant. A flagged workflow must be resolved into one of three states.
Genuine long-lived — the workflow is live and uses a stored secret. It must be migrated to OIDC or covered by a logged exception. False positive (obsolete file) — the flagged file is no longer in use. It must be deleted, not migrated. Migrated, secrets dormant — the workflow already uses OIDC but old secrets remain in settings. The secrets must be deleted and the underlying identity disabled; until then the dormant credential is a live risk.
Teams must follow this resolution sequence for each flagged repository.
- Confirm whether the file is live or obsolete (delete if obsolete).
- If live, confirm the mechanism using the id-token: write test; if already OIDC, remove dormant secrets.
- If genuinely long-lived, triage need then migrate or log an exception capturing rotation cadence, least-privilege scope, and data-sensitivity / risk asset class.
- Record the verified posture with supporting maintainer confirmation in the remediation log.
Continuous monitoring of cloud authentication posture
Posture must be assessed continuously, not only at a point in time. moj-github-discovery runs against the estate and classifies each workflow as one of
oidclong_lived_credentialsmixednone
A scheduled scan workflow has been developed via moj-github-discovery ticket 160. This workflow can be used to flag new long-lived credential usage. Teams whose workflows are flagged must be contacted to migrate, delete an obsolete file, remove dormant secrets, or log an exception.
Related standards
This standard is the GitHub-Actions-specific application of MOJ’s wider secrets-management guidance and should be read alongside it. The general controls for secret generation, approved secret stores, secret scanning, and incident response are covered by MOJ’s secrets-management standard. This standard does not restate them and focuses only on how GitHub Actions workflows authenticate to cloud providers. SHA pinning of third-party actions is covered by MOJ’s existing SHA-pinning guidance.
Appendix A - migration walkthrough (AWS)
Note: This appendix is for information only. It shows the standard AWS migration path. Equivalent flows for other providers and MOJ platforms are availale in References.
1. Provision the OIDC identity provider in your AWS account
- this should be done once per account utilising the provider URL
https://token.actions.githubusercontent.com - the audience claim should be set to
sts.amazonaws.com - if it is absent, raise a request with the account owner
2. Create an IAM role for your workflow
The IAM role should have a trust policy scoped to the repository, branch, and environment that will be using it. The role should have a least-privilege policy attached to it, scoped to the resources the workflow needs to access.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::ACCOUNT_ID:oidc-provider/token.actions.githubusercontent.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
},
"StringLike": {
"token.actions.githubusercontent.com:sub": "repo:ministryofjustice/YOUR-REPO:ref:refs/heads/main"
}
}
}
]
}
Tighten the sub-claim further where possible, for example to a specific environment or tag.
3. Attach a least-privilege policy to the role
Grant only what the workflow needs. Do not reuse a policy designed for human users.
4. Update your workflow to use the OIDC role
An example workflow snippet is shown below.
The id-token: write permission is required to mint the OIDC token, and the role-to-assume input points to the IAM role created in step 2.
Ensure all actions are pinned to a full commit SHA, not a branch or tag, to avoid supply-chain compromise.
permissions:
id-token: write
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@<full-commit-sha>
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@<full-commit-sha>
with:
role-to-assume: ${{ vars.AWS_ROLE_TO_ASSUME }}
aws-region: ${{ vars.AWS_REGION }}
- run: aws sts get-caller-identity
5. Validate the Workflow
Run against a non-production branch first; confirm with aws sts get-caller-identity that the assumed role and session permissions are correct.
6. Remove Old Credentials
Delete the AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY secrets and disable or delete the underlying IAM user. This step is the most often forgotten; until it is done the dormant key remains fully exploitable and the migration provides no security benefit.
References
- About security hardening with OpenID Connect
- Configuring OpenID Connect in Amazon Web Services
- Azure: Workload Identity Federation with GitHub Actions
- GCP: Workload Identity Federation with deployment pipelines
- Microsoft: authentication vs authorization
- NCSC cloud security guidance
- GDS Service Standard
- Cloud Platform OIDC usage example
- Modernisation Platform OIDC usage example