Skip to content

muhammadhammad2005/k8s-cicd-security-scanning

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

16 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Kubernetes CI/CD Security Scanning & DevSecOps Pipeline

A production-ready DevSecOps project demonstrating how to integrate static security analysis, container image scanning, and compliance enforcement directly into a Kubernetes CI/CD pipeline β€” using Kubesec, KubeLinter, and Trivy, automated via GitHub Actions.

Pipeline Kubernetes Security License


πŸ“Œ What This Repo Does

This repository demonstrates shifting security left β€” catching misconfigurations and vulnerabilities at the manifest and image level before anything reaches production Kubernetes. It answers the question:

"How do I make security automatic and enforced, not manual and optional?"

Core Activities

Activity Tool Result
Kubernetes manifest scoring Kubesec secure-app scored +8, vulnerable-app scored -37
Manifest policy linting KubeLinter 13 violations caught in vulnerable manifest
Container image CVE scanning Trivy Custom app image: 0 CRITICAL, 0 HIGH
CI/CD security gate GitHub Actions Blocks insecure code from merging automatically
Registry policy enforcement OPA Gatekeeper Restricts images to trusted registries only

πŸ“ Repository Structure

k8s-cicd-security-scanning/
β”œβ”€β”€ app/                               # Hardened Node.js demo application
β”‚   β”œβ”€β”€ Dockerfile                     # Multi-stage build with apk upgrade + non-root user
β”‚   β”œβ”€β”€ .dockerignore
β”‚   β”œβ”€β”€ package.json
β”‚   β”œβ”€β”€ package-lock.json
β”‚   └── src/
β”‚       └── server.js                  # Express server with /health and / endpoints
β”‚
β”œβ”€β”€ manifests/                         # Kubernetes YAML files
β”‚   β”œβ”€β”€ secure-app.yaml                # Hardened deployment (best practices applied)
β”‚   β”œβ”€β”€ network-policy.yaml            # Default-deny NetworkPolicy
β”‚   └── demo/
β”‚       └── vulnerable-app.yaml        # Intentionally insecure deployment (reference only)
β”‚
β”œβ”€β”€ policies/                          # Security policies
β”‚   β”œβ”€β”€ kubelinter-config.yaml         # Custom KubeLinter rules
β”‚   β”œβ”€β”€ allowed-registries.yaml        # Trusted image registries ConfigMap
β”‚   β”œβ”€β”€ image-policy-template.yaml     # OPA Gatekeeper ConstraintTemplate
β”‚   └── image-policy-constraint.yaml   # OPA Gatekeeper Constraint
β”‚
β”œβ”€β”€ scripts/                           # Automation scripts
β”‚   β”œβ”€β”€ ci-cd-pipeline.sh              # Full local pipeline simulation
β”‚   β”œβ”€β”€ kubesec-scan.sh                # Kubesec batch scanner
β”‚   β”œβ”€β”€ kubelinter-scan.sh             # KubeLinter batch scanner
β”‚   └── image-scan.sh                  # Trivy image scanner
β”‚
β”œβ”€β”€ reports/                           # Scan output (generated at runtime, gitignored)
β”œβ”€β”€ .github/
β”‚   └── workflows/
β”‚       └── security-scan.yml          # GitHub Actions CI/CD pipeline
β”œβ”€β”€ .gitignore
└── README.md

πŸ” Security Concepts Demonstrated

1. Vulnerable vs Secure Deployments

Two deployments are included side by side for comparison:

manifests/demo/vulnerable-app.yaml β€” what NOT to do:

  • Runs as root (runAsUser: 0)
  • Privileged container (privileged: true)
  • Allows privilege escalation
  • Hardcoded secrets in environment variables
  • No resource limits defined
  • Outdated image (nginx:1.14 β€” hundreds of CVEs)
  • Kubesec score: -37 ❌

manifests/secure-app.yaml β€” what to do:

  • Non-root user (runAsUser: 1000)
  • Read-only root filesystem
  • All Linux capabilities dropped (drop: ALL)
  • Secrets from Kubernetes Secrets object
  • CPU and memory requests/limits defined
  • Liveness and readiness probes configured
  • Minimal up-to-date image
  • Kubesec score: +8 βœ…

