Skip to content

Commit 35a33bd

Browse files
authored
Merge pull request #9 from RII6/feature/lab9
feat(lab9): falco custom rules + conftest hardening policies
2 parents 4b2a562 + e4bc91f commit 35a33bd

3 files changed

Lines changed: 195 additions & 0 deletions

File tree

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# labs/lab9/falco/rules/custom-rules.yaml
2+
- rule: "Write to /tmp by container"
3+
desc: "Detects writes to /tmp inside any container"
4+
condition: open_write and container.id != host and fd.name startswith /tmp/
5+
output: "Write to /tmp by container (container=%container.name user=%user.name file=%fd.name cmdline=%proc.cmdline)"
6+
priority: WARNING
7+
tags: [container, drift]
8+
9+
- rule: "Possible Cryptominer Activity"
10+
desc: "Detects container connecting to common mining-pool ports or running known miner processes"
11+
condition: >
12+
container.id != host and
13+
(
14+
(evt.type = connect and fd.sport in (3333, 4444, 5555, 7777, 14444, 19999, 45700)) or
15+
(proc.name in (xmrig, ethminer, cgminer, t-rex, claymore))
16+
)
17+
output: "Possible Cryptominer Activity (container=%container.name proc=%proc.name target=%fd.name)"
18+
priority: CRITICAL
19+
tags: [container, mitre_execution, mitre_command_and_control]
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package k8s.security
2+
3+
import rego.v1
4+
5+
# 1. runAsNonRoot must be true
6+
deny contains msg if {
7+
pod := input.spec.template.spec
8+
container := pod.containers[_]
9+
not has_run_as_non_root(pod, container)
10+
msg := sprintf("Container '%v' must set runAsNonRoot to true", [container.name])
11+
}
12+
13+
has_run_as_non_root(pod, _) if pod.securityContext.runAsNonRoot == true
14+
has_run_as_non_root(_, container) if container.securityContext.runAsNonRoot == true
15+
16+
# 2. allowPrivilegeEscalation must be false
17+
deny contains msg if {
18+
container := input.spec.template.spec.containers[_]
19+
not has_privilege_escalation_false(container)
20+
msg := sprintf("Container '%v' must set allowPrivilegeEscalation to false", [container.name])
21+
}
22+
23+
has_privilege_escalation_false(container) if container.securityContext.allowPrivilegeEscalation == false
24+
25+
# 3. capabilities.drop must include "ALL"
26+
deny contains msg if {
27+
container := input.spec.template.spec.containers[_]
28+
not drops_all_capabilities(container)
29+
msg := sprintf("Container '%v' must drop ALL capabilities", [container.name])
30+
}
31+
32+
drops_all_capabilities(container) if "ALL" in container.securityContext.capabilities.drop

submissions/lab9.md

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
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

Comments
 (0)