← All Visualizations

Pipeline Flow

Follow a container image from source checks through signing, staged deployment, approval, and rollback on AWS EKS. Select a job to inspect its work.

Pull Request
πŸ” dependency-review PR only
Blocks merge on HIGH/CRITICAL deps Β· dependency-review-action@v4
Push main / tag
πŸ”’ code-quality
20 min Β· Gitleaks Β· CodeQL Β· Semgrep Β· Super-Linter
πŸ—οΈ build
20 min Β· Docker image + ECR Β· SBOM + provenance Β· GHA cache
πŸ›‘οΈ scan-sign
15 min Β· Trivy CVE block Β· SARIF Β· Cosign sign
πŸš€ deploy-staging
30 min Β· Helm + Argo Rollouts canary Β· Cosign verify
πŸ•·οΈ dast-staging
20 min Β· OWASP ZAP baseline scan
⏸️ promote Manual Approval
60 min window Β· GitHub Environment gate Β· production env
🎯 deploy-prod
45 min Β· Exact same digest Β· Argo Rollouts canary 15m
↩️ rollback-prod on failure
20 min Β· argo rollouts undo Β· auto-triggered

Select a job to see its steps, tools, and dependencies.

Jobs Reference

Use the trigger, dependency, and timeout columns to see when each job runs and what must finish first.

# Job Trigger Timeout Needs Purpose
0 dependency-review PR only β€” β€” Blocks merge on HIGH/CRITICAL vulnerable dependencies
1 code-quality push 20 min β€” Gitleaks + CodeQL + Semgrep + Super-Linter
2 build push 20 min code-quality Build & push Docker image to ECR with SBOM and provenance
3 scan-sign push 15 min build Trivy CVE scan, SARIF upload, Cosign sign
4 deploy-staging push 30 min scan-sign Helm + Argo Rollouts canary to staging EKS
5 dast-staging push 20 min deploy-staging OWASP ZAP baseline against live staging URL
6 promote push 60 min dast-staging Manual approval gate on production environment
7 deploy-prod push 45 min promote, scan-sign Deploy exact same digest to production EKS
8 rollback-prod on failure 20 min deploy-prod Auto-rollback via argo rollouts undo

Trigger Conditions

Event Branches / Patterns Jobs Fired
push main code-quality β†’ build β†’ scan-sign β†’ deploy-staging β†’ dast-staging β†’ promote β†’ deploy-prod
push v*.*.* tags Same as push to main
pull_request targeting main dependency-review only

Security Controls Map

Match each security check to the pipeline stage that runs it, then see whether a finding blocks delivery or records evidence.

Control Tool Stage Blocks?
Secrets in git history Gitleaks code-quality βœ… Yes
SAST (source-level) CodeQL code-quality βœ… Yes
SAST (OWASP Top-10) Semgrep code-quality βœ… Yes
Code linting (50+ langs) Super-Linter code-quality βœ… Yes
SCA (dependency vulns) dependency-review-action dependency-review βœ… Yes (PRs)
Container CVE scan Trivy scan-sign βœ… Yes (HIGH/CRIT)
CVE visibility / reporting Trivy SARIF β†’ GitHub Security tab scan-sign βž– Report only
Supply chain signing Cosign (Sigstore OIDC) scan-sign βœ… Yes (sign)
Deploy-time verification Cosign verify deploy-staging, deploy-prod βœ… Yes
SBOM & provenance docker/build-push-action build βž– Attestation
DAST (runtime attacks) OWASP ZAP dast-staging βœ… Yes
Manual promotion gate GitHub Environment approval promote βœ… Yes

Key Design Decisions

These choices reduce artifact substitution, credential exposure, and deployment risk. Their protection still depends on the referenced policies, tools, and environment settings.

1. Build Once, Deploy Everywhere (Digest Pinning)

The image is built once in build and tagged by github.sha. All subsequent jobs reference the immutable sha256:… digest instead of a mutable tag. Staging and production therefore request the same image manifest.

2. OIDC β€” No Long-Lived Credentials

GitHub Actions requests an OIDC token and exchanges it for temporary AWS role credentials. This removes stored IAM access keys from the workflow. Each job uses a role scoped to its task, such as pushing to ECR or deploying to one EKS environment.

3. Cosign Verification at Deploy Time

The deployment jobs run cosign verify against the selected digest before using it. Whether this blocks an unwanted image depends on the verification identity and issuer constraints configured in the workflow, so treat those values as part of the policy.

4. Canary-First Progressive Delivery

Both environments use an Argo Rollouts canary so traffic can move in stages. The workflow waits for the rollout result and runs argo rollouts undo after a failed production deployment. The configured analysis and health checks determine what the rollout can detect.

5. Concurrency Protection

The concurrency group identifies runs for the same ref, and cancel-in-progress: true cancels an older matching run when a newer one starts. Review this behavior carefully for deployment jobs that should finish rather than be interrupted.

6. Language-Agnostic by Design

The sample leaves application tests for you to add to code-quality. Configure CodeQL, Semgrep, the linter, and the test command for the languages and build system in the repository; their defaults do not make the workflow language-independent by themselves.

Required Configuration

Configure these repository values, AWS roles, and GitHub environments before the workflow can authenticate or deploy.

πŸ”‘ Secrets

Secret Used In
AWS_ACCOUNT_ID All configure-aws-credentials steps

🎭 IAM Roles (OIDC trust)

Role Job
github-actions-ecr build, scan-sign
github-actions-eks-staging deploy-staging
github-actions-eks-prod deploy-prod, rollback-prod

🌍 GitHub Environments

Environment Protection Rule
staging None (auto-deploy)
production Required reviewers (manual)

🎯 Update Before Deploy

Field Location
DAST target URL dast-staging β†’ target:
EKS cluster name (staging) deploy-staging step
EKS cluster name (prod) deploy-prod step
IMAGE_NAME top-level env:
AWS_REGION top-level env:
DAST target: Store the target as a GitHub Environment variable (vars.STAGING_URL) instead of hardcoding it in the YAML to avoid manual edits on every deploy.
πŸ”§ setup-tools.yml  Place this reusable workflow under .github/workflows/setup-tools.yml. It installs Cosign, kubectl, Helm, and Argo Rollouts CLI and is called via uses: ./.github/workflows/setup-tools.yml in deploy jobs.

YAML Source

Review the permissions, action versions, role ARNs, environments, and application commands before adapting these workflow files.

name: DevSecOps Pipeline v3 (Progressive + Promotion)

on:
  push:
    branches: [ main ]
    tags: [ "v*.*.*" ]
  pull_request:
    branches: [ main ]

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

permissions:
  id-token: write
  contents: read
  security-events: write
  pull-requests: read

