Security is not something you add at the end. It runs through every step of building and deploying software. This module teaches you the mindset, the tools, and the pipeline practices that make security part of your daily DevOps workflow - not an afterthought.
It is 3 AM. An alert fires in production. A security researcher has just published a CVE for a dependency your application has been using for eight months. Your entire team scrambles to figure out which services are affected, which images are deployed, and how fast you can patch and redeploy. This scenario plays out every week at companies across India — at Flipkart, Razorpay, Hotstar, at startups and enterprises alike. It happens because security is treated as someone else's job until it is everyone's emergency. **DevSecOps is the practice of making security a continuous, automated part of the development and delivery process — not a one-time review at the end.** ### Your Job vs The Security Team's Job As a DevOps engineer, you are not expected to become a penetration tester or security researcher. Your responsibility is narrower and more actionable: Your core responsibility: Build pipelines that run security checks automatically Manage secrets correctly — no hardcoded credentials anywhere Scan infrastructure before provisioning Scan container images before they reach production Make security failures block deployments, not generate reports nobody reads The security team's job: Define what policies and rules to enforce Review high-risk architectural decisions Handle incident response for serious breaches Run penetration tests and red team exercises You build the rails. They define where the rails go. ### Shift Left — The Core Idea Imagine a bug found at code review versus a bug found in production. The code review fix takes 10 minutes. The production fix takes hours, involves rollbacks, incident management, customer communication, and a postmortem. Security vulnerabilities work the same way — but the cost difference is far larger. Timeline and cost of finding a vulnerability: Developer's machine (pre-commit hook) → cost: seconds, developer fixes it now Pull request (CI pipeline) → cost: minutes, fix before merge Staging environment (DAST) → cost: hours, fix before release Production (security researcher finds it) → cost: days to weeks + breach cost **Shift left means moving security checks earlier in this timeline.** The earlier you catch something, the cheaper and faster it is to fix. ### DevSecOps Maturity — Where Are You Starting? Level 0 — No security automation Security happens manually at the end (or not at all) Vulnerabilities discovered in production Security team and dev team are in constant conflict Level 1 — Basic automation added Secret scanning in CI Dependency alerts via Dependabot Basic SAST on PRs Developers see security feedback for the first time Level 2 — Security in every pipeline stage SAST + SCA + container scanning in CI IaC security scanning before terraform apply DAST against staging before production deploy Security failures block merges to main Level 3 — Security as culture Developers fix their own findings without being asked Threat modeling before features are designed Security metrics tracked in team retros Most teams reading this are at Level 0 or Level 1. Getting to Level 2 is the practical goal for this module. ---
Your Git repository is where every incident in this module begins. A developer commits an AWS secret key. A config file with a database password gets pushed. A dependency with a critical CVE gets merged without anyone noticing. Before touching any pipeline tool, the Git layer needs to be secure. ### What Must Never Be Committed Every developer on the team needs to internalize this list: Never commit: AWS / GCP / Azure access keys or secret keys Database passwords or connection strings API tokens — Stripe, Razorpay, Twilio, OpenAI Private SSH keys (.pem files) .env files with real values kubeconfig files with cluster credentials Use instead: Environment variables injected at runtime AWS Secrets Manager / HashiCorp Vault GitHub Actions secrets / GitLab CI protected variables .gitignore rules blocking sensitive file patterns ### Pre-Commit Hooks — Catch Secrets Before They Leave the Machine A pre-commit hook runs automatically before every `git commit`. If it detects a secret, the commit is blocked before it ever reaches the remote repository. ```bash ## Install pre-commit pip install pre-commit ## Create .pre-commit-config.yaml in your repo root — this is checked into git ``` ```yaml repos: - repo: https://github.com/gitleaks/gitleaks rev: v8.18.0 hooks: - id: gitleaks - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.5.0 hooks: - id: detect-private-key - id: check-yaml - id: check-json ``` ```bash ## Install hooks into the local git repo — runs on every commit from here on pre-commit install ## Test against all existing files immediately pre-commit run --all-files ``` > 📌 **Remember:** Pre-commit hooks can be bypassed with `git commit --no-verify`. They are the first layer — not the only layer. Always add secret scanning in CI as a mandatory second check that cannot be skipped. ### Secret Scanning in CI — The Mandatory Layer ```yaml ## GitHub Actions — runs on every push and pull request - name: Secret Scanning uses: gitleaks/gitleaks-action@v2 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} ``` ```yaml ## GitLab CI secret-scan: stage: quality script: - docker run --rm -v $(pwd):/path zricethezav/gitleaks:latest detect --source /path --verbose ``` ### Branch Protection — The Non-Negotiable Setup Enable these on your main branch immediately. Go to GitHub → Repository Settings → Branches: * Require pull request reviews before merging — minimum one reviewer * Require status checks to pass before merge — CI must be green * Prevent force pushes to main * Restrict who can merge PRs > 🔴 **Common Mistake:** Skipping branch protection during initial project setup and never going back to enable it. Set these on day one. They take three minutes to configure and prevent an entire category of incidents. ### What to Do When a Secret Is Already Committed If a secret was pushed — even to a private repo — assume it is compromised. Bots scan for exposed credentials continuously. Step 1 — Revoke immediately Rotate the API key, access key, or password right now Investigate after — never before revoking Step 2 — Remove from Git history git filter-repo --path secrets.txt --invert-paths Step 3 — Force push the cleaned history git push --force --all Step 4 — Check for abuse Review access logs for the exposed credential > ⚠️ **Security:** Deleting the file and committing again does NOT remove the secret from Git history. Anyone with repo access can still see it with `git log`. You must rewrite history with git filter-repo. ---
Once code is written, how do you automatically find security vulnerabilities before they reach production? **SAST (Static Application Security Testing)** scans your source code without running it, looking for patterns that indicate vulnerabilities. Think of it as a security-focused code reviewer that runs on every commit, never gets tired, and knows every common vulnerability pattern. ### What SAST Finds — and What It Cannot SAST finds well: SQL injection — string concatenation in database queries Command injection — user input passed to shell commands Hardcoded credentials — passwords or keys in source files Insecure cryptography — use of MD5, SHA1, weak key sizes Cross-site scripting — unescaped output in web responses Path traversal — unvalidated file paths from user input SAST cannot find: Runtime issues — problems that only appear when the app is running Authentication logic flaws — SAST cannot reason about business logic Configuration issues — those are caught by IaC scanning (next section) ### Tool Options | Tool | Language Support | Notes | |:-----|:----------------|:------| | **Semgrep** | 30+ languages | Fast, open-source, customizable rules, strong free tier | | **Bandit** | Python only | Standard choice for Python projects | | **CodeQL** | Multi-language | GitHub native, very thorough, free for open source | | **SonarCloud** | Multi-language | Hosted SonarQube, no server to manage | ### Adding Semgrep to Your Pipeline ```yaml ## GitHub Actions — runs OWASP Top 10 ruleset on every PR - name: SAST — Semgrep uses: semgrep/semgrep-action@v1 with: config: p/owasp-top-ten ``` ```yaml ## GitLab CI sast-scan: stage: quality image: returntocorp/semgrep script: - semgrep --config=p/owasp-top-ten --error . ``` ### SonarCloud Integration (Jenkins) SonarCloud is SonarQube as a managed service — no server to run. Create an account at sonarcloud.io, create an organization and project, generate a token, and add it as a Jenkins credential with ID `SONAR_TOKEN`. ```groovy ## Jenkins pipeline stage stage("SAST — SonarCloud") { steps { sh ''' mvn clean verify sonar:sonar \ -Dsonar.projectKey=razorpay-payments-api \ -Dsonar.organization=razorpay-devops \ -Dsonar.host.url=https://sonarcloud.io \ -Dsonar.token=$SONAR_TOKEN ''' } } ``` > 🔴 **Common Mistake:** Running SAST but ignoring the results. Define a policy: Critical and High findings must be fixed before merge. Medium findings must be tracked. SAST with no follow-up action is just noise. ---
Your application code is only part of what gets deployed. Every third-party library in your `package.json`, `requirements.txt`, or `pom.xml` is code you did not write — and each one can have known vulnerabilities. **SCA (Software Composition Analysis)** scans your dependency list against public CVE databases and flags packages with known security issues. ### Why This Matters A real scenario that plays out constantly: Your package.json includes: lodash@4.17.4 SCA finds: CVE-2021-23337 — Prototype Pollution — HIGH severity Fix: upgrade to lodash@4.17.21 Your pom.xml includes: log4j-core:2.14.1 SCA finds: CVE-2021-44228 — Log4Shell — CRITICAL severity Fix: upgrade to 2.17.1 immediately Without SCA, you could be running Log4Shell in production for months without knowing. ### Tool Options | Tool | Ecosystems | Notes | |:-----|:----------|:------| | **Snyk** | npm, pip, Maven, Go, Docker | Most popular, great UI, auto-fix PRs | | **Dependabot** | GitHub repos, most ecosystems | Built into GitHub, auto-creates upgrade PRs | | **npm audit** | Node.js only | Built into npm, zero setup required | | **pip-audit** | Python only | Built for Python projects | ### Snyk in CI/CD ```bash ## Install Snyk CLI npm install -g snyk ## Authenticate with your Snyk account snyk auth ## Test for vulnerabilities — fails if high or critical found snyk test --severity-threshold=high ``` ```yaml ## GitHub Actions - name: SCA — Snyk uses: snyk/actions/node@master env: SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} with: args: --severity-threshold=high ``` ```groovy ## Jenkins pipeline stage stage("SCA — Snyk") { steps { withCredentials([string(credentialsId: 'SNYK_TOKEN', variable: 'SNYK_TOKEN')]) { sh 'mvn snyk:test -fn' } } } ``` ### npm audit — The Minimum Baseline If you are not using any SCA tool yet, add this to your CI pipeline today. It is built into npm and requires zero setup: ```bash ## Run audit — exits non-zero if vulnerabilities found npm audit ## Fail only on high and critical npm audit --audit-level=high ``` > 📌 **Remember:** SAST and SCA solve different problems. SAST finds bugs in your code. SCA finds vulnerabilities in code others wrote that you are using. You need both. A perfectly written codebase can still be critically vulnerable through a single outdated dependency. ---
Infrastructure as Code is how modern cloud infrastructure is provisioned. A single misconfigured Terraform file can expose your entire cloud environment — an S3 bucket set to public, a security group open to the entire internet, an IAM role with unrestricted access. These mistakes are easy to make, hard to spot in a code review, and catastrophic in production. **IaC security scanning catches these misconfigurations before they are ever applied to your cloud account.** ### The Misconfigurations That Cause Real Incidents S3 Buckets: publicly_accessible = true No server-side encryption No versioning — no recovery if data is deleted Security Groups: Port 22 (SSH) open to 0.0.0.0/0 — internet-wide login attempts Port 3306 or 5432 open to 0.0.0.0/0 — database exposed to internet IAM: Role with Action: "*" and Resource: "*" — full AWS access Service accounts using long-term access keys instead of IAM roles RDS Databases: publicly_accessible = true No encryption at rest deletion_protection = false with no backup retention ### Checkov — Scan Terraform, CloudFormation, Kubernetes Manifests **Checkov** is the most widely used IaC security scanner. One tool covers Terraform, CloudFormation, Kubernetes manifests, Dockerfiles, and more. ```bash ## Install pip install checkov --break-system-packages ## Scan a Terraform directory checkov -d ./terraform/ ## Scan and output only failures checkov -d ./terraform/ --compact ``` Example output: Check: CKV_AWS_20: S3 bucket should not have public ACL FAILED for resource: aws_s3_bucket.app-data File: /terraform/s3.tf:5-12 Fix: add this block to your S3 resource: resource "aws_s3_bucket_public_access_block" "app-data" { bucket = aws_s3_bucket.app-data.id block_public_acls = true block_public_policy = true ignore_public_acls = true restrict_public_buckets = true } ### Adding Checkov to CI/CD ```yaml ## GitHub Actions - name: IaC Security Scan — Checkov uses: bridgecrewio/checkov-action@v12 with: directory: terraform/ framework: terraform soft_fail: false ``` ```yaml ## GitLab CI iac-scan: stage: quality image: bridgecrew/checkov:latest script: - checkov -d terraform/ --compact ``` > 📌 **Remember:** Run IaC scanning before `terraform apply` — not after. Once a misconfigured resource is applied, you have a live cloud exposure until it is fixed and reapplied. The scan is useless after the fact. ---
Containers are the default deployment unit for DevOps teams. But containers introduce their own security surface — a vulnerable base image, a process running as root inside the container, secrets stored as plain environment variables. **Container security means securing the image itself and the Kubernetes configuration around it.** ### Secure Dockerfiles — The Basics Image base choices (approximate sizes and attack surface): ubuntu:latest → many packages, large attack surface python:3.11 → includes build tools not needed at runtime python:3.11-slim → fewer packages, smaller surface area gcr.io/distroless → no shell, no package manager, minimal surface Always run containers as a non-root user: ```dockerfile FROM python:3.11-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY src/ ./src/ ## Create and switch to a non-root user before CMD RUN addgroup --system appgroup && \ adduser --system --ingroup appgroup appuser USER appuser CMD ["python", "src/app.py"] ``` Use multi-stage builds to keep build tools out of the production image: ```dockerfile ## Stage 1 — build FROM node:20 AS builder WORKDIR /app COPY package*.json ./ RUN npm ci COPY . . RUN npm run build ## Stage 2 — only compiled output goes to production FROM node:20-alpine AS production WORKDIR /app COPY --from=builder /app/dist ./dist COPY --from=builder /app/package*.json ./ RUN npm ci --only=production RUN addgroup -S appgroup && adduser -S appuser -G appgroup USER appuser CMD ["node", "dist/server.js"] ``` ### Scanning Images with Trivy **Trivy** scans Docker images for known CVEs across all installed packages. Run it before pushing any image to your registry: ```bash ## Install Trivy sudo apt install wget apt-transport-https gnupg -y wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key | sudo apt-key add - echo "deb https://aquasecurity.github.io/trivy-repo/deb generic main" | \ sudo tee /etc/apt/sources.list.d/trivy.list sudo apt update && sudo apt install trivy -y ## Scan a local image trivy image swiggy-backend:latest ## Fail on CRITICAL or HIGH — use in CI trivy image --severity CRITICAL,HIGH --exit-code 1 swiggy-backend:latest ``` Adding Trivy to CI/CD: ```yaml ## GitHub Actions - name: Container Scan — Trivy uses: aquasecurity/trivy-action@master with: image-ref: swiggy-backend:${{ github.sha }} severity: CRITICAL,HIGH exit-code: 1 ``` ```yaml ## GitLab CI container-scan: stage: build script: - trivy image --severity CRITICAL,HIGH --exit-code 1 $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA ``` ### Kubernetes Security — The Essentials Apply least privilege to every pod. Never assign cluster-admin to an application pod: ```yaml ## Good: only the specific permissions the app actually needs apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: app-reader namespace: swiggy-prod rules: - apiGroups: [""] resources: ["configmaps"] verbs: ["get", "list"] ## Read-only access to configmaps only ``` Always set a security context on pods: ```yaml spec: securityContext: runAsNonRoot: true runAsUser: 1000 containers: - name: swiggy-backend image: registry.example.com/swiggy-backend:abc12345 securityContext: allowPrivilegeEscalation: false readOnlyRootFilesystem: true capabilities: drop: ["ALL"] ``` Secrets in Kubernetes — in order of preference: ```yaml ## Worst: hardcoded in the manifest — visible in git and kubectl describe env: - name: DB_PASSWORD value: "mysecretpassword" ## Better: reference a Kubernetes Secret object env: - name: DB_PASSWORD valueFrom: secretKeyRef: name: db-credentials key: password ## Best: External Secrets Operator syncing from AWS Secrets Manager ## The secret never lives in the cluster as plaintext ``` > 🔴 **Common Mistake:** Using `latest` as your image tag in production. Latest means a different image on every pull — no traceability, no reliable rollback. Always tag with the commit SHA. ---
It is 3 AM. An alert fires in production. A security researcher has just published a CVE for a dependency your applicati...
Your Git repository is where every incident in this module begins. A developer commits an AWS secret key. A config file ...
Once code is written, how do you automatically find security vulnerabilities before they reach production? SAST (Static ...
Your application code is only part of what gets deployed. Every third-party library in your package.json, requirements.t...
Infrastructure as Code is how modern cloud infrastructure is provisioned. A single misconfigured Terraform file can expo...
Containers are the default deployment unit for DevOps teams. But containers introduce their own security surface — a vul...
SAST scans code. SCA scans dependencies. DAST does something different — it sends real HTTP requests to your running app...
Every tool in the previous sections needs to be wired together. This is where a DevOps engineer does the most important ...
This project builds a complete DevSecOps pipeline using Jenkins on AWS. You will wire together every tool covered in thi...
Your Core Tool Stack What You Need to Do Tool to Use Detect secrets before commit gitleaks + pre-commit Scan source code...
Aligns directly with DevOps, Site Reliability (SRE), and Platform Engineering job descriptions.