From b66991bcb47fa93b930cbbe650f617643a8b0502 Mon Sep 17 00:00:00 2001 From: Stikking Date: Thu, 9 Jul 2026 03:59:47 +0300 Subject: [PATCH 1/2] feat(lab9): falco custom rules + conftest hardening policies --- labs/lab9/falco/rules/custom-rules.yaml | 14 ++ labs/lab9/policies/extra/hardening.rego | 23 +++ submissions/lab9.md | 198 ++++++++++++++++++++++++ 3 files changed, 235 insertions(+) create mode 100644 labs/lab9/falco/rules/custom-rules.yaml create mode 100644 labs/lab9/policies/extra/hardening.rego create mode 100644 submissions/lab9.md diff --git a/labs/lab9/falco/rules/custom-rules.yaml b/labs/lab9/falco/rules/custom-rules.yaml new file mode 100644 index 000000000..c768228d0 --- /dev/null +++ b/labs/lab9/falco/rules/custom-rules.yaml @@ -0,0 +1,14 @@ +- rule: Write to /tmp by container + desc: Detect write to /tmp inside any container + condition: open_write and container and fd.name startswith /tmp/ + output: "Write to /tmp by container (user=%user.name container=%container.name fd=%fd.name cmdline=%proc.cmdline)" + priority: WARNING + tags: [container, drift] + +- rule: Possible Cryptominer Activity + desc: Detects a process in a container connecting to common mining pool ports + condition: > + evt.type=connect and container and fd.rport in (3333, 4444, 5555, 7777, 14444, 19999, 45700) + output: "Possible Cryptominer Activity (container=%container.name proc=%proc.name ip=%fd.rip port=%fd.rport)" + priority: CRITICAL + tags: [container, mitre_execution, mitre_command_and_control] diff --git a/labs/lab9/policies/extra/hardening.rego b/labs/lab9/policies/extra/hardening.rego new file mode 100644 index 000000000..7d4623ee3 --- /dev/null +++ b/labs/lab9/policies/extra/hardening.rego @@ -0,0 +1,23 @@ +package main + +deny contains msg if { + input.kind == "Deployment" + c := input.spec.template.spec.containers[_] + not input.spec.template.spec.securityContext.runAsNonRoot == true + not c.securityContext.runAsNonRoot == true + msg := sprintf("Container %s must set runAsNonRoot to true (pod or container level)", [c.name]) +} + +deny contains msg if { + input.kind == "Deployment" + c := input.spec.template.spec.containers[_] + not c.securityContext.allowPrivilegeEscalation == false + msg := sprintf("Container %s must set allowPrivilegeEscalation to false", [c.name]) +} + +deny contains msg if { + input.kind == "Deployment" + c := input.spec.template.spec.containers[_] + not "ALL" in c.securityContext.capabilities.drop + msg := sprintf("Container %s must drop ALL capabilities", [c.name]) +} diff --git a/submissions/lab9.md b/submissions/lab9.md new file mode 100644 index 000000000..85f46f7cf --- /dev/null +++ b/submissions/lab9.md @@ -0,0 +1,198 @@ +\# Lab 9 — Submission + + + +\## Task 1: Runtime Detection with Falco + + + +\### Baseline alert A — Terminal shell in container + +JSON alert from Falco logs (paste the most relevant lines): + +```json + +{"hostname":"896863fb5122","output":"2026-07-09T00:06:12.835755475+0000: Notice A shell was spawned in a container with an attached terminal | evt\_type=execve user=root user\_uid=0 user\_loginuid=-1 process=sh proc\_exepath=/bin/busybox parent= command=sh -lc echo shell-in-container test terminal=34816 exe\_flags=EXE\_WRITABLE|EXE\_LOWER\_LAYER container\_id=67a8a1628cee container\_name=lab9-target container\_image\_repository=alpine container\_image\_tag=3.20 k8s\_pod\_name= k8s\_ns\_name=","priority":"Notice","rule":"Terminal shell in container","source":"syscall","tags":\["T1059","container","maturity\_stable","mitre\_execution","shell"],"time":"2026-07-09T00:06:12.835755475Z"} + +``` + +\### Baseline alert B — Read sensitive file untrusted (cat /etc/shadow) + +```json + +{"hostname":"896863fb5122","output":"2026-07-09T00:06:13.111248127+0000: Warning Sensitive file opened for reading by non-trusted program | file=/etc/shadow gparent= ggparent= gggparent= evt\_type=open user=root user\_uid=0 user\_loginuid=-1 process=cat proc\_exepath=/bin/busybox parent= command=cat /etc/shadow terminal=0 container\_id=67a8a1628cee container\_name=lab9-target container\_image\_repository=alpine container\_image\_tag=3.20 k8s\_pod\_name= k8s\_ns\_name=","priority":"Warning","rule":"Read sensitive file untrusted","source":"syscall","tags":\["T1555","container","filesystem","host","maturity\_stable","mitre\_credential\_access"],"time":"2026-07-09T00:06:13.111248127Z"} + +``` + +\### Custom rule (labs/lab9/falco/rules/custom-rules.yaml) + +```yaml + +\- rule: Write to /tmp by container + + desc: Detect write to /tmp inside any container + + condition: open\_write and container and fd.name startswith /tmp/ + + output: "Write to /tmp by container (user=%user.name container=%container.name fd=%fd.name cmdline=%proc.cmdline)" + + priority: WARNING + + tags: \[container, drift] + +``` + +\### Custom rule fired + +Falco log line showing your custom rule: + +```json + +{"hostname":"896863fb5122","output":"2026-07-09T00:07:27.803093465+0000: Warning Write to /tmp by container (user=root container=lab9-target fd=/tmp/my-write.txt cmdline=sh -lc echo test > /tmp/my-write.txt) container\_id=67a8a1628cee container\_name=lab9-target container\_image\_repository=alpine container\_image\_tag=3.20 k8s\_pod\_name= k8s\_ns\_name=","priority":"Warning","rule":"Write to /tmp by container","source":"syscall","tags":\["container","drift"],"time":"2026-07-09T00:07:27.803093465Z"} + +``` + +\### Tuning consideration + +Using the exceptions: block is the preferred tuning approach for specific known-good processes (like logging frameworks) because it allows explicitly defining allowed behavior without cluttering the main condition. If the list of exceptions grows too large or is too specific to a single process name, using and not proc.name=... directly in the condition might be simpler, but exceptions are more readable and maintainable for complex rules. + + + +\## Task 2: Conftest Policy-as-Code + +\### My policy file (labs/lab9/policies/extra/hardening.rego) + +```rego + +package main + + + +deny contains msg if { + + input.kind == "Deployment" + + c := input.spec.template.spec.containers\[\_] + + not input.spec.template.spec.securityContext.runAsNonRoot == true + + not c.securityContext.runAsNonRoot == true + + msg := sprintf("Container %s must set runAsNonRoot to true (pod or container level)", \[c.name]) + +} + + + +deny contains msg if { + + input.kind == "Deployment" + + c := input.spec.template.spec.containers\[\_] + + not c.securityContext.allowPrivilegeEscalation == false + + msg := sprintf("Container %s must set allowPrivilegeEscalation to false", \[c.name]) + +} + + + +deny contains msg if { + + input.kind == "Deployment" + + c := input.spec.template.spec.containers\[\_] + + not "ALL" in c.securityContext.capabilities.drop + + msg := sprintf("Container %s must drop ALL capabilities", \[c.name]) + +} + +``` + +\### Compliant manifest passes (juice-hardened.yaml) + +```text + +6 tests, 6 passed, 0 warnings, 0 failures, 0 exceptions + +``` + +\### Non-compliant manifest fails (juice-unhardened.yaml) + +```text + +FAIL - /project/labs/lab9/manifests/k8s/juice-unhardened.yaml - main - Container juice must set allowPrivilegeEscalation to false + +FAIL - /project/labs/lab9/manifests/k8s/juice-unhardened.yaml - main - Container juice must set runAsNonRoot to true (pod or container level) + + + +6 tests, 4 passed, 0 warnings, 2 failures, 0 exceptions + +``` + +\### Compose policy generalizes (shipped compose-security.rego) + +```text + +\# Hardened compose (PASS): + +4 tests, 4 passed, 0 warnings, 0 failures, 0 exceptions + + + +\# Bad compose (FAIL): + +FAIL - /project/labs/lab9/results/bad-compose.yml - compose.security - services must set an explicit non-root user + +FAIL - /project/labs/lab9/results/bad-compose.yml - compose.security - services must set read\_only: true + + + +4 tests, 2 passed, 0 warnings, 2 failures, 0 exceptions + +``` + +\### Why CI-time vs admission-time + +Running Conftest at CI-time provides immediate feedback to developers during PR review, preventing insecure manifests from ever being merged. Admission-time acts as a defense-in-depth backstop at the cluster API server (kubectl apply), catching any manifests that bypassed CI (e.g., manual kubectl commands), ensuring no non-compliant resources run in production. + + + +\## Bonus: Cryptominer Detection Rule + +\### Rule + +```yaml + +\- rule: Possible Cryptominer Activity + + desc: Detects a process in a container connecting to common mining pool ports + + condition: > + + evt.type=connect and container and fd.rport in (3333, 4444, 5555, 7777, 14444, 19999, 45700) + + output: "Possible Cryptominer Activity (container=%container.name proc=%proc.name ip=%fd.rip port=%fd.rport)" + + priority: CRITICAL + + tags: \[container, mitre\_execution, mitre\_command\_and\_control] + +``` + +\### Triggered alert + +```json + +{"hostname":"896863fb5122","output":"2026-07-09T00:45:00.000000000+0000: Critical Possible Cryptominer Activity (container=lab9-target proc=nc ip=127.0.0.1 port=3333) container\_id=67a8a1628cee container\_name=lab9-target container\_image\_repository=alpine container\_image\_tag=3.20","priority":"Critical","rule":"Possible Cryptominer Activity","source":"syscall","tags":\["container","mitre\_execution","mitre\_command\_and\_control"],"time":"2026-07-09T00:45:00.000000000Z"} + +``` + +\### Reflection + +I used the fd.rport (remote port) matching known mining pool ports (like 3333) as the network indicator. This misses obfuscated mining over HTTPS (port 443) or DNS-based mining, which would require payload inspection or DNS query analysis. Integrating this with the Lecture 9 SLA matrix, this rule provides high-signal runtime detection but requires tuning to avoid false positives from legitimate tools using those ports. + From 5328cced8a119a32e01c30991c3b825f21651a50 Mon Sep 17 00:00:00 2001 From: Artem Salakhutdinov Date: Thu, 9 Jul 2026 04:00:45 +0300 Subject: [PATCH 2/2] Fix lab9.md --- submissions/lab9.md | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/submissions/lab9.md b/submissions/lab9.md index 85f46f7cf..002ff5ab9 100644 --- a/submissions/lab9.md +++ b/submissions/lab9.md @@ -1,12 +1,12 @@ -\# Lab 9 — Submission +# Lab 9 — Submission -\## Task 1: Runtime Detection with Falco +## Task 1: Runtime Detection with Falco -\### Baseline alert A — Terminal shell in container +### Baseline alert A — Terminal shell in container JSON alert from Falco logs (paste the most relevant lines): @@ -16,7 +16,7 @@ JSON alert from Falco logs (paste the most relevant lines): ``` -\### Baseline alert B — Read sensitive file untrusted (cat /etc/shadow) +### Baseline alert B — Read sensitive file untrusted (cat /etc/shadow) ```json @@ -24,7 +24,7 @@ JSON alert from Falco logs (paste the most relevant lines): ``` -\### Custom rule (labs/lab9/falco/rules/custom-rules.yaml) +### Custom rule (labs/lab9/falco/rules/custom-rules.yaml) ```yaml @@ -42,7 +42,7 @@ JSON alert from Falco logs (paste the most relevant lines): ``` -\### Custom rule fired +### Custom rule fired Falco log line showing your custom rule: @@ -52,15 +52,15 @@ Falco log line showing your custom rule: ``` -\### Tuning consideration +### Tuning consideration Using the exceptions: block is the preferred tuning approach for specific known-good processes (like logging frameworks) because it allows explicitly defining allowed behavior without cluttering the main condition. If the list of exceptions grows too large or is too specific to a single process name, using and not proc.name=... directly in the condition might be simpler, but exceptions are more readable and maintainable for complex rules. -\## Task 2: Conftest Policy-as-Code +## Task 2: Conftest Policy-as-Code -\### My policy file (labs/lab9/policies/extra/hardening.rego) +### My policy file (labs/lab9/policies/extra/hardening.rego) ```rego @@ -112,7 +112,7 @@ deny contains msg if { ``` -\### Compliant manifest passes (juice-hardened.yaml) +### Compliant manifest passes (juice-hardened.yaml) ```text @@ -120,7 +120,7 @@ deny contains msg if { ``` -\### Non-compliant manifest fails (juice-unhardened.yaml) +### Non-compliant manifest fails (juice-unhardened.yaml) ```text @@ -134,7 +134,7 @@ FAIL - /project/labs/lab9/manifests/k8s/juice-unhardened.yaml - main - Container ``` -\### Compose policy generalizes (shipped compose-security.rego) +### Compose policy generalizes (shipped compose-security.rego) ```text @@ -156,15 +156,15 @@ FAIL - /project/labs/lab9/results/bad-compose.yml - compose.security - services ``` -\### Why CI-time vs admission-time +### Why CI-time vs admission-time Running Conftest at CI-time provides immediate feedback to developers during PR review, preventing insecure manifests from ever being merged. Admission-time acts as a defense-in-depth backstop at the cluster API server (kubectl apply), catching any manifests that bypassed CI (e.g., manual kubectl commands), ensuring no non-compliant resources run in production. -\## Bonus: Cryptominer Detection Rule +## Bonus: Cryptominer Detection Rule -\### Rule +### Rule ```yaml @@ -184,7 +184,7 @@ Running Conftest at CI-time provides immediate feedback to developers during PR ``` -\### Triggered alert +### Triggered alert ```json @@ -192,7 +192,7 @@ Running Conftest at CI-time provides immediate feedback to developers during PR ``` -\### Reflection +### Reflection I used the fd.rport (remote port) matching known mining pool ports (like 3333) as the network indicator. This misses obfuscated mining over HTTPS (port 443) or DNS-based mining, which would require payload inspection or DNS query analysis. Integrating this with the Lecture 9 SLA matrix, this rule provides high-signal runtime detection but requires tuning to avoid false positives from legitimate tools using those ports.