env:
  AWS_REGION: us-east-1
  ECR_REGISTRY: ${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.us-east-1.amazonaws.com
  IMAGE_NAME: my-app

jobs:

  # ─── 0. DEPENDENCY REVIEW (PRs only) ───────────────────────────────────────
  dependency-review:
    runs-on: ubuntu-latest
    if: github.event_name == 'pull_request'
    steps:
      - uses: actions/checkout@v4
      - uses: actions/dependency-review-action@v4
        with:
          fail-on-severity: high

  # ─── 1. CODE QUALITY GATE ───────────────────────────────────────────────────
  code-quality:
    runs-on: ubuntu-latest
    if: github.event_name == 'push'
    timeout-minutes: 20
    permissions:
      security-events: write
      actions: read
      contents: read
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - uses: gitleaks/gitleaks-action@v2
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      - uses: github/codeql-action/init@v3
        with:
          languages: auto
      - uses: github/codeql-action/autobuild@v3
      - uses: github/codeql-action/analyze@v3
        with:
          category: codeql-source
      - uses: returntocorp/semgrep-action@v1
        with:
          config: >
            p/owasp-top-ten p/secrets p/ci
      - uses: super-linter/super-linter@v7
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          DEFAULT_BRANCH: main
          VALIDATE_ALL_CODEBASE: false

  # ─── 2. BUILD ONCE ─────────────────────────────────────────────────────────
  build:
    runs-on: ubuntu-latest
    needs: code-quality
    if: github.event_name == 'push'
    timeout-minutes: 20
    outputs:
      digest: ${{ steps.build.outputs.digest }}
    steps:
      - uses: actions/checkout@v4
      - uses: docker/setup-buildx-action@v3
      - uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/github-actions-ecr
          aws-region: ${{ env.AWS_REGION }}
      - uses: aws-actions/amazon-ecr-login@v2
      - name: Build and Push Image
        id: build
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          provenance: true
          sbom: true
          tags: ${{ env.ECR_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

  # ─── 3. SECURITY SCAN + SIGN ────────────────────────────────────────────────
  scan-sign:
    runs-on: ubuntu-latest
    needs: build
    timeout-minutes: 15
    outputs:
      image_uri: ${{ steps.uri.outputs.image_uri }}
    steps:
      - uses: actions/checkout@v4
      - uses: sigstore/cosign-installer@v3
      - uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/github-actions-ecr
          aws-region: ${{ env.AWS_REGION }}
      - uses: aws-actions/amazon-ecr-login@v2
      - uses: aquasecurity/trivy-action@0.24.0
        with:
          image-ref: ${{ env.ECR_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
          severity: CRITICAL,HIGH
          exit-code: "1"
      - uses: aquasecurity/trivy-action@0.24.0
        if: always()
        with:
          image-ref: ${{ env.ECR_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
          format: sarif
          output: trivy-results.sarif
      - uses: github/codeql-action/upload-sarif@v3
        if: always()
        with:
          sarif_file: trivy-results.sarif
          category: trivy-container
      - name: Compute Image URI (digest-based)
        id: uri
        run: |
          echo "image_uri=${{ env.ECR_REGISTRY }}/${{ env.IMAGE_NAME }}@${{ needs.build.outputs.digest }}" >> $GITHUB_OUTPUT
      - name: Sign Image
        run: cosign sign --yes ${{ steps.uri.outputs.image_uri }}

  # ─── 4. DEPLOY TO STAGING (CANARY) ─────────────────────────────────────────
  deploy-staging:
    runs-on: ubuntu-latest
    needs: scan-sign
    timeout-minutes: 30
    environment:
      name: staging
      url: https://staging.my-app.example.com
    steps:
      - uses: actions/checkout@v4
      - uses: sigstore/cosign-installer@v3
      - uses: azure/setup-kubectl@v4
      - uses: azure/setup-helm@v4
      - name: Install Argo Rollouts CLI
        run: |
          curl -LO https://github.com/argoproj/argo-rollouts/releases/latest/download/kubectl-argo-rollouts-linux-amd64
          chmod +x kubectl-argo-rollouts-linux-amd64
          sudo mv kubectl-argo-rollouts-linux-amd64 /usr/local/bin/kubectl-argo-rollouts
      - uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/github-actions-eks-staging
          aws-region: ${{ env.AWS_REGION }}
      - run: aws eks update-kubeconfig --name my-eks-staging
      - name: Verify Image Signature
        run: |
          cosign verify \
            --certificate-oidc-issuer=https://token.actions.githubusercontent.com \
            ${{ needs.scan-sign.outputs.image_uri }}
      - name: Deploy via Helm (Argo Rollout)
        run: |
          helm upgrade --install my-app ./helm/my-app \
            --namespace staging --create-namespace \
            --set image.digest=${{ needs.build.outputs.digest }}
      - name: Monitor Canary Rollout
        run: kubectl argo rollouts status my-app -n staging --timeout 10m

  # ─── 5. DAST β€” STAGING ──────────────────────────────────────────────────────
  dast-staging:
    runs-on: ubuntu-latest
    needs: deploy-staging
    timeout-minutes: 20
    steps:
      - uses: zaproxy/action-baseline@v0.12.0
        with:
          target: https://staging.my-app.example.com   # ← update this
          fail_action: true
          issue_title: "ZAP Baseline Scan β€” Staging"

  # ─── 6. PROMOTION GATE (MANUAL APPROVAL) ────────────────────────────────────
  promote:
    runs-on: ubuntu-latest
    needs: dast-staging
    timeout-minutes: 60
    environment:
      name: production
    steps:
      - run: echo "Approved for production deployment"

  # ─── 7. DEPLOY TO PRODUCTION (CANARY) ───────────────────────────────────────
  deploy-prod:
    runs-on: ubuntu-latest
    needs: [ promote, scan-sign ]
    timeout-minutes: 45
    environment:
      name: production
      url: https://my-app.example.com
    steps:
      - uses: actions/checkout@v4
      - uses: sigstore/cosign-installer@v3
      - uses: azure/setup-kubectl@v4
      - uses: azure/setup-helm@v4
      - name: Install Argo Rollouts CLI
        run: |
          curl -LO https://github.com/argoproj/argo-rollouts/releases/latest/download/kubectl-argo-rollouts-linux-amd64
          chmod +x kubectl-argo-rollouts-linux-amd64
          sudo mv kubectl-argo-rollouts-linux-amd64 /usr/local/bin/kubectl-argo-rollouts
      - uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/github-actions-eks-prod
          aws-region: ${{ env.AWS_REGION }}
      - run: aws eks update-kubeconfig --name my-eks-prod
      - name: Verify Image Signature
        run: |
          cosign verify \
            --certificate-oidc-issuer=https://token.actions.githubusercontent.com \
            ${{ needs.scan-sign.outputs.image_uri }}
      - name: Deploy via Helm (same digest)
        run: |
          helm upgrade --install my-app ./helm/my-app \
            --namespace production --create-namespace \
            --set image.digest=${{ needs.build.outputs.digest }}
      - name: Monitor Canary Rollout
        run: kubectl argo rollouts status my-app -n production --timeout 15m

  # ─── 8. ROLLBACK ON PRODUCTION FAILURE ──────────────────────────────────────
  rollback-prod:
    runs-on: ubuntu-latest
    needs: deploy-prod
    if: failure()
    timeout-minutes: 20
    environment:
      name: production
    steps:
      - uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/github-actions-eks-prod
          aws-region: ${{ env.AWS_REGION }}
      - run: aws eks update-kubeconfig --name my-eks-prod
      - name: Install Argo Rollouts CLI
        run: |
          curl -LO https://github.com/argoproj/argo-rollouts/releases/latest/download/kubectl-argo-rollouts-linux-amd64
          chmod +x kubectl-argo-rollouts-linux-amd64
          sudo mv kubectl-argo-rollouts-linux-amd64 /usr/local/bin/kubectl-argo-rollouts
      - name: Rollback Argo Rollout
        run: |
          kubectl argo rollouts undo my-app -n production
          kubectl argo rollouts status my-app -n production --timeout 10m