Stage 5 · Platform
Building Pipelines
Secrets and OIDC
Managing masked secrets, environments, GitHub OIDC, cloud IAM roles, and least-privilege tokens.
Pipeline Secrets
Pipelines need access to secrets — API keys, deploy tokens, database credentials. Hardcoding secrets in workflow files is a critical security vulnerability. CI platforms provide encrypted secret storage that injects values at runtime and masks them in logs.
CI platforms automatically mask secrets in logs, but they cannot mask secrets that appear in unexpected formats. Never echo, print, or log secret values. Never include them in error messages. Never write them to files without proper permissions.
GitHub Actions Secrets
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Deploy to AWS
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- run: aws s3 sync dist/ s3://my-bucket/
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
registry-url: https://registry.npmjs.org
cache: npm
- run: npm ci && npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}Secrets are accessed via the secrets context. They are available in all step expressions but cannot be read directly in job-level if conditions. The NPM_TOKEN is passed as an environment variable because npm requires it there.
GitLab CI Variables
# Protected variables (only available on protected branches/tags)
deploy_key:
value: "LS0tLS1CRUdJTi..."
options:
- protected: true
- masked: true
- file: false
# File-type variables
kubeconfig:
value: |
apiVersion: v1
kind: Config
clusters:
- cluster:
server: https://k8s.example.com
options:
- protected: true
- masked: true
- file: true
jobs:
deploy:
stage: deploy
script:
- kubectl --kubeconfig=$KUBECONFIG apply -f k8s/
environment:
name: production
variables:
KUBECONFIG: $kubeconfigGitLab CI variables can be protected (only on protected branches), masked (hidden in logs), or file-type (written to a temporary file). File-type variables are useful for large values like certificates or kubeconfig files.
GitHub OIDC with Cloud Providers
OpenID Connect (OIDC) eliminates the need for long-lived cloud credentials. GitHub provides a JWT token that cloud providers can verify. The cloud provider trusts GitHub's identity and issues short-lived credentials for the workflow run.
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
steps:
- uses: actions/checkout@v4
- name: Configure AWS credentials via OIDC
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/github-actions-deploy
aws-region: us-east-1
audience: sts.amazonaws.com
- run: aws s3 sync dist/ s3://my-bucket/
- name: Deploy to EKS
run: |
aws eks update-kubeconfig --name my-cluster --region us-east-1
kubectl apply -f k8s/The role-to-assume references an IAM role whose trust policy allows GitHub's OIDC provider. The role grants temporary credentials that expire after the job completes. No AWS keys are stored in GitHub secrets.
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
steps:
- uses: actions/checkout@v4
- id: auth
uses: google-github-actions/auth@v2
with:
workload_identity_provider: projects/123456789/locations/global/workloadIdentityPools/github-pool/providers/github-provider
service_account: github-actions@my-project.iam.gserviceaccount.com
- name: Deploy to Cloud Run
uses: google-github-actions/deploy-cloudrun@v2
with:
service: my-service
region: us-central1
image: gcr.io/my-project/myapp:${{ github.sha }}Workload Identity Federation configures GCP to trust GitHub's OIDC tokens. The service account has minimal permissions for deployment. Credentials are short-lived and scoped to the repository.
Environments and Approvals
jobs:
deploy-staging:
runs-on: ubuntu-latest
environment: staging
steps:
- run: ./deploy.sh staging
deploy-production:
needs: deploy-staging
runs-on: ubuntu-latest
environment:
name: production
url: https://app.example.com
steps:
- run: ./deploy.sh production
# Environment protection rules configured in GitHub UI:
# - Required reviewers (manual approval)
# - Wait timer (e.g., 15 minutes)
# - Branch restrictions (only main)
# - Deployment branchesEnvironments provide a layer of control between the workflow and the deployment target. Configure protection rules in the GitHub repository settings to require manual approval, enforce wait timers, or restrict deployments to specific branches.
Least-Privilege Tokens
- Use OIDC instead of stored credentials — short-lived tokens limit the window of exposure.
- Scope tokens to specific resources — grant deploy access to specific buckets, not all S3.
- Use environment-specific secrets — staging secrets should not work in production.
- Rotate secrets regularly — set expiration dates and automate rotation.
- Audit secret usage — track which workflows use which secrets and remove unused ones.
Secrets management is one layer of defense. Combine it with branch protection, environment approvals, least-privilege IAM, and audit logging. No single measure is sufficient — defense in depth means multiple overlapping controls.
Mark this lesson complete to store local progress and unlock a cleaner resume path the next time you visit.