← All Visualizations

Pipeline Flow

Shift-left security for containerised apps on AWS EKS. Click a job to inspect it.

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

πŸ‘† Click any job card to see its steps, tools, and details here.

Jobs Reference

All jobs defined in secopspipeline.yaml with triggers and timeouts.

# 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

Every control, the tool that enforces it, and whether it blocks the pipeline.

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

Architectural choices that make this pipeline supply-chain safe and operationally robust.

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 β€” never a mutable tag. This guarantees staging and production receive byte-for-byte identical images.

2. OIDC β€” No Long-Lived Credentials

AWS credentials are federated via GitHub Actions OIDC (id-token: write). No AWS_ACCESS_KEY_ID or AWS_SECRET_ACCESS_KEY secrets are stored anywhere. Each job assumes a least-privilege IAM role scoped to its action (ECR push, EKS staging, EKS prod).

3. Cosign Verification at Deploy Time

Even if ECR were compromised post-sign, cosign verify at deploy time rejects unsigned or tampered images. Verification uses Sigstore's public Fulcio/Rekor transparency log.

4. Canary-First Progressive Delivery

Both staging and production use Argo Rollouts with a canary strategy. Traffic shifts incrementally. If the rollout health check fails, the rollback-prod job issues argo rollouts undo automatically.

5. Concurrency Protection

cancel-in-progress: true ensures only one run per ref is active at a time. A fast follow-up push cancels the previous run rather than queuing a stale deployment.

6. Language-Agnostic by Design

CodeQL auto-detects the language. Semgrep rules apply universally. The only app-specific step is unit/integration testing β€” add it in code-quality for Python (pytest), Node (npm test), Go (go test), or Java (mvn test).

Required Configuration

Secrets, IAM roles, and GitHub environments needed before running this pipeline.

πŸ”‘ 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:
πŸ’‘ Tip: Store the DAST 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

Raw workflow files β€” copy and place in .github/workflows/.

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