Chapter 9.2: Application & Pipeline Security
Part 1: The "Why" - What is DevSecOps?
In Chapter 9.1, we learned how to manage secrets with Vault. That was the first step in **DevSecOps** (Development + Security + Operations). But "security" is much more than just hiding passwords. It's a mindset.
The old way: Developers write code for 6 months. Then, they "throw it over the wall" to the Security team, who spend 1 month finding 200 bugs. The developers get angry, the project is delayed, and everyone is unhappy.
The DevSecOps way: **"Shift Left."** This means you move security *left* in the timeline, from the end of the process to the very beginning. You build security *into* your CI/CD pipeline. You automate it.
The Goal: Fast, Automated Feedback
The goal is to find vulnerabilities *before* they are merged. Your CI pipeline (Chapter 4) is the perfect place for this. A good DevSecOps pipeline looks like this:
- Developer -> `git push`
- **CI Pipeline Triggers:**
- **Step 1: Secret Scanning:** Does this code contain an API key? (Fail if yes)
- **Step 2: SAST Scan:** Does this code have any obvious bugs (like SQL Injection)? (Fail if yes)
- **Step 3: SCA Scan:** Does this code use any open-source libraries with known vulnerabilities? (Fail if yes)
- **Step 4: Build:** Does the code compile?
- **Step 5: Test:** Do all the unit tests pass?
- **Step 6: Build Docker Image:** Package the app.
- **Step 7: Container Scan:** Does the new Docker image have any OS vulnerabilities? (Fail if yes)
- **Step 8: Deploy to Staging:** Push the *clean* app to a test server.
- **Step 9: DAST Scan:** Attack the live staging server. (Fail if it breaks)
- **Step 10:** Merge to `main` and deploy to production.
This chapter will teach you how to build this pipeline, step-by-step.
Part 2: SAST (Static Application Security Testing)
What is SAST?** It's "white-box" testing. SAST tools scan your **source code** *before* it's ever run. They are like a super-powered linter that looks for common bug patterns and security flaws.
What it finds:
- **SQL Injection:**
db.query("SELECT * FROM users WHERE id = " + user_id) - **Hardcoded Secrets:**
val apiKey = "sk_live_123..." - **Buffer Overflows:** (In C/C++)
strcpy(buffer, user_input) - **Logical Flaws:** Code that will never be reached (dead code).
Tool 1: GitHub Code Scanning (CodeQL)
If your project is on GitHub, this is the easiest way to start. CodeQL is GitHub's powerful SAST engine.
- Go to your GitHub repository.
- Click the **"Security"** tab.
- Find **"Code scanning"** and click "Set up."
- GitHub will suggest a workflow file. Just commit it.
This will create a codeql-analysis.yml file in your .github/workflows folder. Now, every time you push, CodeQL will run, and it will report vulnerabilities *directly on your pull request*.
Tool 2: SonarQube (The Industry Standard)
SonarQube is the most popular, comprehensive platform for "static code analysis." It does SAST, but also measures "Code Quality" and "Code Smells" (e.g., duplicated code, overly complex functions).
How it Works:
- You run a SonarQube server (e.g., in Docker).
- You add a "SonarScanner" step to your CI pipeline.
- The scanner analyzes your code and sends the report to your SonarQube server.
- The server shows a dashboard with a grade (A-F) and a "Quality Gate."
- You configure your pipeline to *fail* if the Quality Gate (e.t., "no new bugs allowed") is not met.
Example: SonarQube in GitHub Actions
name: Build, Test, and Scan
on: push
jobs:
sonar-scan:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0 # Needed for SonarQube to analyze branches
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
- name: Analyze with SonarQube
uses: sonarsource/sonarqube-scan-action@master
env:
# These secrets are set in GitHub repo settings
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}
Read More about SonarQube/SonarCloud →
Part 3: SCA (Software Composition Analysis)
This is arguably the **most important** security scan you can run. **Your code is not just *your* code.** If you're using Node.js, you have 1,000+ dependencies in node_modules. You didn't write them. Are they secure?
SCA tools scan your dependency files (package.json, requirements.txt, pom.xml) and check all your libraries (and *their* dependencies) against a global database of **CVEs (Common Vulnerabilities and Exposures)**.
The `Log4Shell` Example (CVE-2021-44228)
In 2021, a critical vulnerability (Log4Shell) was found in log4j, a very popular Java logging library. Hackers could gain *full control* of a server just by sending a special text message. SCA tools instantly saved millions of companies by:
1. Scanning their code.
2. Reporting: "VULNERABILITY FOUND: You are using log4j 2.14. This is vulnerable. You **must** upgrade to 2.17."
Tool 1: GitHub Dependabot (The "Easy Button")
This is built into GitHub and is the easiest way to do SCA.
Go to **Repo > Settings > Code security and analysis** and enable **Dependabot alerts** and **Dependabot security updates**.
That's it! Now, when a vulnerability is found in one of your dependencies, Dependabot will **automatically create a Pull Request** for you, upgrading the library to the safe version. All you have to do is "Merge".
Tool 2: Snyk (The CI/CD Tool)
Dependabot is great, but it's not a CI check. Snyk is a popular tool that you can run *in* your pipeline to *fail the build* if a new, high-severity vulnerability is found.
.github/workflows/main.yml (add this job) security-scan:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
- name: Install Snyk
run: npm install -g snyk
- name: Run Snyk to check for vulnerabilities
run: snyk test --fail-on=high
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
Part 4: Container & Image Scanning
Okay, we've scanned our source code (SAST) and our dependencies (SCA). We're safe, right? **No.**
Your Dockerfile starts with FROM node:18-alpine. This alpine OS image itself contains *other* software (like openssl, curl, libc). What if that openssl version is 5 years old and has a critical bug?
A **Container Scanner** analyzes every layer of your *built Docker image* and checks all the OS packages and app dependencies inside it for known CVEs.
Tool: Trivy (The Best Open-Source Tool)
Trivy, by Aqua Security, is the most popular, fastest, and easiest-to-use open-source scanner. You run it in your CI pipeline *after* you build your image but *before* you push it to a registry.
Example: Trivy in GitHub Actions
name: Build, Scan, and Push Docker Image
on: push
jobs:
build-and-scan:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Build the Docker image
run: docker build -t my-app:latest .
- name: Install Trivy
uses: aquasecurity/trivy-action@v0.12.0
with:
scan-type: 'image'
image-ref: 'my-app:latest'
format: 'table'
# Fail the build if any CRITICAL vulnerabilities are found
exit-code: '1'
severity: 'CRITICAL'
- name: Log in to GHCR
# ... (This step only runs if Trivy passes) ...
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Push the clean image
run: docker push ghcr.io/${{ github.repository_owner }}/my-app:latest
Part 5: DAST (Dynamic Application Security Testing)
**What is DAST?** This is "black-box" testing. DAST tools don't know your source code. They act like a *real hacker* and attack your *live, running application* (on a staging server) from the outside.
They will "crawl" your website and then try to inject malicious payloads into every input field, form, and URL parameter to find vulnerabilities.
What it finds:
- Cross-Site Scripting (XSS):** Can it inject a
<script>alert(1)</script>tag? - **SQL Injection:** Can it send
' OR 1=1; --in the login form? - **Insecure Headers:** Is your server missing key security headers like
Content-Security-Policy?
Tool: OWASP ZAP (Zed Attack Proxy)
ZAP is the most popular, open-source DAST tool. You can run it in your CI pipeline as a Docker container.
Example: ZAP Baseline Scan in GitHub Actions
This job runs *after* your "Deploy to Staging" job is complete.
dast-scan:
runs-on: ubuntu-latest
needs: deploy-to-staging # Wait for deployment to finish
steps:
- name: Run ZAP Baseline Scan
uses: actions/zap-scan@v0.0.19
with:
# The URL of your live staging app
target: 'https://staging.codewithmsmaxpro.me'
docker_name: 'owasp/zap2docker-stable'
# This will fail the build if ZAP finds any high-risk issues
fail_action: true
Part 6: Secret Scanning (Prevention)
This is your last line of defense. What happens if a developer *does* commit an API key ("sk_live_...") or an SSH private key (-----BEGIN RSA PRIVATE KEY-----)?
Secret Scanning tools scan your code for high-entropy (random-looking) strings and known patterns of API keys.
Tool 1: GitHub Secret Scanning (Built-in)
For public repos, this is enabled automatically. If you push an AWS or GitHub token, GitHub will email you within *seconds* to let you know, and will often auto-revoke the token for you.
Tool 2: `gitleaks` (The CI/CD Tool)
gitleaks is an open-source tool you can run in your pipeline to *prevent* the push from ever succeeding.
Example: `gitleaks` in GitHub Actions
secret-scan:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0 # Fetch all history for scanning
- name: Run Gitleaks
uses: gitleaks/gitleaks-action@v2
env:
# This token is needed to report back to the PR
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
If `gitleaks` finds a hardcoded password or key, it will fail the build and prevent the merge, forcing the developer to fix their mistake *before* it becomes a security disaster.
Read the Official OWASP Top 10 List →