Enterprise-Grade Security Pipeline — 4 Services × 20 Stages × 23 Jobs
Visual representation of the full DevSecOps flow — from feature branch to production.
| Language | Python 3.11 |
| Framework | Flask |
| Port | :5000 |
| Unit Tests | 26 tests (99% coverage) |
| SAST Tools | Semgrep, Bandit |
| SCA Tools | pip-audit |
| DefectDojo | Engagement #1 |
| Image Size | ~150 MB |
| Language | Go 1.24 |
| Framework | net/http (stdlib) |
| Port | :8081 |
| Unit Tests | 17 tests (80% coverage) |
| SAST Tools | Semgrep, gosec, go vet |
| SCA Tools | govulncheck |
| DefectDojo | Engagement #3 |
| Image Size | ~15 MB (multi-stage) |
| Language | Node.js 18 |
| Framework | Express.js |
| Port | :3000 |
| Unit Tests | 22 tests (100% coverage) |
| SAST Tools | Semgrep, njsscan, ESLint |
| SCA Tools | npm audit |
| DefectDojo | Engagement #2 |
| Image Size | ~120 MB |
| Type | Static Dashboard |
| Server | Nginx 1.25 (Alpine) |
| Port | :80 |
| Pipeline | 3 stages (build → deploy → verify) |
| Deploy | GitOps + ArgoCD (STG + PROD) |
| Health | /health endpoint |
| Image Size | ~25 MB |
Each microservice follows the Build Once, Deploy Everywhere model: DEV → STG → PROD. The DEV pipeline builds the image once and runs all security scans. Staging and Production deploy the same image — no rebuild. This ensures reproducibility and supply chain integrity.
Blocks on failure Allowed to fail Manual trigger
First line of defense. If secrets are found, the pipeline stops immediately — no image is ever built from compromised code.
Scans the current source tree with 1000+ regex patterns for API keys, tokens, and passwords.
zricethezav/gitleaks:v8.18.0gitleaks-report.jsonScans git history and uses entropy analysis to detect random strings that are likely tokens — only flags verified secrets (confirmed still active). Uses --only-verified and --exclude-paths=.git to reduce false positives.
trufflesecurity/trufflehog:3.63.0trufflehog-report.jsonExamines code patterns to find vulnerabilities. Each language gets its own specialized scanner in addition to shared Semgrep analysis.
Multi-language SAST engine with --config=auto that applies the right ruleset (p/python, p/golang, p/javascript, p/owasp-top-10).
semgrep/semgrep:latestsemgrep-report.jsonpython:3.11-slimgolang:1.24-alpinepython:3.11-slimbandit-report.jsongosec-report.jsonnjsscan-report.jsonGo: go vet — printf format mismatches, unreachable code, mutex issues.
Node.js: eslint + eslint-plugin-security — object injection, non-literal regexp (ReDoS), eval patterns.
~80% of modern applications consist of third-party code. SCA checks those dependencies against vulnerability databases.
pip-audit (PyPI/OSV DB)govulncheck (vuln.go.dev)npm audit (npm Advisory)junit-report.xmljunit-report.xmljunit.xmldocker:27-clidevsecops-demo-harbor.yhakkache.tech${IMAGE_NAME}:${CI_COMMIT_SHORT_SHA} + :latest/var/run/docker.sock) — no DinD servicepython:3.11-slimgolang:1.24 → alpine:3.18node:18-alpinepip installgo build → binary onlynpm ci --productionaquasec/trivy:latesttrivy-report.jsonalpine:3.18 + curl install grype (anchore image is distroless — no shell)grype-report.jsonProduces a complete list of all packages, libraries, and their versions — essential for supply chain security and compliance. CycloneDX (OWASP) + SPDX (ISO). Retained 30 days.
alpine:3.18 + curl install syft (anchore image is distroless — no shell)sbom-cyclonedx.json, sbom-spdx.jsonSigns the Docker image with a private key via Sigstore Cosign, then verifies the signature. Kubernetes can enforce that only signed images are deployed.
bitnami/cosign:latestcosign sign --key env://COSIGN_KEYcosign verify --key env://COSIGN_PUBFinal DEV stage. Uses git push to auto-promote code from dev to staging branch, which triggers the STG pipeline. Also saves $IMAGE_TAG to .promote-image-tag artifact so staging/prod deploy the exact same image — no rebuild.
git push origin HEAD:staging.promote-image-tag (contains $CI_COMMIT_SHORT_SHA)Uses the GitOps pattern: the pipeline clones the infra-gitops repo using oauth2:${GITLAB_PAT} (cross-project access), reads the image tag from .promote-image-tag, updates the newTag in the Kustomize overlay, commits, and pushes. ArgoCD watches the repo and automatically deploys to K3s.
alpine:3.18sed → update kustomization.yaml → git pushUses wget --no-check-certificate on alpine:3.18 instead of curl (Alpine musl libc has TLS read issues with HAProxy). Runner uses network_mode: host for direct access to the cluster network.
alpine:3.18wget --spider --no-check-certificatehttps://stg-${APP_NAME}.apps.yhakkache.techOWASP ZAP performs a baseline scan against the live staging application, testing for SQL injection, XSS, CSRF, missing security headers, and information disclosure.
ZAP Docker image requires /zap/wrk to exist for file-based output. The job creates this directory with mkdir -p /zap/wrk, then copies reports to the build directory for artifact collection.
ghcr.io/zaproxy/zaproxy:stablemkdir -p /zap/wrk → cp /zap/wrk/zap-*.* .zap-stg-report.json, zap-stg-report.htmlSonarQube v10.7.0 (self-hosted) analyzes code quality, coverage, bugs, and security hotspots. Configuration is read from sonar-project.properties at the repo root. With sonar.qualitygate.wait=true, the pipeline blocks until the result is returned.
sonarsource/sonar-scanner-cli:latesthttps://devsecops-demo-sonar.yhakkache.techsonar-project.properties (projectKey, sources, exclusions, test paths, JUnit report)| Metric | Threshold |
|---|---|
| Coverage | ≥ 70% |
| Critical Bugs | 0 |
| Security Hotspots | All reviewed |
| Code Smells | Acceptable level |
| Duplicated Code | < 3% |
Runs on the staging branch. Since scan artifacts are produced on the dev branch (different pipeline), the job downloads them via the GitLab API: curl --header "PRIVATE-TOKEN: ${GITLAB_PAT}" "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/jobs/artifacts/dev/download?job={JOB_NAME}" → unzip. The ZAP STG report is available from the current pipeline's dast_stg stage. Each service maps to a dedicated DefectDojo engagement.
GitLab limits needs: to 5 entries, and needs:project cannot use $CI_PROJECT_PATH variables. The solution: download artifacts from the latest successful dev pipeline via the GitLab REST API using GITLAB_PAT, then unzip them into the build directory before uploading to DefectDojo.
| Report | Scan Type | Source Stage |
|---|---|---|
gitleaks-report.json | Gitleaks Scan | Secret Scan |
trufflehog-report.json | Trufflehog Scan | Secret Scan |
semgrep-report.json | Semgrep JSON Report | SAST |
bandit / gosec / njsscan | Per-language scan type | SAST |
pip-audit / govulncheck / npm-audit | Per-language scan type | SCA |
trivy-report.json | Trivy Scan | Container Scan |
grype-report.json | Anchore Grype | Container Scan |
sbom-cyclonedx.json | CycloneDX Scan | SBOM |
zap-stg-report.json | ZAP Scan | DAST |
The gateway to production. Runs on the staging branch with when: manual. A Tech Lead or SRE must click "Play" in the GitLab UI. The job then auto-merges staging into main via git push origin staging:main, triggering the PROD pipeline.
staginggit push origin staging:mainoauth2:${GITLAB_PAT}Runs on main branch. Same GitOps mechanism as staging — clones infra-gitops via oauth2:${GITLAB_PAT}, reads the staging image tag from the staging kustomization overlay, and updates the production overlay. Requires when: manual — a team member must explicitly click "Deploy". ArgoCD auto-syncs the production namespace.
Same health + API checks as staging, using wget on alpine:3.18. Targets https://${APP_NAME}.apps.yhakkache.tech (no prod- prefix — matches SSL certificate SANs). If this fails, the rollback job becomes immediately relevant.
Baseline-only ZAP scan on production using mkdir -p /zap/wrk. A full scan would generate too much traffic and risk performance impact on live users.
Uploads the production ZAP report to DefectDojo for tracking alongside staging findings. Uses python:3.11-slim with apt-get install curl (Alpine curl has musl TLS issues with HAProxy).
Manual trigger, emergency-only. Clones infra-gitops via oauth2:${GITLAB_PAT} and uses CI_COMMIT_BEFORE_SHA to roll back to the previous image tag. ArgoCD detects the change and redeploys the old version.
| # | Stage | Python | Go | Node.js | Shared? |
|---|---|---|---|---|---|
| 1-2 | Secret Scan | GitLeaks + TruffleHog | GitLeaks + TruffleHog | GitLeaks + TruffleHog | ✓ |
| 3 | SAST (generic) | Semgrep | Semgrep | Semgrep | ✓ |
| 4 | SAST (lang) | Bandit | gosec | njsscan | ✗ |
| 5 | Linter | — | go vet | ESLint + security | ✗ |
| 6 | SCA (primary) | pip-audit | govulncheck | npm audit | ✗ |
| 7 | SCA (secondary) | — | — | — | — |
| 8 | Tests | pytest (26) | go test (17) | Jest (22) | ✗ |
| 9 | Build | Docker CLI (socket) | Docker CLI (socket) | Docker CLI (socket) | ✓ |
| 10-11 | Container Scan | Trivy + Grype | Trivy + Grype | Trivy + Grype | ✓ |
| 12 | SBOM | Syft | Syft | Syft | ✓ |
| 13 | Sign | Cosign | Cosign | Cosign | ✓ |
| 14 | Deploy | GitOps + ArgoCD | GitOps + ArgoCD | GitOps + ArgoCD | ✓ |
| 15 | Promote Prod | git push (manual) | git push (manual) | git push (manual) | ✓ |
| 16 | Smoke | wget (Alpine) | wget (Alpine) | wget (Alpine) | ✓ |
| 17 | DAST | OWASP ZAP | OWASP ZAP | OWASP ZAP | ✓ |
| 18 | Quality Gate | SonarQube | SonarQube | SonarQube | ✓ |
| 19 | Vuln Mgmt | DefectDojo | DefectDojo | DefectDojo | ✓ |
Every pipeline run produces the following artifacts, stored for 7 days (SBOM for 30 days).
Configured in GitLab → Settings → CI/CD → Variables.
| Variable | Description | Masked | Used By |
|---|---|---|---|
HARBOR_USER | Harbor registry username | ✓ | Build, Container Scan, SBOM, Sign |
HARBOR_PASSWORD | Harbor registry password | ✓ | Build, Container Scan, SBOM, Sign |
SONAR_HOST_URL | SonarQube instance URL | ✗ | Quality Gate |
SONAR_TOKEN | SonarQube project analysis token | ✓ | Quality Gate |
DEFECTDOJO_TOKEN | DefectDojo API token | ✓ | DefectDojo uploads |
COSIGN_KEY | Cosign private key (signing) | ✓ | Image signing |
COSIGN_PASSWORD | Cosign key passphrase | ✓ | Image signing |
COSIGN_PUB | Cosign public key (verification) | ✗ | Image verification |
GITLAB_PAT | Personal Access Token for cross-project access | ✓ | Promote, Deploy STG, Deploy PROD, Rollback, DefectDojo artifact download |
Self-hosted on DigitalOcean + GCP, interconnected via Tailscale mesh VPN.
These jobs block the pipeline on failure — the security and quality gates.
| # | Stage | Job | Rationale |
|---|---|---|---|
| 1 | Secret Scan | GitLeaks | Secrets in code must never be built into an image |
| 2 | Secret Scan | TruffleHog | allow_failure — only verified secrets; false positives common |
| 3 | Test | Unit Tests | Broken logic must not be deployed |
| 4 | Build | Docker Build | No image = nothing to deploy |
| 5 | SBOM | Syft | Blocks pipeline — supply chain transparency is mandatory |
| 6 | Sign | Cosign | Blocks pipeline — unsigned images must not be deployed |
| 7 | Deploy STG | GitOps Deploy | Must deploy to staging before validation |
| 8 | Smoke STG | Health Check | Application must be alive before further testing |
| 9 | Quality Gate | SonarQube | Code quality threshold — no production without passing |
| 10 | Promote PROD | promote_to_production | Manual — Lead/SRE approves merge to main |
| 11 | Deploy PROD | GitOps Deploy | Manual — requires human approval |
| 12 | Smoke PROD | Health Check | Production failure = immediate rollback consideration |
| 13 | Rollback | Rollback | Manual — emergency revert |