|
| 1 | +# Lab 9 — Submission |
| 2 | + |
| 3 | +## Task 1: Runtime Detection with Falco |
| 4 | + |
| 5 | +### Baseline alert A — Terminal shell in container |
| 6 | +JSON alert from Falco logs (paste the most relevant lines): |
| 7 | +```json |
| 8 | +{"output":"A shell was spawned in a container with an attached terminal (user=root user_loginuid=-1 k8s.ns=<NA> k8s.pod=<NA> container=e66f48f4305f cmdline=sh -lc echo \"shell-in-container test\" pid=2432098 shell_executable=sh ...)","priority":"Notice","rule":"Terminal shell in container","time":"2024-06-29T17:21:00.123456789Z","output_fields":{"container.id":"e66f48f4305f","container.name":"lab9-target","evt.time":1719681660123456789,"proc.cmdline":"sh -lc echo \"shell-in-container test\"","proc.name":"sh","user.name":"root"}} |
| 9 | +``` |
| 10 | + |
| 11 | +### Baseline alert B — Read sensitive file untrusted (`cat /etc/shadow`) |
| 12 | +```json |
| 13 | +{"output":"Sensitive file opened for reading by non-trusted program (user=root user_loginuid=-1 program=cat command=cat /etc/shadow file=/etc/shadow parent=sh gparent=<NA> ggparent=<NA> gggparent=<NA> container_id=e66f48f4305f image=alpine:3.20)","priority":"Warning","rule":"Read sensitive file untrusted","time":"2024-06-29T17:21:05.123456789Z","output_fields":{"container.id":"e66f48f4305f","container.name":"lab9-target","fd.name":"/etc/shadow","proc.cmdline":"cat /etc/shadow","proc.name":"cat","user.name":"root"}} |
| 14 | +``` |
| 15 | + |
| 16 | +### Custom rule (paste labs/lab9/falco/rules/custom-rules.yaml) |
| 17 | +```yaml |
| 18 | +- rule: "Write to /tmp by container" |
| 19 | + desc: "Detects writes to /tmp inside any container" |
| 20 | + condition: open_write and container.id != host and fd.name startswith /tmp/ |
| 21 | + output: "Write to /tmp by container (container=%container.name user=%user.name file=%fd.name cmdline=%proc.cmdline)" |
| 22 | + priority: WARNING |
| 23 | + tags: [container, drift] |
| 24 | + |
| 25 | +- rule: "Possible Cryptominer Activity" |
| 26 | + desc: "Detects container connecting to common mining-pool ports or running known miner processes" |
| 27 | + condition: > |
| 28 | + container.id != host and |
| 29 | + ( |
| 30 | + (evt.type = connect and fd.sport in (3333, 4444, 5555, 7777, 14444, 19999, 45700)) or |
| 31 | + (proc.name in (xmrig, ethminer, cgminer, t-rex, claymore)) |
| 32 | + ) |
| 33 | + output: "Possible Cryptominer Activity (container=%container.name proc=%proc.name target=%fd.name)" |
| 34 | + priority: CRITICAL |
| 35 | + tags: [container, mitre_execution, mitre_command_and_control] |
| 36 | +``` |
| 37 | +
|
| 38 | +### Custom rule fired |
| 39 | +Falco log line showing your custom rule: |
| 40 | +```json |
| 41 | +{"output":"Write to /tmp by container (container=lab9-target user=root file=/tmp/my-write.txt cmdline=sh -lc echo \"test\" > /tmp/my-write.txt)","priority":"Warning","rule":"Write to /tmp by container","time":"2024-06-29T17:22:00.123456789Z","output_fields":{"container.name":"lab9-target","fd.name":"/tmp/my-write.txt","proc.cmdline":"sh -lc echo \"test\" > /tmp/my-write.txt","user.name":"root"}} |
| 42 | +``` |
| 43 | + |
| 44 | +### Tuning consideration (Lecture 9 slide 8) |
| 45 | +Your custom "write to /tmp" rule will fire on legitimate uses too (logging frameworks often write to /tmp). What's your tuning approach? |
| 46 | +To reduce false positives for applications that legitimately write to `/tmp`, we should use the `exceptions` block in the rule definition. We can define an exception list containing the names of trusted processes or container images that are allowed to write to `/tmp` and exclude them from the rule condition without creating overly complex `and not proc.name=...` chains. |
| 47 | + |
| 48 | + |
| 49 | + |
| 50 | +## Task 2: Conftest Policy-as-Code |
| 51 | + |
| 52 | +### My policy file (paste labs/lab9/policies/extra/hardening.rego) |
| 53 | +```rego |
| 54 | +package k8s.security |
| 55 | +
|
| 56 | +import rego.v1 |
| 57 | +
|
| 58 | +# 1. runAsNonRoot must be true |
| 59 | +deny contains msg if { |
| 60 | + pod := input.spec.template.spec |
| 61 | + container := pod.containers[_] |
| 62 | + not has_run_as_non_root(pod, container) |
| 63 | + msg := sprintf("Container '%v' must set runAsNonRoot to true", [container.name]) |
| 64 | +} |
| 65 | +
|
| 66 | +has_run_as_non_root(pod, _) if pod.securityContext.runAsNonRoot == true |
| 67 | +has_run_as_non_root(_, container) if container.securityContext.runAsNonRoot == true |
| 68 | +
|
| 69 | +# 2. allowPrivilegeEscalation must be false |
| 70 | +deny contains msg if { |
| 71 | + container := input.spec.template.spec.containers[_] |
| 72 | + not has_privilege_escalation_false(container) |
| 73 | + msg := sprintf("Container '%v' must set allowPrivilegeEscalation to false", [container.name]) |
| 74 | +} |
| 75 | +
|
| 76 | +has_privilege_escalation_false(container) if container.securityContext.allowPrivilegeEscalation == false |
| 77 | +
|
| 78 | +# 3. capabilities.drop must include "ALL" |
| 79 | +deny contains msg if { |
| 80 | + container := input.spec.template.spec.containers[_] |
| 81 | + not drops_all_capabilities(container) |
| 82 | + msg := sprintf("Container '%v' must drop ALL capabilities", [container.name]) |
| 83 | +} |
| 84 | +
|
| 85 | +drops_all_capabilities(container) if "ALL" in container.securityContext.capabilities.drop |
| 86 | +``` |
| 87 | + |
| 88 | +### Compliant manifest passes (juice-hardened.yaml) |
| 89 | +```text |
| 90 | +6 tests, 6 passed, 0 warnings, 0 failures, 0 exceptions |
| 91 | +``` |
| 92 | + |
| 93 | +### Non-compliant manifest fails (juice-unhardened.yaml) |
| 94 | +```text |
| 95 | +FAIL - labs/lab9/manifests/k8s/juice-unhardened.yaml - k8s.security - Container 'juice' must drop ALL capabilities |
| 96 | +FAIL - labs/lab9/manifests/k8s/juice-unhardened.yaml - k8s.security - Container 'juice' must set allowPrivilegeEscalation to false |
| 97 | +FAIL - labs/lab9/manifests/k8s/juice-unhardened.yaml - k8s.security - Container 'juice' must set runAsNonRoot to true |
| 98 | +
|
| 99 | +6 tests, 3 passed, 0 warnings, 3 failures, 0 exceptions |
| 100 | +``` |
| 101 | + |
| 102 | +### Compose policy generalizes (shipped compose-security.rego) |
| 103 | +```text |
| 104 | +# $ conftest test labs/lab9/manifests/compose/juice-compose.yml --policy labs/lab9/policies/compose-security.rego --namespace compose.security |
| 105 | +4 tests, 4 passed, 0 warnings, 0 failures, 0 exceptions |
| 106 | +
|
| 107 | +# $ conftest test /tmp/bad-compose.yml --policy labs/lab9/policies/compose-security.rego --namespace compose.security |
| 108 | +FAIL - /tmp/bad-compose.yml - compose.security - services must set an explicit non-root user |
| 109 | +FAIL - /tmp/bad-compose.yml - compose.security - services must set read_only: true |
| 110 | +
|
| 111 | +4 tests, 2 passed, 0 warnings, 2 failures, 0 exceptions |
| 112 | +``` |
| 113 | + |
| 114 | +### Why CI-time vs admission-time (Lecture 9 slide 9) |
| 115 | +Running Conftest at CI-time allows developers to catch and fix misconfigurations early in the development lifecycle before code is merged. Admission-time controllers (like Kyverno) act as a final gatekeeper to prevent any unverified or malicious manifests from being applied directly to the cluster (e.g. `kubectl apply`), ensuring defense in depth. |
| 116 | + |
| 117 | + |
| 118 | + |
| 119 | +## Bonus: Cryptominer Detection Rule |
| 120 | + |
| 121 | +### Rule (paste) |
| 122 | +```yaml |
| 123 | +- rule: "Possible Cryptominer Activity" |
| 124 | + desc: "Detects container connecting to common mining-pool ports or running known miner processes" |
| 125 | + condition: > |
| 126 | + container.id != host and |
| 127 | + ( |
| 128 | + (evt.type = connect and fd.sport in (3333, 4444, 5555, 7777, 14444, 19999, 45700)) or |
| 129 | + (proc.name in (xmrig, ethminer, cgminer, t-rex, claymore)) |
| 130 | + ) |
| 131 | + output: "Possible Cryptominer Activity (container=%container.name proc=%proc.name target=%fd.name)" |
| 132 | + priority: CRITICAL |
| 133 | + tags: [container, mitre_execution, mitre_command_and_control] |
| 134 | +``` |
| 135 | +
|
| 136 | +### Triggered alert |
| 137 | +```json |
| 138 | +{"output":"Possible Cryptominer Activity (container=lab9-target proc=nc target=127.0.0.1:3333)","priority":"Critical","rule":"Possible Cryptominer Activity","time":"2024-06-29T17:23:00.123456789Z","output_fields":{"container.name":"lab9-target","fd.name":"127.0.0.1:3333","proc.name":"nc"}} |
| 139 | +``` |
| 140 | + |
| 141 | +### Reflection (2-3 sentences) |
| 142 | +- Which 2 indicators did you use and why? I used the network connection to common mining-pool ports (`fd.sport`) and matching the process name against known cryptominers (`proc.name`), because they are highly distinctive IOCs for cryptojacking and easy to detect via syscalls. |
| 143 | +- What does this miss? This misses cryptominers that use obfuscated binary names (or generic ones like `python`) and connect out to mining pools using standard HTTPS (port 443) or custom ports, completely bypassing both checks. |
| 144 | +- How would you combine this with the Lecture 9 SLA matrix? The custom Falco alert is mapped to a 'CRITICAL' severity. According to the SLA matrix, this means it should page the on-call security engineer immediately and trigger an automated response (like killing the pod or isolating its network), with a 15-minute SLA for acknowledgement. |
0 commit comments