2. Kubesec Scoring

Kubesec assigns a numeric security score to Kubernetes manifests. A negative score means critical security issues are present. The pipeline fails on any negative score.

vulnerable-app β†’ score: -37  ❌  (pipeline blocked)
secure-app     β†’ score:  +8  βœ…  (pipeline passed)

3. KubeLinter Policy Checks

KubeLinter checks for violations including missing probes, missing resource limits, running as root, writable root filesystems, host network access, and more β€” configured via policies/kubelinter-config.yaml.

4. Trivy Image CVE Scanning

Trivy scans every layer of the container image against the CVE database. The custom app image achieves zero HIGH or CRITICAL vulnerabilities by:

  • Using node:20-alpine as the base image
  • Running apk upgrade --no-cache in both build and production stages
  • Using a multi-stage build to exclude build tools from the final image
  • Excluding npm internal modules from the scanning scope
secure-demo-app:1.0.0
  alpine packages  β†’  0 vulnerabilities βœ…
  app dependencies β†’  0 vulnerabilities βœ…

5. The Security Gate

The CI/CD pipeline enforces hard rules on every push and pull request:

Condition Action
Kubesec score < 0 ❌ Pipeline fails, merge blocked
CRITICAL CVEs > 0 ❌ Pipeline fails, merge blocked
HIGH CVEs > 10 ❌ Pipeline fails, merge blocked
KubeLinter violations ⚠️ Warning flagged, pipeline continues

πŸ› οΈ Prerequisites

Tool Version Install
Docker 20.x+ docs.docker.com
Minikube 1.30+ minikube.sigs.k8s.io
kubectl 1.26+ kubernetes.io/docs
Kubesec v2.13.0 See setup below
KubeLinter v0.6.8 See setup below
Trivy latest See setup below
jq any sudo apt-get install jq

πŸš€ Quick Start

Step 1 β€” Clone the repo

git clone https://github.com/muhammadhammad2005/k8s-cicd-security-scanning.git
cd k8s-cicd-security-scanning

Step 2 β€” Start Minikube

minikube start
kubectl get nodes

Step 3 β€” Install scanning tools

# Kubesec (pinned version for stability)
curl -sSL https://github.com/controlplaneio/kubesec/releases/download/v2.13.0/kubesec_linux_amd64.tar.gz \
  | tar xz kubesec
chmod +x kubesec && sudo mv kubesec /usr/local/bin/
kubesec version

# KubeLinter
curl -sSL https://github.com/stackrox/kube-linter/releases/download/v0.6.8/kube-linter-linux.tar.gz \
  | tar xz
sudo mv kube-linter /usr/local/bin/
kube-linter version

# Trivy
sudo apt-get install wget apt-transport-https gnupg lsb-release -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 $(lsb_release -sc) main" \
  | sudo tee /etc/apt/sources.list.d/trivy.list
sudo apt-get update && sudo apt-get install trivy -y
trivy --version

# jq (required for JSON parsing in scripts)
sudo apt-get install jq -y

Step 4 β€” Run the full local security pipeline

