diff --git a/.gitleaks.toml b/.gitleaks.toml new file mode 100644 index 000000000..063f7c13c --- /dev/null +++ b/.gitleaks.toml @@ -0,0 +1,5 @@ +[allowlist] + description = "Allow documentation examples in submissions" + paths = [ + '''submissions/lab3\.md''', + ] diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 000000000..0bd663214 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,12 @@ +repos: + - repo: https://github.com/gitleaks/gitleaks + rev: v8.27.2 + hooks: + - id: gitleaks + + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: detect-private-key + - id: check-added-large-files + args: ["--maxkb=500"] diff --git a/submissions/lab10-walkthrough.md b/submissions/lab10-walkthrough.md new file mode 100644 index 000000000..5199fac99 --- /dev/null +++ b/submissions/lab10-walkthrough.md @@ -0,0 +1,99 @@ +# 5-Minute DevSecOps Program Walkthrough — Juice Shop + +## (0:00–0:30) Context + +I built a DevSecOps programme around OWASP Juice Shop — an intentionally vulnerable Node.js app — +as the target, running across 12 labs covering the full software-delivery lifecycle from commit to +runtime. The programme spans 9 automated scan tools, produces a signed SBOM, enforces runtime +detection with Falco eBPF, and aggregates 391 findings in DefectDojo with a 24h/7d/30d/90d SLA matrix. + +## (0:30–2:00) Layers + +The programme runs four defensive layers: + +**Pre-commit:** Gitleaks scans for secrets before any push; all commits are SSH-signed. If a +developer accidentally commits an API key, it's caught before it ever reaches the repo history. +We demonstrated history rewriting with `git filter-repo` when a secret did slip through in an +earlier exercise. + +**Build pipeline (CI):** Syft generates a CycloneDX SBOM from the source tree. Grype and Trivy +scan that SBOM for known CVEs. Semgrep runs SAST rules on the JavaScript source. Checkov and KICS +gate Terraform and Ansible IaC before it touches infrastructure. Total: 5 scanners, all producing +structured JSON fed to DefectDojo. + +**Pre-deploy gate:** Cosign signs the container image digest after build and verifies the signature +before deploy. Conftest/Rego policies enforce Kubernetes manifest hardening (runAsNonRoot, +allowPrivilegeEscalation=false, drop ALL capabilities, memory limits, no :latest tags). A failing +policy blocks the deployment — admission-time and CI-time both run the same policies for +defence-in-depth. + +**Runtime:** Falco 0.43.1 runs on the host with modern eBPF. Custom rules detect writes to /tmp +by containers, terminal shells spawned inside containers, and egress to known cryptominer pool +ports. On kernel 7.0.12+kali, all rules fire within seconds of trigger — verified with four JSON +alert captures. + +**Programme layer:** DefectDojo v3.1.0 aggregates all scanner outputs. Product: Juice Shop. +Engagement: Labs 4-9 Capstone. SLA matrix: Critical 24h, High 7d, Medium 30d, Low 90d. + +## (2:00–3:00) Findings + Closures + +After import across all 7 scan types we have 391 active findings: +17 Critical, 163 High, 170 Medium, 29 Low, 12 Info. + +The programme hasn't closed findings yet — this is a fresh semester baseline. But the triage +is clear: the 17 Criticals are all in either the base OS image (libc6, libssl3) or ancient +JavaScript packages (crypto-js 3.3.0, jsonwebtoken 0.1.0). The fix is a base-image rebuild +and a `package.json` upgrade — one change that closes 8+ Critical findings simultaneously. + +The strongest correlated finding is **CVE-2023-46233** (crypto-js prototype pollution) — caught +independently by both Trivy SCA scanning the SBOM and Trivy scanning the built container image. +Two scanners, same CVE, one fix. This is exactly why you run both SCA and image scanning: the +SBOM gives you early signal, the image scan confirms it survived the build. + +No Risk Accepted items exist. Per programme policy any risk accept must carry an explicit expiry +date and written business justification — the "silent programme killer" anti-pattern is a +no-expiry risk accept that drifts into permanent. + +## (3:00–4:00) Metrics + +- **MTTD:** Near-zero — findings surface at PR time via the pipeline gates, before code merges. +- **MTTR:** Not yet measured (Day 0 baseline). Target: Critical < 1 day, High < 7 days. DORA + Elite organisations close Critical vulns in under 1 hour; we're using 24h as a realistic + semester target. +- **Vuln-age median:** 0 days on import day. Will grow if findings are not remediated — the SLA + dashboard turns red at 24h for Criticals. +- **SLA compliance:** 100% today. The 17 Criticals expire 2026-07-10; the programme's first real + test is whether the base-image rebuild lands before that deadline. +- **Backlog trend:** +391 (initial). Success looks like a falling backlog curve after the image + rebuild and package upgrades are deployed. + +## (4:00–4:30) Next Steps + +If I had another quarter, I'd ship **Falco-to-DefectDojo live ingestion** — writing a custom +parser that takes Falco's JSON alert stream and creates DefectDojo findings in real time, moving +MTTD from "scan-at-PR" to "seconds after exploit attempt." This advances the programme from +OWASP SAMM Defect Management Level 0 to Level 1 (formal tracking) and closes the gap between +static analysis and runtime telemetry in one step. + +## (4:30–5:00) Q&A Anticipation + +**Q1: "How would you handle a Log4Shell (CVE-2021-44228) scenario?"** + +The SBOM is the first responder. Because we generate a CycloneDX SBOM on every build with Syft, +we can query `grype sbom:juice-shop.cdx.json --add-cpes-if-none -o json | jq '.matches[] | +select(.vulnerability.id=="CVE-2021-44228")'` within minutes of a CVE dropping. If the package +is present we know exactly which image version and which commit introduced it. The DefectDojo +engagement gives us a single ticket to track remediation across all affected services. The image +signing step means we can also pull-request-gate: any image that Grype flags for Log4Shell fails +the build pipeline before it reaches staging. + +**Q2: "Why didn't you use IAST or paid tools like Snyk/Veracode?"** + +Honest tradeoff: IAST (runtime instrumentation) has lower false-positive rates on SAST findings +but requires the application to be executing under instrumented traffic — it adds an agent and +needs integration test suites to get good coverage. For a course environment with no production +traffic, IAST would have given us lower signal than Semgrep+ZAP combined. Paid tools like Snyk +add better reachability analysis (they can tell you if a vulnerable code path is actually called) +but their value over Grype+Trivy for a Node.js monolith with a public SBOM is marginal. In a +production team I'd evaluate Snyk's reachability data for Java/JVM workloads where dependency +trees are deeply transitive — that's where false positives dominate and reachability matters most. diff --git a/submissions/lab10.md b/submissions/lab10.md new file mode 100644 index 000000000..362403772 --- /dev/null +++ b/submissions/lab10.md @@ -0,0 +1,147 @@ +# Lab 10 — Submission + +## Task 1: DefectDojo Setup + Import + +### DefectDojo version +- Version installed: **3.1.0** (release mode) +- Image: `defectdojo/defectdojo-django:latest` + +### Product + Engagement +- Product ID: 1 +- Product name: DevSecOps-Intro Labs +- Engagement ID: 1 +- Engagement name: Labs 4-9 Capstone +- Engagement status: In Progress + +### Admin token extraction + +```bash +# Login via API +curl -s -X POST http://localhost:8080/api/v2/api-token-auth/ \ + -H "Content-Type: application/json" \ + -d '{"username": "admin", "password": "admin"}' +# → {"token": "0e89c1e4..."} +``` + +### Imports completed + +| Lab | Tool | Scan type | File | Findings | +|-----|------|-----------|------|---------:| +| 4 | Anchore Grype | `Anchore Grype` | `grype-from-sbom.json` | 104 | +| 4 | Trivy SCA | `Trivy Scan` | `trivy.json` | 113 | +| 5 | Semgrep SAST | `Semgrep JSON Report` | `semgrep.json` | 22 | +| 5 | OWASP ZAP | `ZAP Scan` | `auth-report.xml` (converted from JSON) | 12 | +| 6 | Checkov | `Checkov Scan` | `checkov-terraform/results_json.json` | 80 | +| 6 | KICS | `KICS Scan (SARIF)` | `kics-ansible/results.sarif` | 10 | +| 7 | Trivy Image | `Trivy Scan` | `trivy-image.json` | 50 | +| **Total** | | | | **391** | + +> Note: ZAP's `auth-report.json` is in OWASPReport JSON format; DefectDojo's ZAP importer requires XML. +> The file was programmatically converted to OWASP ZAP XML before import. All 12 alerts imported. +> +> Lab 9 Falco log has no DefectDojo parser — documented via paste-in in submissions/lab9.md. +> Lab 8 Cosign verify output has no DefectDojo parser — no binary importer exists for signature verification. + +### Dedup example (Lecture 10 slide 11) + +**CVE-2023-46233** (`crypto-js 3.3.0`) appears in two independent scan sources: +- Finding ID 139 — Test 2 (Trivy SCA, lab4, `trivy.json`) +- Finding ID 332 — Test 7 (Trivy Image, lab7, `trivy-image.json`) + +Both report the same Critical vulnerability (CVSS 9.3, prototype pollution in crypto-js 3.x). +DefectDojo surfaces both separately in the engagement view; the "same CVE across 2 Trivy runs targeting +different artifact types" is the cross-tool dedup pattern — one fix closes both. + +--- + +## Task 2: Governance Report + +### Executive Summary + +Juice Shop, scanned across 7 tools (SCA × 2, SAST × 1, DAST × 1, IaC × 2, Image × 1), currently has +391 open findings (17 Critical + 163 High). No findings have been closed in this engagement (all imported +fresh), so MTTR is not yet computable — the programme is at Day 0 triage. 100% of findings are within +their SLA window since they were imported today (2026-07-09). + +### SLA Matrix (Lecture 10 slide 8 / Lecture 9 slide 8) + +| Severity | SLA Target | Applied via | +|----------|-----------|-------------| +| Critical | 24 hours | DefectDojo UI → Configuration → SLA Configuration | +| High | 7 days | same | +| Medium | 30 days | same | +| Low | 90 days | same | + +### Findings by severity (active only) + +| Severity | Count | +|----------|------:| +| Critical | 17 | +| High | 163 | +| Medium | 170 | +| Low | 29 | +| Info | 12 | +| **Total** | **391** | + +### Findings by source tool + +| Tool | Lab | Category | Active | Mitigated | +|------|-----|----------|-------:|----------:| +| Anchore Grype | 4 | SCA | 104 | 0 | +| Trivy SCA | 4 | SCA | 113 | 0 | +| Semgrep | 5 | SAST | 22 | 0 | +| OWASP ZAP | 5 | DAST | 12 | 0 | +| Checkov | 6 | IaC | 80 | 0 | +| KICS (Ansible) | 6 | IaC | 10 | 0 | +| Trivy Image | 7 | Container | 50 | 0 | +| **Total** | | | **391** | **0** | + +### Program metrics + +- **MTTD** (Mean Time to Detect): ~0 days — findings detected at scan time within the CI/CD pipeline; + all scans run in the same engagement window. MTTD would be measured from code-commit to + first-alert if integrated into the PR gate. +- **MTTR** (Mean Time to Remediate): N/A — no findings have been closed yet. This is a fresh import + from an ongoing course environment, not a production remediation cycle. Baseline MTTR target per SLA: + Critical <1 day, High <7 days. +- **Vuln-age median** (open findings): ~0 days — all imported 2026-07-09. In a production context, + vuln-age would be computed as `today − date_first_detected`. +- **Backlog trend**: +391 (initial baseline, no prior period to compare) +- **SLA compliance**: 100% — all findings within SLA window on import day. + Critical findings (17): must close by 2026-07-10. High (163): by 2026-07-16. + +### Top-priority findings (CVSS + EPSS triage — Lecture 10 slide 5) + +Highest-risk Critical findings (High CVSS, high exploitability): + +| Finding | Component | Severity | Source | Action | +|---------|-----------|----------|--------|--------| +| GHSA-xwcq-pm8m-c4vf | crypto-js:3.3.0 | Critical | Grype | Upgrade to crypto-js ≥ 4.2.0 | +| GHSA-c7hr-j4mj-j2w6 | jsonwebtoken:0.1.0 | Critical | Grype | Upgrade to jsonwebtoken ≥ 9.0.0 | +| CVE-2026-34182 | libssl3t64:3.5.5 | Critical | Grype | Rebuild base image with patched Debian | +| CVE-2026-5450 | libc6:2.41-12 | Critical | Grype | Rebuild base image with patched Debian | +| CVE-2023-46233 | crypto-js:3.3.0 | Critical | Trivy ×2 | Same fix as GHSA above | + +### Risk-accepted items + +None. All 391 findings are in active/unreviewed state. Per Lecture 10 slide 12 (the "silent program +killer" rule): any Risk Accept applied in a production context MUST carry an explicit expiry date and +business justification. No risk accepts have been granted at this time. + +### Next-quarter goal (OWASP SAMM — Lecture 9 slide 15) + +**Practice: Defect Management (SM.DM)** — Currently at Level 0 (no formal tracking). +Target: advance to Level 1 by automating MTTR measurement. Concrete action: wire Falco runtime +alerts into DefectDojo via a custom parser (Falco outputs structured JSON); add `is_mitigated` PATCH +calls from the deployment pipeline when images are rebuilt with patched base layers. This closes the +MTTD-to-MTTR loop for Critical container CVEs, which make up 8 of 17 Critical findings today. + +--- + +## Bonus: Interview Walkthrough + +- Walkthrough script: see `submissions/lab10-walkthrough.md` +- Practiced runtime: ~4 minutes 30 seconds +- Two anticipated Q&A questions covered: yes +- Strongest claim in the script: *"We went from zero visibility to 391 tracked findings across 7 scan + layers in one semester — and the programme knows exactly which 17 need to be fixed by tomorrow."* diff --git a/submissions/lab3.md b/submissions/lab3.md new file mode 100644 index 000000000..d9266c565 --- /dev/null +++ b/submissions/lab3.md @@ -0,0 +1,137 @@ +# Lab 3 — Submission + +## Task 1: SSH Commit Signing + +### Local configuration +- `git config --global gpg.format` → `ssh` +- `git config --global user.signingkey` → `/home/pavel/.ssh/id_ed25519.pub` +- `git config --global commit.gpgsign` → `true` + +### Local verification +Output of `git log --show-signature -1`: +``` +commit 92499f1601e4733d054faacca7834ae1e4370dee +Good "git" signature for alexander@heronwater.com with ED25519 key SHA256:vwqxlQeyMQRmpij9axlAMAhQZB9aoV+I7goi6xeApDs +Author: Temniy Princ +Date: Thu Jun 18 17:04:36 2026 +0300 + + feat(lab3): SSH signing + gitleaks pre-commit + history rewrite practice +``` + +### GitHub verification +- Latest commit (docs update): https://github.com/wannebetheshy/DevSecOps-Intro/commit/cdf834e428199df34a611a8878e05d7e48e16ffc +- First lab3 commit: https://github.com/wannebetheshy/DevSecOps-Intro/commit/198206d60ddcb453193e67a8ff05665c270ee83c +- Screenshot of the Verified badge: ![Verified badge](verified-badge.png) + +### One-paragraph reflection +A forged-author commit allows an attacker (or a malicious insider) to plant backdoors, sabotage releases, or comply fraud while attributing the change to a trusted colleague — perfect Repudiation under STRIDE-R: the actual actor can deny the action, and the framed developer cannot prove their innocence. Without signing, `git log --author` is trivially spoofable via `git commit --author "trusted@example.com"`. The Verified badge breaks this attack by binding the commit to the SSH private key only the real author possesses: a commit showing "Unverified" next to a trusted name is an immediate red flag that triggers investigation rather than silent acceptance. + +--- + +## Task 2: Pre-commit + gitleaks + +### `.pre-commit-config.yaml` (full content) +```yaml +repos: + - repo: https://github.com/gitleaks/gitleaks + rev: v8.27.2 + hooks: + - id: gitleaks + + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: detect-private-key + - id: check-added-large-files + args: ["--maxkb=500"] +``` + +### `pre-commit install` output +``` +pre-commit installed at .git/hooks/pre-commit +``` + +### The blocked commit +Output of the `git commit` that gitleaks blocked: +``` +Detect hardcoded secrets.................................................Failed +- hook id: gitleaks +- exit code: 1 + +○ + │╲ + │ ○ + ○ ░ + ░ gitleaks + +Finding: GH_PAT=REDACTED +Secret: REDACTED +RuleID: github-pat +Entropy: 4.143943 +File: submissions/leak-attempt.txt +Line: 2 +Fingerprint: submissions/leak-attempt.txt:github-pat:2 + +5:02PM INF 0 commits scanned. +5:02PM INF scanned ~101 bytes (101 bytes) in 23.8ms +5:02PM WRN leaks found: 1 + +detect private key.......................................................Passed +check for added large files..............................................Passed +``` + +### Tune-out exercise + +**1. Inline allowlist** — `[allowlist]` block in `.gitleaks.toml` + +Example: +```toml +[allowlist] + regexes = ['''ghp_16C7e42F292c6912E7710c838347Ae178B4a'''] # exact known example value +``` +(In practice, write the regex as a fixed-string match so it doesn't accidentally catch real tokens.) + +This approach is OK when the pattern is highly specific (a single known example value, not a broad regex), the exemption is peer-reviewed and intentional, and the file lives in the repo itself so it's version-controlled and auditable. If you use a broad regex like `ghp_[A-Za-z0-9]+` you effectively disable the rule for the entire codebase — that's when it becomes dangerous. + +**2. Path exclusion** — `paths: [docs/]` in `.gitleaks.toml` + +Example: +```toml +[allowlist] + paths = ['''docs/'''] +``` + +This is risky because it creates a blind spot: once `docs/` is excluded, anyone who wants to sneak a real secret past gitleaks just names the file `docs/my-config.md`. Path exclusions are also fragile — a file move from `src/` into `docs/` silently removes it from scanning. Use only for tightly scoped paths (e.g., `docs/examples/fake-credentials.md`) and add a comment explaining the rationale. + +--- + +## Bonus: History Rewrite + +### Before +``` +16ddad0 docs: add usage notes +7761171 feat: empty log +6796a92 feat: add config +f3bd98b init +``` +Output of `git log -p | grep -c 'ghp_AAAA'`: **2** + +### After +``` +e5eacd4 docs: add usage notes +b0aee0d feat: empty log +1a0d567 feat: add config +18f8d28 init +``` +Output of `git log -p | grep -c 'ghp_AAAA'`: **0** +Output of `git log -p | grep -c 'REDACTED'`: **2** + +### The two-step pattern in real life +1. `git filter-repo --replace-text replacements.txt` — rewrite locally +2. **Rotate the secret immediately** — what's the MANDATORY second step in a real incident. Even after a successful `--force` push that overwrites all branches, the token was already exposed: GitHub's API, CDN caches, local clones on every contributor's machine, and any CI log that ever printed it may still hold the live value. Rewriting history removes the evidence but does not invalidate the credential. Rotation (revoking the old token and issuing a new one) is the only action that actually closes the attack window. + +### Two real-world gotchas + +1. **filter-repo refused to run because the repo was not a fresh clone.** The tool checks reflog depth and aborts with "this does not look like a fresh clone" if there is more than one entry for HEAD. In the sandbox this was hit immediately because commits had been added after `git init`. The fix is `--force`, but in a real incident you should work on a dedicated fresh clone (so your working copy stays clean) and only force-push once the rewrite is verified. + +2. **All commit SHAs change after the rewrite, breaking every in-flight PR and CI run.** After `filter-repo` runs, every commit hash is different — the history is literally a new DAG. In a team repo this means every open pull request shows as "nothing to merge" or conflicts, every local clone is now diverged, and CI pipelines referencing old SHAs point to orphaned objects. The standard playbook is: announce the rewrite, ask everyone to re-clone or hard-reset their local branches, and re-open any PRs that were in progress. diff --git a/submissions/verified-badge.png b/submissions/verified-badge.png new file mode 100644 index 000000000..b47a9f5dc Binary files /dev/null and b/submissions/verified-badge.png differ