100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
DevSecOps & Site Reliability Engineering
50 minadvanced

Security Practice — Harden a Pipeline

What You'll Build

In this exercise you will integrate all Module 2 security tools into a single hardened GitHub Actions pipeline for the CricketPulse API. The finished pipeline will: scan dependencies with pip-audit, perform SAST with Semgrep and Bandit, scan the container image with Trivy, generate and sign an SBOM, and use OIDC for all cloud authentication — replacing every static credential with ephemeral tokens.

Analogy🏏Cricket
🏏 Think of it like cricket: This is a full-match simulation in the nets — a structured practice match with umpires, scorers, and a target, played under match conditions so the team builds muscle memory for the real thing. The coach throws curveballs to test the team's response to unexpected events. After the simulation, the team watches the footage and identifies improvements for the next real match.

Prerequisites

  • Lessons 5, 6, and 7 completed.
  • A GitHub repository with the CricketPulse FastAPI app from Module 1.
  • AWS account with an IAM OIDC identity provider configured for GitHub Actions.
  • Docker installed and a container registry (GHCR or ECR) available.
  • GitHub Advanced Security enabled (for SARIF upload to Security tab).

Step 1 — Configure OIDC Authentication

Replace all static AWS credentials with an OIDC identity provider. Create an IAM role that GitHub Actions can assume via OIDC, scoped to your specific repository and branch. This eliminates long-lived credentials from your pipeline entirely.

Analogy🏏Cricket
🏏 Think of it like cricket: The ICC grants match officials temporary authority credentials valid only for the specific match they're officiating — not a permanent ICC staff pass. OIDC grants your CI job temporary AWS credentials valid only for that pipeline run.
bash
# 1. Create the OIDC provider in AWS (one-time setup)
aws iam create-open-id-connect-provider   --url https://token.actions.githubusercontent.com   --client-id-list sts.amazonaws.com   --thumbprint-list 6938fd4d98bab03faadb97b34396831e3780aea1

# 2. Create IAM role trust policy — restrict to your repo
cat > trust-policy.json << 'EOF'
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": {
      "Federated": "arn:aws:iam::ACCOUNT_ID: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:myorg/cricketpulse:*"
      }
    }
  }]
}
EOF

aws iam create-role   --role-name github-actions-cricketpulse   --assume-role-policy-document file://trust-policy.json

# 3. Attach least-privilege policy (ECR push + EKS deploy only)
aws iam attach-role-policy   --role-name github-actions-cricketpulse   --policy-arn arn:aws:iam::ACCOUNT_ID:policy/cricketpulse-deploy-policy

Step 2 — Build the Hardened Pipeline

Assemble all the security gates into a single workflow with parallel jobs for SAST, dependency scanning, and image scanning. The deploy job depends on all security gates passing and uses OIDC credentials scoped to the production deployment role.

Analogy🏏Cricket
🏏 Think of it like cricket: The match day checklist runs all inspections in parallel: the pitch inspection team, the equipment certification team, and the DRS calibration team all work simultaneously, not sequentially. Only when all three give the green light does the toss proceed.
yaml
# .github/workflows/secure-pipeline.yaml
name: Secure Build and Deploy

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

permissions:
  contents: read
  id-token: write          # required for OIDC
  security-events: write   # required for SARIF upload
  packages: write          # required for GHCR push

jobs:
  # ── Security Gates (run in parallel) ────────────────────────────────
  dependency-scan:
    name: Dependency Audit
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11  # v4.1.1
      - uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d  # v5.1.0
        with:
          python-version: "3.12"
      - name: pip-audit
        run: |
          pip install pip-audit
          pip-audit --requirement requirements.txt --format json             --output pip-audit.json
          python3 -c "
          import json, sys
          r = json.load(open('pip-audit.json'))
          vulns = r.get('dependencies', [])
          highs = [v for d in vulns for v in d.get('vulns',[]) if v.get('fix_versions')]
          if highs:
              print(f'FAIL: {len(highs)} patchable vulnerabilities found')
              sys.exit(1)
          print('PASS: no patchable vulnerabilities')
          "

  sast:
    name: SAST Scan
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
      - name: Semgrep
        uses: returntocorp/semgrep-action@v1
        with:
          config: >-
            p/owasp-top-ten
            p/python
            p/secrets
      - name: Bandit
        run: |
          pip install bandit
          bandit -r src/ -f sarif -o bandit.sarif -ll || true
      - uses: github/codeql-action/upload-sarif@v3
        with:
          sarif_file: bandit.sarif

  image-scan:
    name: Image Scan
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
      - name: Build image
        run: docker build -t cricketpulse:${{ github.sha }} .
      - name: Hadolint
        uses: hadolint/hadolint-action@v3.1.0
        with:
          dockerfile: Dockerfile
          failure-threshold: error
      - name: Trivy scan
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: cricketpulse:${{ github.sha }}
          format: sarif
          output: trivy.sarif
          exit-code: '1'
          severity: HIGH,CRITICAL
          ignore-unfixed: true
      - uses: github/codeql-action/upload-sarif@v3
        with:
          sarif_file: trivy.sarif

  # ── Deploy (depends on all gates) ───────────────────────────────────
  deploy:
    name: Deploy to Production
    needs: [dependency-scan, sast, image-scan]
    runs-on: ubuntu-latest
    environment:
      name: production
      url: https://cricketpulse.example.com
    steps:
      - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
      - name: Configure AWS via OIDC
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789:role/github-actions-cricketpulse
          aws-region: ap-south-1

      - name: Login to ECR
        id: ecr-login
        uses: aws-actions/amazon-ecr-login@v2

      - name: Push image
        run: |
          docker tag cricketpulse:${{ github.sha }}             ${{ steps.ecr-login.outputs.registry }}/cricketpulse:${{ github.sha }}
          docker push             ${{ steps.ecr-login.outputs.registry }}/cricketpulse:${{ github.sha }}

      - name: Generate and upload SBOM
        uses: anchore/sbom-action@v0
        with:
          image: cricketpulse:${{ github.sha }}
          format: spdx-json
          output-file: sbom.spdx.json
      - uses: actions/upload-artifact@v4
        with:
          name: sbom-${{ github.sha }}
          path: sbom.spdx.json
          retention-days: 365

      - name: Deploy to EKS
        run: |
          aws eks update-kubeconfig --name prod-cluster --region ap-south-1
          kubectl set image deployment/cricketpulse-api             api=${{ steps.ecr-login.outputs.registry }}/cricketpulse:${{ github.sha }}
          kubectl rollout status deployment/cricketpulse-api --timeout=120s

Step 3 — Add the Required Reviewer Gate

Configure a GitHub environment with required reviewers so that the deploy job cannot proceed without a named approver even if all automated gates pass. This implements separation of duties — the engineer who writes the code cannot also approve their own deployment to production.

Analogy🏏Cricket
🏏 Think of it like cricket: The DRS review requires both the fielding captain's call AND the third umpire's independent verification — one person cannot complete both roles. The required reviewer gate ensures the developer cannot be both the coder and the production approver.
yaml
# GitHub UI: Settings > Environments > production
# Enable: Required reviewers
# Add: your team lead or ops team as required reviewers

# With required reviewer, the pipeline pauses after all automated gates
# and sends a notification to the reviewer:
# "Deploy job waiting for approval — all security gates passed"
# Reviewer clicks Approve or Reject with a comment

# Verify in workflow: the environment block handles this automatically
deploy:
  environment:
    name: production    # triggers required reviewer check
    url: https://cricketpulse.example.com

# To test: push a commit and observe the pipeline pause at the deploy job
# In GitHub Actions UI: a yellow "Waiting for approval" status appears
# Reviewer approves: job proceeds
# Reviewer rejects: job fails with reviewer's comment

Verify Your Work

Run these verification checks to confirm all five security layers are active and functional before marking the exercise complete.

Analogy🏏Cricket
🏏 Think of it like cricket: A practice session is worthless until you grade it against clear benchmarks. Just as a batting coach scores a net session on measurable targets — did the batter rotate strike, was the trigger movement quick enough, did they leave the balls outside off — you review the drill against five concrete criteria rather than a vague sense that 'it went okay'. Just as the coach times how fast the batter read the length (time to detect, target under ten minutes) and how quickly they adjusted their shot (time to contain, target under twenty), you measure detection and containment against fixed thresholds. Just as a session only passes if every benchmark is met, not most of them, a passing drill must satisfy all five: fast detection, fast containment, status page updated, a systemic root cause, and at least three owned action items. The payoff: honest scoring against a rubric shows exactly which reflex to sharpen before the real match, rather than leaving you falsely confident.
bash
# Verification checklist

# 1. Confirm no static credentials in workflows
grep -r "AWS_ACCESS_KEY_ID\|AWS_SECRET_ACCESS_KEY" .github/   && echo "FAIL: static credentials found"   || echo "PASS: no static credentials"

# 2. Confirm all Actions are pinned to commit SHAs
grep "uses:" .github/workflows/*.yaml | grep "@" | grep -v "@[a-f0-9]\{40\}"   && echo "WARN: some actions not pinned to SHA"   || echo "PASS: all actions pinned to SHA"

# 3. Trigger pipeline and confirm parallel gate execution
# Push a commit and verify all three gates run simultaneously in GitHub Actions
# Expected: dependency-scan, sast, image-scan all show "In progress" at the same time

# 4. Test that a vulnerable dependency blocks the build
echo "cryptography==41.0.0" >> requirements.txt
git add requirements.txt && git commit -m "test: inject vulnerable dep"
git push
# Expected: dependency-scan job fails; deploy job does not run

# Revert
git revert HEAD && git push

# 5. Verify SBOM is uploaded as artifact
# After a successful deploy: Actions > your run > Artifacts
# Expect: sbom-<sha>.zip with sbom.spdx.json inside

# 6. Verify required reviewer gate
# Push to main and observe deploy job status
# Expected: "Waiting for review" before deploy proceeds

The required reviewer gate only provides value if reviewers actually read the deployment context before approving. Include the commit SHA, PR link, and a summary of what changed in the deployment notification. A reviewer who approves without reading provides no additional security — they just add latency.

GitHub's environment protection rules also support 'Wait timer' (delay before deploy) and 'Deployment branches' (only specific branches can deploy to this environment). Combine required reviewers + deployment branches (main only) + wait timer (15 minutes) for a layered production protection that resists both mistakes and rushed deployments.

  • OIDC authentication eliminates long-lived AWS credentials from the pipeline — credentials are ephemeral per job.
  • Parallel security gates (dependency scan, SAST, image scan) reduce total pipeline time vs sequential execution.
  • All GitHub Actions must be pinned to commit SHAs to prevent supply chain attacks via action repo tampering.
  • GitHub environment required reviewers implements separation of duties for production deployments.
  • SBOM artifacts should be retained for 365 days alongside the image they describe for future CVE auditing.
Lesson 8 of 24
0% complete