chmod +x scripts/*.sh
./scripts/ci-cd-pipeline.sh

Reports are saved to reports/.


πŸ”¬ Running Individual Scans

Kubesec β€” score a manifest

# Scan secure manifest
kubesec scan manifests/secure-app.yaml

# Scan vulnerable manifest (demo reference)
kubesec scan manifests/demo/vulnerable-app.yaml

# Batch scan all production manifests
./scripts/kubesec-scan.sh

KubeLinter β€” lint all manifests

# Quick lint
kube-linter lint manifests/

# Full scan with reports saved in all formats
./scripts/kubelinter-scan.sh

Trivy β€” scan container images

# Scan the custom app image (full)
trivy image secure-demo-app:1.0.0

# Show only HIGH and CRITICAL, exclude npm internals
trivy image \
  --skip-dirs /usr/local/lib/node_modules/npm \
  --skip-dirs /opt/yarn-v1.22.22 \
  --severity HIGH,CRITICAL \
  secure-demo-app:1.0.0

🐳 Building and Deploying the Custom App

Build the image with Minikube

# Point Docker to Minikube's daemon (no push to registry needed)
eval $(minikube docker-env)

# Build the hardened image
docker build -t secure-demo-app:1.0.0 ./app

# Verify image exists
docker images | grep secure-demo-app

Scan before deploying

trivy image \
  --skip-dirs /usr/local/lib/node_modules/npm \
  --skip-dirs /opt/yarn-v1.22.22 \
  --severity HIGH,CRITICAL \
  secure-demo-app:1.0.0
# Expected result: 0 CRITICAL, 0 HIGH

Deploy to Minikube

kubectl apply -f manifests/secure-app.yaml
kubectl apply -f manifests/network-policy.yaml

# Watch pods come up (takes ~35s for probes to pass)
kubectl get pods -w

Test the running app

# Terminal 1 - forward the port
kubectl port-forward svc/secure-app-service 8080:80

# Terminal 2 - test endpoints
curl http://localhost:8080
# {"app":"secure-demo-app","version":"1.0.0","message":"Running securely in Kubernetes"}

curl http://localhost:8080/health
# {"status":"ok","uptime":12.345}

Cleanup

kubectl delete -f manifests/secure-app.yaml
kubectl delete -f manifests/network-policy.yaml
eval $(minikube docker-env -u)

πŸ“Š Understanding the Reports

After running scans, check the reports/ directory:

Report file Tool What it shows
kubesec-secure-app.json Kubesec Score +8, passed checks breakdown
kubesec-vulnerable-app.json Kubesec Score -37, critical failures
kubelinter-full.json KubeLinter All violations with object names
kubelinter-summary.txt KubeLinter Human-readable summary
trivy-secure-demo-app.json Trivy CVE scan of custom app image

Compare Kubesec scores

echo "Secure app score:"
cat reports/kubesec-secure-app.json | \
  jq '[.[] | select(.object | startswith("Deployment"))] | .[0].score'

echo "Vulnerable app score:"
cat reports/kubesec-vulnerable-app.json | \
  jq '[.[] | select(.object | startswith("Deployment"))] | .[0].score'

Count CVEs in custom image

echo "CRITICAL CVEs:"
cat reports/trivy-secure-demo-app.json | \
  jq '[.Results[]?.Vulnerabilities // [] | .[] | select(.Severity=="CRITICAL")] | length'

echo "HIGH CVEs:"
cat reports/trivy-secure-demo-app.json | \
  jq '[.Results[]?.Vulnerabilities // [] | .[] | select(.Severity=="HIGH")] | length'

βš™οΈ GitHub Actions CI/CD Pipeline

The pipeline at .github/workflows/security-scan.yml triggers automatically on every push to main or develop, and on every pull request to main.

Pipeline flow

Push / PR to main
        β”‚
        β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Install Tools          β”‚  Kubesec v2.13.0, KubeLinter v0.6.8, Trivy latest
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
             β”‚
        β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Kubesec Analysis       β”‚  Score manifests/secure-app.yaml
β”‚                         β”‚  score < 0 β†’ FAIL ❌
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
             β”‚
        β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  KubeLinter Analysis    β”‚  Lint all manifests/
β”‚                         β”‚  violations β†’ WARNING ⚠️
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
             β”‚
        β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Build App Image        β”‚  docker build secure-demo-app:1.0.0
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
             β”‚
        β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Trivy Image Scan       β”‚  Scan secure-demo-app:1.0.0
β”‚                         β”‚  CRITICAL > 0 or HIGH > 10 β†’ FAIL ❌
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
             β”‚
        β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Upload Reports         β”‚  Saved as artifact (30 days retention)
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
             β”‚
        β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Security Gate Summary  β”‚  Final results printed
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
             β”‚
        β–Ό
      PASS βœ…

Actual pipeline results (verified)

Kubesec | kubesec-secure-app     | score: 8          βœ…
Trivy   | trivy-secure-demo-app  | CRITICAL: 0 HIGH: 0  βœ…

To use in your own project

  1. Fork or clone this repo
  2. Push to your GitHub account
  3. Go to Actions tab β€” pipeline runs automatically on push
  4. Download security reports from the Artifacts section after each run
  5. No secrets or tokens required β€” tools are installed fresh each run

πŸ›‘οΈ OPA Gatekeeper Policy (Advanced)

The policies/ directory includes OPA Gatekeeper templates to enforce image registry restrictions at the Kubernetes admission controller level β€” Kubernetes itself rejects deployments from untrusted registries before they even run.

Install Gatekeeper on Minikube

kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper/v3.13.0/deploy/gatekeeper.yaml

# Wait for it to be ready
kubectl -n gatekeeper-system wait --for=condition=ready pod --all --timeout=120s

Apply the policies

kubectl apply -f policies/image-policy-template.yaml
kubectl apply -f policies/image-policy-constraint.yaml

Test the policy

# Should be REJECTED (untrusted registry)
kubectl run test --image=untrusted-registry.com/myapp:latest

# Should be ALLOWED (trusted registry)
kubectl run test --image=docker.io/nginx:1.21-alpine

πŸ”‘ Security Best Practices Applied

Container / Image

  • βœ… Non-root user (runAsUser: 1000)
  • βœ… Read-only root filesystem
  • βœ… All Linux capabilities dropped (drop: ALL)
  • βœ… No privilege escalation allowed
  • βœ… Multi-stage Docker build
  • βœ… Minimal Alpine base image (node:20-alpine)
  • βœ… apk upgrade --no-cache patches OS CVEs at build time
  • βœ… Zero HIGH/CRITICAL CVEs in final image

Kubernetes

  • βœ… Resource requests and limits on all containers
  • βœ… Liveness and readiness probes configured
  • βœ… Secrets from Kubernetes Secrets, not hardcoded env vars
  • βœ… Default-deny NetworkPolicy
  • βœ… ClusterIP service only (not exposed externally)
  • βœ… Trusted image registry enforcement via OPA Gatekeeper

CI/CD

  • βœ… Automated manifest scoring on every push
  • βœ… Automated image CVE scanning on every push
  • βœ… Security gate blocking bad code from merging
  • βœ… Reports uploaded as downloadable artifacts (30 days)

πŸ› Troubleshooting

Issue Cause Fix
Pod ImagePullBackOff Image not in Minikube Run eval $(minikube docker-env) before building
Pod CrashLoopBackOff App error Run kubectl logs -l app=secure-app
Probes failing on port 8080 Port mismatch App listens on 3000 β€” ensure manifest uses port 3000
npm ci fails in Docker Missing package-lock.json Run npm install in app/ directory first
Kubesec exit code 2 Unsupported resource type (Secret/Service) Use || true and filter score by Deployment in jq
Trivy finds npm CVEs npm internal packages bundled in Node image Add --skip-dirs /usr/local/lib/node_modules/npm
GitHub Actions sed fails | delimiter conflict Use # as sed delimiter instead of / or |
actions/upload-artifact fails Deprecated v3 Upgrade to actions/upload-artifact@v4

πŸ“š Tools Reference

Tool Docs Purpose
Kubesec https://kubesec.io Kubernetes manifest security scoring
KubeLinter https://docs.kubelinter.io Manifest policy linting
Trivy https://aquasecurity.github.io/trivy Container image CVE scanning
OPA Gatekeeper https://open-policy-agent.github.io/gatekeeper Admission controller policies
Minikube https://minikube.sigs.k8s.io Local Kubernetes cluster

Skills demonstrated:

  • Kubernetes cluster hardening & manifest security
  • DevSecOps CI/CD pipelines
  • Container image vulnerability scanning
  • OPA Gatekeeper policy enforcement
  • Automation using GitHub Actions & Bash

πŸ“„ License

MIT β€” free to use, modify, and distribute.

About

Production-ready Kubernetes DevSecOps pipeline: secure app manifests & container images scanned with Kubesec, KubeLinter, and Trivy, enforced via GitHub Actions CI/CD

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors