Securing CI/CD Pipelines: From Secrets to Software Supply Chain
A practical guide to securing your CI/CD pipelines. Covers secret management with Vault/GitHub Secrets, dependency scanning with Trivy, static analysis (SAST), and software supply chain security with SLSA.
What You'll Learn
📋 Prerequisites
- An existing CI/CD pipeline (like the one from our previous guide).
- Familiarity with Docker concepts.
- Basic understanding of software vulnerabilities (CVEs).
🏷️ Topics Covered
💡 Shifting Security Left, The Right Way
"Shifting left" means integrating security into the earliest stages of development, not treating it as an afterthought. A secure CI/CD pipeline is the engine of DevSecOps, automatically scanning every change to catch vulnerabilities before they ever reach production. This guide shows you how to build that engine.
The Four Pillars of CI/CD Security
A truly secure pipeline doesn't rely on a single tool. It integrates multiple layers of defense. We'll focus on four critical pillars that provide comprehensive protection.
Secret Management
Protecting sensitive credentials like API keys and passwords from being exposed in your code or logs.
Dependency Scanning (SCA)
Automatically finding known vulnerabilities (CVEs) in the open-source libraries and dependencies your application uses.
Code Scanning (SAST)
Analyzing your own source code to find common security flaws like SQL injection or hardcoded secrets before you even commit.
Supply Chain Security
Ensuring the integrity of your build process and artifacts, preventing attackers from injecting malicious code into your final product.
Pillar 1: Secure Secret Management
Hardcoding secrets is a major security risk. Your pipeline needs a secure way to access credentials at runtime.
Option A: GitHub Encrypted Secrets
For simple use cases, GitHub's built-in secrets are a good starting point. They are encrypted and can be scoped to specific environments (e.g., `production`).
Option B: External Vault (Recommended for Scale)
For enterprise-grade security, using a dedicated secrets manager like HashiCorp Vault is the best practice. It provides centralized management, dynamic secrets, and detailed audit logs.
🔄 YAML: Accessing HashiCorp Vault in GitHub Actions
- name: Import Secrets from Vault
uses: hashicorp/vault-action@v2
with:
url: ${{ secrets.VAULT_ADDR }}
token: ${{ secrets.VAULT_TOKEN }}
secrets: |
secret/data/aws AWS_ACCESS_KEY_ID | AWS_KEY ;
secret/data/aws AWS_SECRET_ACCESS_KEY | AWS_SECRET ; Pillar 2: Dependency Scanning with Trivy (SCA)
Software Composition Analysis (SCA) tools scan your project's dependencies against a database of known vulnerabilities. We can easily add this to our workflow using the popular open-source tool, Trivy.
📦 YAML: Vulnerability Scanning Job in GitHub Actions
jobs:
vulnerability-scan:
name: 'Trivy Vulnerability Scan'
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
scan-type: 'fs'
scan-ref: '.'
ignore-unfixed: true
format: 'sarif'
output: 'trivy-results.sarif'
# Fail the build if any CRITICAL or HIGH vulnerabilities are found
severity: 'CRITICAL,HIGH'
- name: Upload Trivy scan results to GitHub Security tab
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: 'trivy-results.sarif' This job scans your entire filesystem for vulnerable packages and will **fail the build** if any high or critical severity issues are found, preventing them from being deployed.
Pillar 3: Static Code Analysis (SAST)
SAST tools analyze your source code for security flaws without actually running it. GitHub's native CodeQL is a powerful and well-integrated option.
You can enable CodeQL by navigating to your repository's **Settings → Code security and analysis** and configuring the "Code scanning" setup.
📜 YAML: Adding a CodeQL Job
jobs:
sast-scan:
name: 'CodeQL SAST Scan'
runs-on: ubuntu-latest
permissions:
security-events: write # Required to upload results
steps:
- name: Checkout repository
uses: actions/checkout@v4
# Initializes the CodeQL tools for scanning
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
- name: Autobuild
uses: github/codeql-action/autobuild@v3
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3 Pillar 4: Software Supply Chain Security
How do you prove that the artifact you deployed is the exact one you built, with no tampering? This is where software supply chain security comes in, ensuring the integrity of your build process.
Key Concepts:
- SLSA (Supply-chain Levels for Software Artifacts): A framework that defines levels of security guarantees for your build process. Higher SLSA levels mean a more tamper-proof build.
- Artifact Signing: Using tools like Sigstore's `cosign`, you can cryptographically sign your Docker images and other build artifacts. This allows you to create policies that only permit signed, trusted artifacts to be deployed.
Example: A Hardened CI/CD Security Workflow
Let's combine these pillars into a single, robust security workflow that runs on every pull request.
🛡️ YAML: `.github/workflows/security-scan.yml`
name: 'Comprehensive Security Scan'
on:
pull_request:
branches: [ main ]
push:
branches: [ main ]
jobs:
security-scans:
name: 'Run Security Scans'
runs-on: ubuntu-latest
permissions:
security-events: write
id-token: write
contents: read
steps:
- name: Checkout code
uses: actions/checkout@v4
# 1. SAST Scan with CodeQL
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: go, javascript, python # Add your project's languages
- name: Autobuild
uses: github/codeql-action/autobuild@v3
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
# 2. SCA Scan with Trivy
- name: Run Trivy for Vulnerable Dependencies
uses: aquasecurity/trivy-action@master
with:
scan-type: 'fs'
scan-ref: '.'
ignore-unfixed: true
format: 'sarif'
output: 'trivy-results.sarif'
severity: 'CRITICAL,HIGH'
- name: Upload Trivy scan results
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: 'trivy-results.sarif'