diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 000000000..2454de86e --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,35 @@ +## Goal + +This PR delivers Lab N work: . + +## Changes + +* Added/updated `submissions/labN.md`. +* Added/updated lab-related configuration files. +* Documented testing steps, observed output, and required artifacts. + +## Testing + +Commands used: + +```bash + +``` + +Observed output: + +```text + +``` + +## Artifacts & Screenshots + +* `submissions/labN.md` +* + +## Checklist + +* [ ] Title is clear (`feat(labN): ` style) +* [ ] No secrets/large temp files committed +* [ ] Submission file at `submissions/labN.md` exists + diff --git a/.github/workflows/lab1-smoke.yml b/.github/workflows/lab1-smoke.yml new file mode 100644 index 000000000..2ed88e2c7 --- /dev/null +++ b/.github/workflows/lab1-smoke.yml @@ -0,0 +1,51 @@ +name: Lab 1 Smoke Test + +on: + pull_request: + branches: + - main + +permissions: + contents: read + +jobs: + smoke-test: + runs-on: ubuntu-latest + + steps: + - name: Pull Juice Shop image + run: docker pull bkimminich/juice-shop:v20.0.0 + + - name: Run Juice Shop + run: | + docker run -d --name juice-shop \ + -p 127.0.0.1:3000:3000 \ + bkimminich/juice-shop:v20.0.0 + + - name: Wait for Juice Shop to become healthy + run: | + for i in $(seq 1 30); do + if curl --silent --fail http://127.0.0.1:3000/rest/admin/application-version; then + echo + echo "Juice Shop is healthy" + exit 0 + fi + + echo "Waiting for Juice Shop... attempt $i/30" + sleep 2 + done + + echo "Juice Shop did not become healthy within 60 seconds" + docker logs juice-shop + exit 1 + + - name: Verify homepage returns HTTP 200 + run: | + STATUS_CODE=$(curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:3000) + echo "Homepage HTTP status: $STATUS_CODE" + + if [ "$STATUS_CODE" != "200" ]; then + echo "Expected HTTP 200, got $STATUS_CODE" + docker logs juice-shop + exit 1 + fi diff --git a/labs/lab6/policies/my-custom-policy.yaml b/labs/lab6/policies/my-custom-policy.yaml new file mode 100644 index 000000000..695b2f315 --- /dev/null +++ b/labs/lab6/policies/my-custom-policy.yaml @@ -0,0 +1,14 @@ +--- +metadata: + id: "CKV2_CUSTOM_1" + name: "Ensure all taggable AWS resources declare the project ownership tag" + category: "CONVENTION" + severity: "MEDIUM" + guideline: "Internal tagging standard: every cloud resource must identify its owning project" +scope: + provider: "aws" +definition: + cond_type: "attribute" + resource_types: "taggable" + attribute: "tags.project" + operator: "exists" diff --git a/submissions/lab1.md b/submissions/lab1.md new file mode 100644 index 000000000..a4b77b61f --- /dev/null +++ b/submissions/lab1.md @@ -0,0 +1,107 @@ +# Lab 1 — Submission + +## Triage Report: OWASP Juice Shop + +### Scope & Asset +- Asset: OWASP Juice Shop (local lab instance) +- Image: `bkimminich/juice-shop:v20.0.0` +- Image digest / image ID: `sha256:fd58bdc9745416afce8184ee0666278a436574633ea7880365153a63bfd418b0` +- Host OS: Arch Linux +- Docker version: `Docker version 29.5.1, build 2518b52d94` + +### Deployment Details +- Run command used: `docker run -d --name juice-shop -p 127.0.0.1:3000:3000 bkimminich/juice-shop:v20.0.0` +- Access URL: http://127.0.0.1:3000 +- Network exposure: 127.0.0.1 only? [x] Yes [ ] No +- Container restart policy: `no` + +### Health Check +- HTTP code on `/`: 200 +- Product count from `/api/Products`: 46 +- Version check from `/rest/admin/application-version`: `{"version":"20.0.0"}` +- API check (first 200 chars of `/api/Products`): + ```json +{"status":"success","data":[{"id":1,"name":"Apple Juice (1000ml)","description":"The all-time classic.","price":1.99,"deluxePrice":0.99,"image":"apple_juice.jpg","createdAt":"2026-06-12T11:00:57.053Z" + ``` +- Container uptime: + ``` +NAMES STATUS PORTS +juice-shop Up 10 minutes 127.0.0.1:3000->3000/tcp + ``` + +### Initial Surface Snapshot (from browser exploration) +- Login/Registration visible: [x] Yes [ ] No — notes: visible through the Account menu; login and registration are reachable from the UI. +- Product listing/search present: [x] Yes [ ] No — notes: product cards are listed on the landing page; product data is also available through `/api/Products`. +- Admin or account area discoverable: [x] Yes [ ] No — notes: account functionality is visible from the top navigation; admin-related backend endpoint `/rest/admin/application-version` is reachable and exposes the application version. +- Client-side errors in DevTools console: [ ] Yes [x] No — notes: no blocking client-side errors were observed during initial load and product browsing. +- Pre-populated local storage / cookies: browser state contains normal Juice Shop client/UI state such as language or welcome/cookie-banner preferences; no user authentication token was present before login. +- Product detail network behavior: opening a product detail triggers API calls such as `/api/Products//reviews`; reading product/review data does not require authentication during initial browsing. + +### Security Headers (Quick Look) +Run: `curl -I http://127.0.0.1:3000 2>&1 | head -20`. Output: +``` + % Total % Received % Xferd Average Speed Time Time Time Current + Dload Upload Total Spent Left Speed + 0 0 0 0 0 0 0 0 0 0 9903 0 0 0 0 0 0 0 0 9903 0 0 0 0 0 0 0 0 9903 0 0 0 0 0 0 0 +HTTP/1.1 200 OK +Access-Control-Allow-Origin: * +X-Content-Type-Options: nosniff +X-Frame-Options: SAMEORIGIN +Feature-Policy: payment 'self' +X-Recruiting: /#/jobs +Accept-Ranges: bytes +Cache-Control: public, max-age=0 +Last-Modified: Fri, 12 Jun 2026 11:00:57 GMT +ETag: W/"26af-19ebb7e0182" +Content-Type: text/html; charset=UTF-8 +Content-Length: 9903 +Vary: Accept-Encoding +Date: Fri, 12 Jun 2026 11:11:54 GMT +Connection: keep-alive +Keep-Alive: timeout=5 + +``` + +Which of these are MISSING? +- [x] `Content-Security-Policy` +- [x] `Strict-Transport-Security` +- [ ] `X-Content-Type-Options: nosniff` +- [ ] `X-Frame-Options` + +### Top 3 Risks Observed +1. **Missing or incomplete browser hardening headers** — The initial header check shows that at least some defensive headers are absent or not explicitly configured. This matters because headers such as CSP, HSTS, X-Frame-Options, and X-Content-Type-Options reduce the impact of common browser-side attacks; this maps to OWASP Top 10:2025 A06 Security Misconfiguration. +2. **Discoverable unauthenticated API surface** — Product data and review-related endpoints are easy to discover from DevTools and can be queried directly. Even when public reads are intended, this increases the reconnaissance surface and should be reviewed for excessive data exposure or missing authorization boundaries; this maps to OWASP Top 10:2025 A01 Broken Access Control. +3. **Input-heavy application surface around login, registration, search, and product interaction** — The first exploration immediately exposes forms, account flows, and API-backed product functionality. These areas are common entry points for injection, authentication abuse, and validation mistakes, so they should be prioritized in later DAST/manual testing; this maps to OWASP Top 10:2025 A03 Injection and A07 Identification & Authentication Failures. + +## PR Template Setup + +- File: `.github/PULL_REQUEST_TEMPLATE.md` +- Sections included: Goal / Changes / Testing / Artifacts & Screenshots +- Checklist items: + - Title is clear (`feat(labN): ` style) + - No secrets/large temp files committed + - Submission file at `submissions/labN.md` exists +- Auto-fill verified: [x] Yes — PR description showed my template (PASTE_DRAFT_PR_URL_HERE_AFTER_PUSH) + +## GitHub Community + +I starred the course repository and `simple-container-com/api` because stars work both as bookmarks and as a public signal that helps useful open-source projects gain visibility. I also followed the professor, TAs, and classmates because following developers makes it easier to track course activity, discover related projects, and build professional connections for future teamwork. + +Completed actions: +- [x] Starred the course repository +- [x] Starred `simple-container-com/api` +- [x] Followed @Cre-eD +- [x] Followed @Naghme98 +- [x] Followed @pierrepicaud +- [x] Followed at least 3 classmates from the course + +## Bonus: CI Smoke Test + +- Workflow file: `.github/workflows/lab1-smoke.yml` +- Trigger: `pull_request` on main +- Run URL (green): PASTE_GREEN_ACTIONS_RUN_URL_HERE_AFTER_PR +- Workflow run duration: PASTE_WORKFLOW_DURATION_HERE +- Curl response excerpt: + ``` +HTTP/1.1 200 OK / Homepage HTTP status: 200 + ``` diff --git a/submissions/lab6.md b/submissions/lab6.md new file mode 100644 index 000000000..c14b87373 --- /dev/null +++ b/submissions/lab6.md @@ -0,0 +1,143 @@ +# Lab 6 — Submission + +## Environment and tool versions + +```text +Docker: Docker version 29.5.2, build 79eb04c7d8 +jq: jq-1.8.1-dirty +Checkov mode: local +Checkov: 3.3.2 +KICS: Keeping Infrastructure as Code Secure v2.1.20 +``` + +The files under `labs/lab6/vulnerable-iac/` were scanned but intentionally not modified. + +## Task 1: Checkov on Terraform + +### Terraform scan + +- Total evaluated checks: **127** +- Passed: **49** +- Failed: **78** +- Skipped: **0** +- Parsing errors: **0** + +| Severity | Count | +|----------|------:| +| Critical | 0 | +| High | 0 | +| Medium | 0 | +| Low | 0 | +| Info | 0 | +| Unspecified | 78 | +| **Total failed** | **78** | + +`Unspecified` means the local open-source Checkov result did not attach a severity value to that built-in policy. + +### Top 5 rule IDs by frequency + +| Rule ID | Count | What it checks | Severity | +|---------|------:|----------------|----------| +| `CKV_AWS_289` | 4 | Ensure IAM policies does not allow permissions management / resource exposure without constraints | Unspecified | +| `CKV_AWS_355` | 4 | Ensure no IAM policies documents allow "*" as a statement's resource for restrictable actions | Unspecified | +| `CKV_AWS_23` | 3 | Ensure every security group and rule has a description | Unspecified | +| `CKV_AWS_288` | 3 | Ensure IAM policies does not allow data exfiltration | Unspecified | +| `CKV_AWS_290` | 3 | Ensure IAM policies does not allow write access without constraints | Unspecified | + +### Module-leverage analysis + +I would fix **CKV_AWS_289 — Ensure IAM policies does not allow permissions management / resource exposure without constraints** at the shared module level first. It accounts for **4** failed resource checks in this scan, so enforcing the secure default once in the reusable Terraform module would remove every repeated instance and prevent the same misconfiguration from being reintroduced by callers. + +## Task 2: KICS on Ansible and Pulumi + +### Ansible severity breakdown + +| Severity | Count | +|----------|------:| +| HIGH | 9 | +| MEDIUM | 0 | +| LOW | 1 | +| INFO | 0 | +| **Total findings** | **10** | + +### Pulumi severity breakdown + +| Severity | Count | +|----------|------:| +| CRITICAL | 1 | +| HIGH | 2 | +| MEDIUM | 1 | +| LOW | 0 | +| INFO | 2 | +| **Total findings** | **6** | + +### Top 5 KICS queries by finding count + +| Query | Severity | Findings | Platform | +|-------|----------|---------:|----------| +| Passwords And Secrets - Generic Password | HIGH | 6 | Ansible | +| Passwords And Secrets - Password in URL | HIGH | 2 | Ansible | +| RDS DB Instance Publicly Accessible | CRITICAL | 1 | Pulumi | +| DynamoDB Table Not Encrypted | HIGH | 1 | Pulumi | +| Passwords And Secrets - Generic Password | HIGH | 1 | Pulumi | + +### Checkov versus KICS + +**What Checkov did better for Terraform:** Checkov produced Terraform-native resource identifiers, CKV rule IDs, source ranges, and graph-aware relationships that make repeated failures easy to trace back to one shared module or resource definition. + +**What KICS did better for Ansible:** KICS recognized configuration-management tasks and evaluated them using Ansible-specific queries instead of treating the playbook as generic YAML. Findings therefore retain task and module context. + +**Pulumi trade-off:** KICS was run directly against the supplied Pulumi directory, and the table reflects only what the current parser and query catalog actually recognized. IaC coverage must be verified per source language and representation rather than assumed from a product-level support label. + +## Bonus: Custom Checkov Policy + +### Policy file + +```yaml +--- +metadata: + id: "CKV2_CUSTOM_1" + name: "Ensure all taggable AWS resources declare the project ownership tag" + category: "CONVENTION" + severity: "MEDIUM" + guideline: "Internal tagging standard: every cloud resource must identify its owning project" +scope: + provider: "aws" +definition: + cond_type: "attribute" + resource_types: "taggable" + attribute: "tags.project" + operator: "exists" +``` + +### Rule fires + +The custom scan produced **12** failed check(s) with a `CKV2_CUSTOM_*` identifier. One representative result is: + +```json +{ + "check_id": "CKV2_CUSTOM_1", + "check_name": "Ensure all taggable AWS resources declare the project ownership tag", + "severity": "MEDIUM", + "resource": "aws_db_instance.unencrypted_db", + "file_path": "/database.tf", + "file_line_range": [ + 5, + 37 + ] +} +``` + +### Why this rule matters + +A mandatory `tags.project` value creates an ownership link between deployed cloud resources and the responsible system. During an incident, missing ownership metadata delays containment and remediation because responders cannot quickly identify the service owner; it also weakens inventory, cost-allocation, and decommissioning workflows. Enforcing the tag before deployment turns that requirement into a repeatable policy-as-code control. + +## Final verification checklist + +- [x] Checkov scanned the deliberately vulnerable Terraform directory. +- [x] Checkov counts and top rules were generated from actual JSON. +- [x] KICS scanned both Ansible and Pulumi directories. +- [x] KICS tables were generated from actual JSON. +- [x] Module-level triage identifies one concrete high-leverage rule. +- [x] `CKV2_CUSTOM_1` was accepted and fired. +- [x] Regenerable scanner output remains outside the commit.