iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Secrets

CI secrets (API keys, DB passwords, signing certs) live in the platform’s vault — never in source. Inject them at job-time, scope to the smallest job, prefer short-lived tokens via OIDC.

Storage, masking, OIDC, leak prevention

EXAMPLE
# 1) Where secrets live
#   - GitHub Actions  : Repo/Org/Env Secrets → ${{ secrets.NAME }}
#   - GitLab CI       : Settings → CI/CD → Variables (mask + protect)
#   - CircleCI        : Project Settings → Environment Variables
#   - Jenkins         : Credentials → store as Secret Text/Username-Password/SSH/File
#   - AWS / GCP / Azure: native secret managers — fetched by the runner

# 2) GitHub Actions — basics
name: deploy
on: { push: { branches: [main] } }
jobs:
    deploy:
        runs-on: ubuntu-latest
        environment: { name: production }   # required for env-scoped secrets
        steps:
            - uses: actions/checkout@v4
            - run: ./deploy.sh
              env:
                  DB_URL:    ${{ secrets.DB_URL }}
                  API_TOKEN: ${{ secrets.API_TOKEN }}

# 3) GitHub Actions — short-lived AWS creds via OIDC (NO long-lived keys!)
jobs:
    deploy:
        runs-on: ubuntu-latest
        permissions:
            id-token: write          # OIDC
            contents: read
        steps:
            - uses: actions/checkout@v4
            - uses: aws-actions/configure-aws-credentials@v4
              with:
                  role-to-assume: arn:aws:iam::123456789012:role/gh-deploy
                  aws-region:     us-east-1
            # No secrets in the workflow!
            - run: aws s3 sync ./dist s3://my-bucket

# 4) GitLab CI — variables
variables:
    NODE_ENV: production
deploy:
    stage: deploy
    script:
        - ./deploy.sh
    # $DB_URL and $API_TOKEN come from project CI variables
    rules:
        - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'

# In project settings:
# - 'Mask variable' so values don't appear in logs
# - 'Protected variable' so only protected branches/tags get it
# - File-type variables for certs (private key, kubeconfig)

# 5) Use secrets from a cloud secret manager at job-time
# AWS Secrets Manager:
- name: Fetch DB password
  run: |
      DB_PW=$(aws secretsmanager get-secret-value --secret-id prod/db --query SecretString --output text)
      echo "::add-mask::${DB_PW}"
      echo "DB_PASSWORD=${DB_PW}" >> $GITHUB_ENV

# Vault (HashiCorp):
- uses: hashicorp/vault-action@v3
  with:
      url:    https://vault.example.com
      method: jwt                                # use OIDC from GitHub
      role:   gh-deployer
      secrets: 'secret/data/prod/db url | DB_URL ;'

# 6) Mask + redact in logs
# GitHub Actions masks `secrets.*` automatically. For computed secrets:
echo "::add-mask::${VALUE}"

# Don't `echo $SECRET` for debugging — even masked, the surrounding text leaks structure.

# 7) DON'T do these
#   ❌ Hardcode secrets in repo files (config.yml, .env)
#   ❌ Print secrets to logs
#   ❌ Pass secrets via command-line args (visible in `ps`)
#   ❌ Commit private keys, even if 'temporary'
#   ❌ Long-lived access keys (AWS_ACCESS_KEY_ID / OPENAI_KEY) when OIDC is available
#   ❌ A single ROOT-permission secret used everywhere — split by scope

# 8) Prevent leaks — pre-commit + CI scans
# pre-commit (developer side)
# - repo: https://github.com/gitleaks/gitleaks
#   rev: v8.18.0
#   hooks:
#     - id: gitleaks

# CI (last line of defence)
- uses: gitleaks/gitleaks-action@v2
  env: { GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} }

# 9) Rotate + audit
#   - Quarterly rotation for service-account secrets
#   - Monthly review: who has access to which secret?
#   - Alert when a secret is used outside expected workflows/repos
#   - On compromise: rotate FIRST, investigate second

# 10) Local dev secrets
#   - .env file (in .gitignore!) — load via dotenv
#   - 1Password CLI / Doppler / Infisical — sync prod secrets to dev safely
#   - Don't share prod secrets in chat — use a secret manager link

# 11) Secret scopes — least privilege
#   GLOBAL    : almost nothing — only universal infrastructure (Sentry DSN)
#   ORG       : shared services (shared S3 bucket creds)
#   REPO      : per-project tokens (deploy keys, third-party APIs)
#   ENV       : production vs staging (different DB passwords, OAuth keys)
#   EPHEMERAL : OIDC-assumed roles — most secure for cloud access

# 12) Special cases
#   - Signing keys (Apple, Google Play): use platform-specific encrypted storage
#   - GPG keys for signed commits: encrypted env vars + base64
#   - SSL certs: GitHub Actions File-type secrets; render to disk at job-time

# 13) Real-world workflow
# Bad commit pushes a secret → immediate panic-rotation:
#   1. Rotate the leaked secret AT THE SOURCE (cloud provider, API)
#   2. Force-push history rewrite is NOT enough — assume the secret is harvested
#   3. Audit usage; look for unauthorized access
#   4. Add the secret pattern to gitleaks ban list

Why it matters

OIDC-assumed roles (no stored AWS keys) and short-lived tokens are the modern CI secret stack. The one inviolable rule: leaked secrets get rotated at the source first — rewriting git history alone never closes the gap.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
# Store in repo / org settings.
# Reference: ${{ secrets.NPM_TOKEN }}
# NEVER echo a secret. Mark with mask-on-output for safety.
Try it Yourself »

Exercise

Reference a secret named NPM_TOKEN.

token: ${{ .NPM_TOKEN }}

Test yourself

Q1. Secrets should be…
Q2. Echoing a secret in logs…
Q3. Per-environment secrets in GitHub Actions live under…

Discussion

Loading…