· via dev.to (home feed)
Guide: replacing Jenkins static AWS keys with OIDC federation and scoped trust policies
A dev.to walkthrough by Oleksandr Kuryzhev explains how to swap long-lived AWS access keys in Jenkins for short-lived STS credentials via OIDC, and lists the trust policy mistakes that quietly undo the benefit.

Static keys out, OIDC in
A guide published on dev.to by Oleksandr Kuryzhev, originally written for his blog kuryzhev.cloud, walks through replacing long-lived AWS access keys in Jenkins with OpenID Connect federation. The author says he has moved three separate Jenkins fleets onto the pattern and that the same configuration mistakes keep recurring across teams.
How the exchange works
Instead of keeping an IAM user's access key in the Jenkins credential store, Jenkins proves who it is on every run. Through an OIDC-capable plugin it mints a signed JWT describing the job — repository, branch, build ID or whichever claims you configure. AWS, configured to trust Jenkins' issuer URL as an OIDC identity provider, validates that token and exchanges it through AssumeRoleWithWebIdentity for temporary credentials.
The trust model is fundamentally different from an access key. A static key is a bearer credential: no expiry, no tie to a specific pipeline, no cryptographic proof of who is calling, and valid from anywhere until manually revoked. An OIDC token is short-lived, bound to a specific subject claim and expires by itself even if a leak goes unnoticed. Crucially, no AWS secret is ever stored in Jenkins — the assertion is generated at runtime and checked against a trust policy before STS issues anything, so there is nothing sitting at rest to steal.
The mistakes that undo the benefit
The most common failure, according to the post, is an overly broad trust policy: conditioning only on the audience claim, or wildcarding the subject claim so any job in any repo on any branch can assume a role meant for production deploys. That reproduces the exposure of the static key it replaced.
Other recurring problems:
- Treating the Jenkins issuer URL as permanent. A new load balancer, domain or certificate changes the OIDC discovery and JWKS endpoints, so every trust policy referencing the old issuer silently stops matching. Pipelines later fail with opaque AccessDenied errors on AssumeRoleWithWebIdentity that nobody connects to the infrastructure change.
- One shared IAM role. Deploy jobs and read-only lint jobs get identical permissions, so a compromised low-trust job — say a PR build with a tampered pipeline script — reaches as far as production.
- HTTPS requirements. AWS must fetch the JWKS document over valid HTTPS. Internal-only Jenkins instances or self-signed certificates fail token validation silently, with no error at setup time, just mysterious auth failures on first use.
The configuration that holds up in production
First, register Jenkins as an IAM OIDC identity provider using its public HTTPS discovery URL. AWS fetches and caches the JWKS thumbprint from that endpoint; this is a one-time registration per AWS account, not per role.
Then pin the trust policy to specific claims using StringEquals, never StringLike with wildcards, for anything that touches production:
{ "Effect": "Allow", "Principal": { "Federated": "arn:aws:iam::123456789012:oidc-provider/jenkins.example.com/oidc" }, "Action": "sts:AssumeRoleWithWebIdentity", "Condition": { "StringEquals": { "jenkins.example.com/oidc:aud": "sts.amazonaws.com", "jenkins.example.com/oidc:sub": "repo:my-org/infra:ref:refs/heads/main" } } }
Inside the pipeline, the plugin step oidcIdToken mints the token and a direct call to the AWS CLI exchanges it for credentials (the example uses a 900-second duration). Those credentials exist only in the job's environment for the duration of the stage and never touch the credential store.
The rule the author refuses to bend: one role per trust boundary. Plan-only versus apply, staging versus production, read-only versus deploy — each gets its own role, its own trust policy and a narrow permission set, so a compromised job never exceeds its smallest legitimate need.
Advanced patterns and running costs
For multi-account setups, register the OIDC provider once per account that needs to trust Jenkins rather than trying to build a single cross-account provider. Each account still scopes the subject claim to a specific job path, so Jenkins never holds account-wide access.
Branch and PR scoping is where OIDC outperforms static keys: encode the branch or tag into the subject claim so only merges to main can assume the deploy role, while PR builds get a separately scoped, usually read-only role. This has to be enforced in the trust policy, not in pipeline conditionals — a PR can edit the pipeline script itself, but it cannot edit an IAM trust policy in another account.
Session tags close an audit gap: passing job name, requester and git SHA on the assume-role call means CloudTrail shows exactly which pipeline execution made which API call, rather than a generic role entry. Permission boundaries add defense in depth for when trust-policy review lags behind team growth.
On cost: the exchange adds roughly one extra network round trip per run, typically 100–300 milliseconds per the post. STS calls are free, but thousands of nightly builds generate real CloudTrail volume worth budgeting. Tokens last roughly 15 minutes to an hour by default, so long-running jobs such as multi-hour Terraform applies need to re-assume or chain roles.
Why it matters
A leaked static CI key keeps working everywhere until someone remembers to rotate it — which, as the author points out, is rare. Federation removes the stored secret entirely and turns the security question into reviewable IAM configuration. But the benefit is conditional: a wildcarded subject claim or a shared role quietly recreates the old problem. For teams running Jenkins against AWS, the guide's checklist of silent failure modes — JWKS fetch problems, issuer drift after infrastructure changes — is as valuable as the setup steps themselves.
- #aws
- #jenkins
- #oidc
- #ci-cd
- #security