Skip to content

feat(lab11): hardened nginx reverse proxy + ModSecurity WAF sidecar - #6

Open
alileeeek wants to merge 11 commits into
mainfrom
feature/lab11
Open

feat(lab11): hardened nginx reverse proxy + ModSecurity WAF sidecar#6
alileeeek wants to merge 11 commits into
mainfrom
feature/lab11

Conversation

@alileeeek

Copy link
Copy Markdown
Owner

Goal

Lab 11 (Bonus) submission: Hardened Nginx reverse proxy with TLS 1.3, security headers, rate limiting, and a ModSecurity v3 + OWASP CRS WAF sidecar to block SQL injection attempts.

Changes

  • Added labs/lab11/reverse-proxy/nginx.conf with production-grade hardening (TLS 1.3 only, 6 security headers, rate limiting, timeouts, cipher hardening).
  • Added labs/lab11/waf/docker-compose.override.yml for the WAF sidecar (ModSecurity v3 + OWASP CRS v4.x).
  • Added submissions/lab11.md with verification proofs, cert-rotation runbook, and tradeoff analysis.

Testing

  • HTTP to HTTPS 308 redirect verified.
  • TLS 1.3 negotiation confirmed with X25519 curve.
  • All 6 security headers present and verified via curl (HSTS, X-CTO, X-FO, Referrer-Policy, Permissions-Policy, CSP).
  • Rate limiting triggers HTTP 429 after burst limit (60 requests test).
  • SQLi payload (' OR 1=1--) passes through Nginx alone but is blocked (HTTP 403) by the WAF sidecar.
  • OWASP CRS audit log confirms Rule 942100 (SQL Injection) was triggered.

Artifacts

  • Hardened Nginx config: labs/lab11/reverse-proxy/nginx.conf
  • WAF override config: labs/lab11/waf/docker-compose.override.yml
  • Full report with proofs: submissions/lab11.md

Task Checklist

  • Task 1 — TLS 1.3 + 6 security headers (with proof)
  • Task 2 — Rate limit + timeouts + cipher hardening + cert-rotation runbook
  • Bonus — ModSecurity WAF + OWASP CRS catching a payload Nginx-alone passes

Cre-eD and others added 11 commits June 11, 2026 23:19
…nces; lab1 /api/Products endpoint

- lec1 Slide 5: '-.-->'  is not valid mermaid (use '-.->'), quote emoji labels
- all submission templates with nested code blocks now use 4-backtick outer
  fences so GitHub stops closing them at the first inner fence
- lab1 template + acceptance criteria still referenced /rest/products,
  which 404s in Juice Shop v20.0.0 (moved to /api/Products)

Signed-off-by: Dmitrii Creed <creeed22@gmail.com>
…ame limit

Threagile uses the model title as the risks.xlsx sheet name; the shipped
41-char title crashed report generation at the Excel step ('the sheet name
length exceeds the 31 characters limit'), leaving JSONs/diagrams but no
risks.xlsx or report.pdf. Verified full output with threagile/threagile:0.9.1.
Also documented the pitfall in lab2.md.

Signed-off-by: Dmitrii Creed <creeed22@gmail.com>
Signed-off-by: Dmitrii Creed <creeed22@gmail.com>
…EADME

Task 1 jq broke on real Checkov output: a directory scan runs multiple
frameworks (terraform + secrets), so results_json.json is a JSON array and
.results.failed_checks[] errored with 'Cannot index array with string'. Use
.[] to iterate frameworks. Open-source Checkov assigns no severities (a Prisma
Cloud feature), so the severity breakdown is replaced with passed/failed and
triage is framed around rule frequency.

Pulumi was referenced in Task 1's report, acceptance criteria, and rubric but
Checkov never scanned it (no pulumi framework; the 'shipped' rendered-state
file never existed). Pulumi is scanned by KICS in Task 2 — moved its severity
table there and fixed the Task 2 jq paths (kics-ansible/, kics-pulumi/ instead
of a kics/ dir that is never created). Closed the 6.3 numbering gap.

README aligned to the actual lab (Checkov + KICS + custom Checkov policy):
dropped tfsec/Terrascan/OPA references, corrected Pulumi as AWS (not GCP), and
removed the nonexistent EKS resource from the Terraform description.

Reported by Albert Khechoyan.

Signed-off-by: Dmitrii Creed <creeed22@gmail.com>
Student-reported breakage where lab instructions assumed tool behavior the
pinned versions no longer have. Each fix verified against the real binaries.

Lab 7 (Trivy 0.69):
- 7.3 jq: add `installed: .InstalledVersion` so the 7.4 "Installed" column has data
- 7.7: `trivy k8s --namespace` was removed in 0.69 -> `--include-namespaces`

Lab 9 (Falco 0.43.1, Conftest 0.68):
- manifests are k8s/juice-{hardened,unhardened}.yaml, not flat good-pod/bad-pod
- 9.3 trigger B: a write to /usr/local/bin fires nothing in the 0.43.1 default
  ruleset (rule moved to incubating feed) -> `cat /etc/shadow` (Read sensitive
  file untrusted), confirmed firing in a live Falco run
- 9.4 hint: point at the real `open_write` macro instead of a non-existent rule
- bonus: `tcp.dport` -> `fd.sport` (real Falco field)
- add inotify pitfall + `-o watch_config_files=false` workaround for WSL2
- wire the shipped compose policy into 9.9 as a second target-shape demo

Reported by students in the DevSecOps-Intro cohort.

Signed-off-by: Dmitrii Creed <creeed22@gmail.com>
…rule passes

The hardened manifest pinned its image by tag (:v19.0.0), so a student who
implements the optional 5th hardening rule ("image must use sha256 digest,
not :tag") saw the GOOD manifest FAIL — contradicting 9.9-A's "should PASS
(0 failures)". Pin by the resolved v19.0.0 multi-arch index digest; the
unhardened manifest (:latest) still fails the rule as the bad example.

Digest resolved via 'docker buildx imagetools inspect', not fabricated.

Signed-off-by: Dmitrii Creed <creeed22@gmail.com>
…dened

Signed-off-by: Dmitrii Creed <creeed22@gmail.com>
Docker Desktop's LinuxKit VM kernel lacks BTF + tracepoints, so Falco runs
but detects nothing on Mac (esp. Apple Silicon). The Setup section wrongly
said Docker Desktop 'is fine' — corrected it, and added a Common Pitfalls
entry with the Colima fix and a 'test -f /sys/kernel/btf/vmlinux' gate check.
Reported by a student on an M-series Mac.

Signed-off-by: Dmitrii Creed <creeed22@gmail.com>
…macOS

The run-imports.sh helper did not work as shipped:

- Line 55 used mapfile, a bash 4+ builtin absent on stock macOS bash 3.2, so
  the script aborted on macOS. Replaced with a portable while-read loop and
  set -u-safe array handling; verified under bash 3.2.57.
- Every hardcoded input path was wrong. Corrected to the real Lab 4-7 outputs
  and anchored them to the repo root so the script runs from any directory.
- The script required DD_API while lab10.md tells students to export DD_URL, so
  require_env aborted immediately. It now accepts DD_URL and derives the API base.
- It imported Nuclei (not in this course) and skipped Checkov/KICS/Trivy
  image/Trivy operator. It now imports exactly the documented Lab 4-7 table,
  with scan_type fallbacks verified against the DefectDojo 2.58.3 parser source.

lab10.md aligned: correct script name (was import-all.sh), KICS split into
kics-ansible/kics-pulumi, fixed the manual curl template path, and added the
env.sample the plumbing promised. Importer outputs are now gitignored.

Reported by Albert Khechoyan.

Signed-off-by: Dmitrii Creed <creeed22@gmail.com>
The shipped nginx.conf listened on 8080/8443 and docker-compose.yml published
8080:8080/8443:8443, but every instruction and verification command in lab11.md
assumes 80/443 (curl http://localhost, https://localhost, s_client -connect
localhost:443, and the 11.1 spec 'HTTP on 80 -> HTTPS 443'). Following the lab
gave connection-refused on 443.

Switch nginx to listen 80/443, drop the :8443 from the redirect target, and
publish 80:80/443:443. The juice backend stays behind the proxy on expose:3000
(verified end-to-end: nginx reaches juice:3000 with expose only, so publishing
the backend on the host is unnecessary and would defeat the lab). Added a remap
hint for rootless Docker / busy ports.

Reported by Albert Khechoyan.

Signed-off-by: Dmitrii Creed <creeed22@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants