DevSecOps Identity Integration Guide: Securing CI/CD Pipelines and Developer Workflows
CI/CD pipelines are among the most privileged identities in any organization. They deploy code to production, access secrets, modify infrastructure, and interact with dozens of systems, often with broad permissions that would alarm any security team if a human held them. Yet pipelines are frequently the least governed identities in the enterprise.
The DevSecOps movement recognizes that security must be integrated into the development lifecycle, not bolted on at the end. Identity is a cornerstone of that integration: who (or what) is running this pipeline, what permissions does it have, how are credentials managed, and is there an audit trail?
This guide covers practical approaches to integrating identity security into CI/CD pipelines and developer workflows.
Prerequisites
- CI/CD platform, GitHub Actions, GitLab CI, Jenkins, Azure DevOps, CircleCI, or similar.
- Cloud provider accounts, AWS, Azure, GCP, or other infrastructure targets for deployments.
- Secrets management solution, HashiCorp Vault, AWS Secrets Manager, Azure Key Vault, or similar.
- Familiarity with pipeline configuration, YAML workflows, pipeline stages, and environment variables.
- A security team willing to partner with engineering, DevSecOps requires collaboration, not gatekeeping.
Architecture: Pipeline Identity Model
The Identity Chain in CI/CD
Every CI/CD pipeline execution involves multiple identity layers:
Developer (triggers pipeline)
↓ (authenticated via IdP + MFA)
Source Code Platform (GitHub, GitLab)
↓ (repository permissions, branch protection)
CI/CD Runtime (Actions runner, GitLab runner, Jenkins agent)
↓ (runner identity, OIDC token)
Cloud Provider (AWS, Azure, GCP)
↓ (assumed role, managed identity)
Target Environment (production, staging)
↓ (deployment permissions)
Applications and Services
Each transition in this chain is an identity boundary. The security of your deployment depends on the weakest link.
The Shift from Static Credentials to OIDC Federation
The traditional approach to pipeline authentication involves storing long-lived credentials (AWS access keys, Azure service principal secrets, GCP service account keys) as CI/CD secrets. This approach has significant problems:
- Credentials do not expire (or have long expiration periods).
- Credentials are shared across many pipelines.
- Rotation is manual and often neglected.
- A leaked credential grants access until someone notices and rotates it.
- There is no way to tie a specific pipeline run to a specific credential use.
OIDC federation eliminates static credentials entirely. The CI/CD platform issues a short-lived, signed token for each pipeline run. The cloud provider trusts that token and grants temporary credentials scoped to the specific pipeline.
GitHub Actions Run → OIDC Token (audience: AWS, subject: repo:org/app:ref:refs/heads/main)
↓
AWS STS → AssumeRoleWithWebIdentity (verify OIDC token, check trust policy)
↓
Temporary AWS credentials (1 hour by default, 15 minutes minimum, scoped to one role)
Step-by-Step Implementation
Step 1: Implement OIDC Federation for CI/CD
GitHub Actions to AWS:
Create an IAM OIDC provider in AWS:
aws iam create-open-id-connect-provider \
--url https://token.actions.githubusercontent.com \
--client-id-list sts.amazonaws.com
Do not hardcode a --thumbprint-list. The parameter is optional: if you omit it, IAM retrieves the thumbprint itself, and for providers whose certificates chain to a CA in AWS's trusted root library, such as GitHub's token.actions.githubusercontent.com, AWS verifies the JWKS endpoint's TLS certificate against those roots and only falls back to thumbprints if that fails (AWS IAM docs). A pinned thumbprint copied from a blog post is at best ignored and at worst breaks federation after a certificate change. GitHub's AWS OIDC guide only asks for the provider URL and the sts.amazonaws.com audience.
Create an IAM role with a trust policy that restricts which repositories and branches can assume it:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::123456789012: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:my-org/my-app:ref:refs/heads/main"
}
}
}
]
}
Use it in a GitHub Actions workflow:
name: Deploy to AWS
on:
push:
branches: [main]
permissions:
id-token: write # Required for OIDC
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/github-deploy-role
aws-region: us-east-1
- name: Deploy
run: aws ecs update-service --cluster prod --service my-app --force-new-deployment
Credentials from AssumeRoleWithWebIdentity last one hour by default. You can request anywhere from 900 seconds (15 minutes) up to the role's maximum session duration, which is configurable from 1 to 12 hours (AWS STS API reference). Ask for the shortest duration that covers the deploy job.
GitHub Actions to Azure:
- name: Azure Login
uses: azure/login@v2
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
# No client-secret needed - uses OIDC
GitHub Actions to GCP:
- name: Authenticate to GCP
uses: google-github-actions/auth@v2
with:
workload_identity_provider: projects/123456/locations/global/workloadIdentityPools/github/providers/my-org
service_account: deploy@my-project.iam.gserviceaccount.com
Step 2: Scope Pipeline Permissions Tightly
Each pipeline should have only the permissions it needs for its specific job.
Principle: One role per pipeline purpose
Do not share a single broad role across all pipelines. Create purpose-specific roles:
# Roles for different pipeline stages
deploy-role:
permissions:
- ecs:UpdateService
- ecs:DescribeServices
- ecs:DescribeTaskDefinition
- ecs:RegisterTaskDefinition
- ecr:GetAuthorizationToken
- ecr:BatchGetImage
trust: repo:my-org/my-app:ref:refs/heads/main
terraform-plan-role:
permissions:
- Read-only access to all resources
- No write permissions
trust: repo:my-org/infrastructure:* # any branch can plan
terraform-apply-role:
permissions:
- Full infrastructure management
trust: repo:my-org/infrastructure:ref:refs/heads/main # only main can apply
Restrict by branch and environment:
{
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:sub": "repo:my-org/my-app:environment:production"
}
}
}
This ensures only deployments to the "production" GitHub environment (which requires approvals) can assume the production deployment role.
Step 3: Secure Secrets in Pipelines
Even with OIDC for cloud access, pipelines still need other secrets: API keys for third-party services, database credentials, signing keys.
Secret management hierarchy:
- OIDC federation, For cloud provider access. No secrets stored.
- Dynamic secrets, For database credentials and API keys. Use Vault or equivalent to generate short-lived credentials per pipeline run.
- Platform-managed secrets, For secrets that cannot be made dynamic. Use the CI/CD platform's encrypted secrets storage with appropriate access controls.
- Never: hardcoded secrets, Never commit secrets to source code, even in encrypted form.
HashiCorp Vault integration example:
jobs:
deploy:
steps:
- name: Import Vault Secrets
uses: hashicorp/vault-action@v3
with:
url: https://vault.mycompany.com
method: jwt
role: github-deploy
jwtGithubAudience: vault.mycompany.com
secrets: |
secret/data/production/database username | DB_USERNAME ;
secret/data/production/database password | DB_PASSWORD ;
secret/data/production/api-keys stripe | STRIPE_KEY
Vault verifies the GitHub OIDC token and issues secrets only to authorized repositories and branches.
Preventing secret leakage:
- Enable secret masking in CI/CD logs (most platforms do this automatically for registered secrets).
- Use step-level permissions to limit which steps can access which secrets.
- Never echo or print environment variables in pipeline logs.
- Scan pipeline logs for accidental secret exposure using tools like trufflehog or gitleaks.
Step 4: Govern Service Accounts and Pipeline Identities
Pipelines are identities too, and they need governance.
Service account inventory:
Maintain a registry of every service account, bot account, and pipeline identity:
| Identity | Purpose | Owner | Permissions | Last Reviewed | Credential Type |
|---|---|---|---|---|---|
| github-deploy-prod | Production deployment | Platform Team | ECS deploy, ECR pull | 2026-03-01 | OIDC (no stored creds) |
| terraform-ci | Infrastructure management | SRE Team | Full infrastructure | 2026-02-15 | OIDC |
| sonarqube-bot | Code quality scanning | Security Team | Repo read-only | 2026-03-10 | PAT (90-day rotation) |
Lifecycle management for pipeline identities:
- Creation, Requires a ticket with business justification, defined permissions, and an assigned owner.
- Review, Quarterly access review by the owning team. Verify permissions are still appropriate and not over-provisioned.
- Rotation, For identities that still use static credentials, enforce rotation schedules (90 days maximum is a common practitioner estimate, not a standard).
- Decommission, When a pipeline or project is retired, decommission the associated identities. Do not let orphaned pipeline accounts persist.
Step 5: Implement Pipeline Security Controls
Branch protection as identity control:
Branch protection rules are access control for your code supply chain:
# GitHub branch protection for main
protection_rules:
required_reviews: 2
require_codeowner_review: true
dismiss_stale_reviews: true
require_signed_commits: true
enforce_admins: true
required_status_checks:
- security-scan
- unit-tests
- integration-tests
Environment-based deployment gates:
GitHub Environments provide approval gates for deployments:
jobs:
deploy-production:
environment:
name: production
url: https://app.mycompany.com
# This job will wait for manual approval from designated reviewers
# before executing
Configure production environments to require approval from senior engineers or SREs.
Deployment approval via identity:
Integrate deployment approvals with your IdP:
- Only users in the "production-deployers" IdP group can approve production deployments.
- Require MFA step-up for deployment approvals.
- Log all approval decisions with the approver's identity and timestamp.
Step 6: Audit and Monitor Pipeline Activity
What to log:
- Every pipeline execution: who triggered it, what branch, what commit
- Every credential assumption: which OIDC token, which role, what permissions
- Every secret access: which secrets were retrieved, by which pipeline
- Every deployment: what was deployed, to which environment, by whom
- Every failed authentication or authorization: potential attack indicators
Monitoring alerts:
- Pipeline assuming a role outside its normal pattern
- Failed OIDC token validation (potential replay or forgery attempt)
- Pipeline accessing secrets it has never accessed before
- Deployment to production from an unexpected branch or repository
- Service account credential used outside of pipeline context (potential credential theft)
Best Practices
Treat Pipelines as Privileged Identities
Your CI/CD pipeline that deploys to production has the same blast radius as a production admin. Treat it accordingly: review its permissions quarterly, monitor its activity, and ensure it follows least privilege.
Eliminate Static Credentials Everywhere Possible
Every static credential is a liability. Prioritize eliminating them:
- Cloud provider access: OIDC federation (no stored credentials)
- Database access: Dynamic credentials via Vault
- API access: Short-lived tokens via OAuth client credentials
- Container registry access: Cloud-native authentication (ECR, ACR, Artifact Registry use cloud IAM)
Separate Build and Deploy Permissions
The identity that builds your code should not be the same identity that deploys it. This separation of duties prevents a compromised build step from deploying malicious code:
jobs:
build:
permissions:
contents: read
packages: write
# Can read code and push container images, but cannot deploy
deploy:
needs: build
environment: production
permissions:
id-token: write
# Can deploy but cannot modify code or build artifacts
Rotate What You Cannot Federate
For secrets that cannot be replaced by OIDC federation, enforce aggressive rotation:
- API keys: 90-day maximum lifetime
- Database passwords: 30-day rotation via Vault
- Signing keys: Annual rotation with overlap period
- Personal access tokens: 30-day maximum, scoped to specific permissions
Testing
- OIDC trust policy testing, Verify that only intended repositories and branches can assume each cloud role. Test that other repositories are denied.
- Permission boundary testing, Deploy with each pipeline role and verify it can perform its intended actions and is denied for everything else.
- Secret access testing, Verify that secrets are only accessible to authorized pipelines and steps.
- Break-glass testing, Simulate a pipeline failure and verify that manual deployment procedures work (using human credentials with appropriate MFA).
- Audit trail testing, Execute a deployment and verify the complete audit trail is captured in your SIEM.
Common Pitfalls
Overly Permissive OIDC Trust Policies
A trust policy that allows repo:my-org/* lets any repository in your organization assume the role. If someone creates a new repository with a malicious workflow, it inherits trust. Restrict trust policies to specific repositories and branches.
Shared Pipeline Credentials
Using one set of credentials for all pipelines is the anti-pattern that OIDC federation eliminates. Each pipeline should have its own identity with its own permissions. Shared credentials make it impossible to audit which pipeline performed which action.
Not Scanning Pipeline Definitions for Security Issues
Pipeline YAML files can contain security misconfigurations: overly broad permissions, missing approval gates, unmasked secret references. Include pipeline definitions in your security scanning scope.
Ignoring Self-Hosted Runners
Self-hosted CI/CD runners are compute resources with access to your pipeline secrets and cloud credentials. They must be hardened, patched, and monitored like any other privileged infrastructure. Ephemeral runners (destroyed after each job) are strongly preferred over persistent runners.
Conclusion
DevSecOps identity integration is about treating CI/CD pipelines as first-class identities deserving the same governance, least privilege, and monitoring that you apply to human users. OIDC federation eliminates the most dangerous pattern, static credentials stored in CI/CD platforms, and replaces it with short-lived, scoped, auditable tokens. Combined with secrets management, pipeline permission scoping, and deployment approval gates, this creates a secure software delivery pipeline that does not slow developers down.
The path forward is clear: federate everything you can, dynamically generate what you cannot federate, govern all pipeline identities like privileged accounts, and audit every action in the delivery chain.
Frequently asked questions
- How do you secure identity in a CI/CD pipeline?
- Treat each pipeline as a privileged identity. Replace stored cloud keys with OIDC federation, give each pipeline purpose its own narrowly scoped role, fetch remaining secrets dynamically from a manager such as HashiCorp Vault, gate production deploys behind approvals, separate build and deploy permissions, and log every role assumption, secret read and deployment.
- How does OIDC federation work for GitHub Actions?
- Each workflow run requests a short-lived signed token from GitHub. The cloud provider verifies it against a trust policy that checks the audience and the subject claim, such as a specific repository, branch or environment, then issues temporary credentials for one role. On AWS those credentials last one hour by default and can be set as short as 15 minutes. No long-lived key is stored in CI.
- Do I need to set a thumbprint when creating the GitHub OIDC provider in AWS?
- No. The thumbprint parameter is optional in the AWS CLI and API, and IAM retrieves it if you omit it. For providers like GitHub whose certificates chain to a CA in AWS's trusted root library, AWS verifies the JWKS endpoint's TLS certificate directly and uses thumbprints only as a fallback. Avoid hardcoding a thumbprint copied from older examples.
- What is the most common OIDC trust policy mistake?
- Using a wildcard subject such as repo:my-org/* lets every repository in the organization assume the role, so anyone who creates a repository with a malicious workflow inherits that trust. Restrict the subject claim to the exact repository and branch, or to a protected GitHub environment that requires approval before deployment.
- Does OIDC federation work with self-hosted CI/CD platforms like Jenkins?
- Jenkins does not issue OIDC tokens out of the box the way GitHub Actions and GitLab CI do. The OpenID Connect Provider plugin adds that: it issues short-lived signed id tokens to builds that AWS, Google Cloud, Azure workload identity federation or Vault can verify. Alternatively, authenticate Jenkins to Vault with AppRole and fetch short-lived credentials from there.
- How do we handle secrets for local development?
- Developers should never hold production secrets locally. Use Vault-backed development secrets or mock services for local testing. For cloud access, issue short-lived credentials through your IdP with commands such as aws sso login or az login, and never hand out long-lived access keys for laptops, where they end up in shell history and dotfiles.
- How do we prevent a compromised dependency from stealing pipeline credentials?
- Pin dependencies and third-party actions to specific versions and commit SHAs rather than mutable tags. Limit network egress from pipeline steps. Split pipelines into stages with isolated credentials so the build stage never holds deployment credentials, and scan dependencies for known vulnerabilities before building. Keep a documented, MFA-protected manual deploy path, tested quarterly, for when CI itself is down.
- Should pipeline service accounts be part of access reviews?
- Yes. Keep an inventory of every service account, bot and pipeline identity with an owner, purpose and credential type. Review each quarterly with the owning team as reviewer, confirm the pipeline is still active and its permissions still fit, rotate any remaining static credentials, and decommission identities when the pipeline or project is retired.
Related on Start with Identity
- GuideConditional Access Policies: A Complete Implementation Guide for Microsoft Entra
Master Microsoft Entra conditional access with risk-based policies, device compliance rules, location-based restrictions, and real-world deployment patterns.
- GuideGreenfield CIAM: how to ship the first version in 8 weeks
A practical 8-week plan to ship the first version of a customer identity system for a new product: build vs buy vs open source, what to build, what to defer, cu
- GuideIAM Cloud Migration Guide: From On-Prem Active Directory to Cloud Identity
A practical guide to migrating from on-premises Active Directory to cloud IAM, covering hybrid identity architecture, migration phases, coexistence strategies,
- BlogGhostAction returns: about 1,900 hijacked GitHub accounts pushed secret-stealing workflows to 30,000 repos
GitGuardian counts more than 30,000 repositories and about 1,900 compromised GitHub accounts in GhostAction's October wave, which committed workflows that dump
- BlogGyazo breach exposes 23.6 million accounts, including session IDs and X integration tokens
An attacker reached Gyazo's database through its image upload server and took 23.62 million user records: emails, password hashes, login session IDs, device IDs
- ArticleIAM for Mergers and Acquisitions: Identity Integration from Day 1 to Full Consolidation
A strategic guide to managing identity through M&A transactions, covering Day 1 access requirements, directory consolidation strategies, culture alignment chall