Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions .devcontainer/05-lex-imperfecta_03-expert/devcontainer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"name": "⚖️ Adventure 05 | 🔴 Expert (Quis Custodiet)",
"image": "mcr.microsoft.com/devcontainers/base:bullseye",
"workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}/adventures/05-lex-imperfecta/expert",
"features": {
"ghcr.io/devcontainers/features/docker-in-docker:2": {}
},
"postCreateCommand": "bash /workspaces/${localWorkspaceFolderBasename}/.devcontainer/05-lex-imperfecta_03-expert/post-create.sh",
"postStartCommand": "bash /workspaces/${localWorkspaceFolderBasename}/.devcontainer/05-lex-imperfecta_03-expert/post-start.sh",
"customizations": {
"codespaces": {
"openFiles": [
"falco-rules.yaml",
"manifests/policies/no-privileged-containers.yaml"
],
"permissions": {
"codespaces": "write"
}
}
},
"forwardPorts": [30110, 30111],
"portsAttributes": {
"30110": {
"label": "Policy Reporter",
"onAutoForward": "notify"
},
"30111": {
"label": "Falcosidekick UI",
"onAutoForward": "notify"
}
},
"otherPortsAttributes": {
"onAutoForward": "ignore"
}
}
22 changes: 22 additions & 0 deletions .devcontainer/05-lex-imperfecta_03-expert/post-create.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#!/usr/bin/env bash
set -e

REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"

# shellcheck disable=SC1091
source "$REPO_ROOT/lib/scripts/tracker.sh"
set_tracking_context "lex-imperfecta" "expert" "05" "06" "2026"
track_container_created

"$REPO_ROOT/lib/shared/init.sh" --version v0.17.0
"$REPO_ROOT/lib/kubernetes/init.sh" \
--kind-version v0.32.0 \
--kubectl-version v1.36.1 \
--kubens-version v0.11.0 \
--k9s-version v0.50.18 \
--helm-version v4.2.0
"$REPO_ROOT/lib/kyverno/init.sh" --version 3.8.1 --cli-version v1.18.1
"$REPO_ROOT/lib/policy-reporter/init.sh" --version 3.7.4
"$REPO_ROOT/lib/falco/init.sh" \
--falco-version 9.0.0 \
--falcosidekick-version 0.13.1
87 changes: 87 additions & 0 deletions .devcontainer/05-lex-imperfecta_03-expert/post-start.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
#!/usr/bin/env bash
set -e

REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
CHALLENGE_DIR="$REPO_ROOT/adventures/05-lex-imperfecta/expert"

echo "✨ Starting Lex Imperfecta - Expert Level"

echo "🏛️ Creating provinces..."
kubectl apply -f - <<'EOF'
apiVersion: v1
kind: Namespace
metadata:
name: gallia
labels:
republic.rome/realm: province
---
apiVersion: v1
kind: Namespace
metadata:
name: hispania
labels:
republic.rome/realm: province
---
apiVersion: v1
kind: Namespace
metadata:
name: aegyptus
labels:
republic.rome/realm: province
---
apiVersion: v1
kind: Namespace
metadata:
name: britannia
labels:
republic.rome/realm: province
---
apiVersion: v1
kind: Namespace
metadata:
name: castra
labels:
republic.rome/realm: infra
EOF

echo "📜 Deploying census archive..."
kubectl apply -f "$CHALLENGE_DIR/manifests/secrets/"

echo "⚖️ Applying policies..."
kubectl apply -f "$CHALLENGE_DIR/manifests/policies/"

echo "📋 Applying exceptions..."
kubectl apply -f "$CHALLENGE_DIR/manifests/exceptions/"

echo "🦅 Loading Falco rules..."
helm upgrade falco falcosecurity/falco \
--namespace falco \
--reuse-values \
--set-file 'customRules.praetorian-guard\.yaml='"$CHALLENGE_DIR/falco-rules.yaml" \
--wait > /dev/null

echo "🧹 Clearing Falcosidekick event history..."
kubectl exec -n falco falcosidekick-ui-redis-0 -- redis-cli FLUSHALL > /dev/null
kubectl rollout restart deployment/falcosidekick-ui -n falco > /dev/null
kubectl rollout status deployment/falcosidekick-ui -n falco --timeout=60s > /dev/null

echo "🏟️ Deploying workloads..."
# The intruder (speculator) is among these. Some workloads may be blocked
# by a misconfigured exception — open Falcosidekick UI at http://localhost:30111
# and Policy Reporter at http://localhost:30110 to begin your investigation.
kubectl apply -f "$CHALLENGE_DIR/manifests/workloads/" 2>&1 || true

# shellcheck disable=SC1091
source "$REPO_ROOT/lib/scripts/tracker.sh"
set_tracking_context "lex-imperfecta" "expert" "05" "06" "2026"
track_container_initialized

echo ""
echo "🏛️ The estate is deployed."
echo ""
echo " Policy Reporter: http://localhost:30110"
echo " Falcosidekick UI: http://localhost:30111"
echo ""
echo " The intruder is already in the estate. The Guard sees nothing."
echo " Run 'make verify' to check your progress."
echo ""
168 changes: 168 additions & 0 deletions adventures/05-lex-imperfecta/docs/expert.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
level: expert
emoji: "🔴"
title: "Quis Custodiet"
devcontainer: lex-imperfecta_03-expert
community_url: "" # TODO: add community thread URL once the adventure is live

summary: "An intruder is already inside the Republic, and the watchmen cannot see it. Fix the Praetorian Guard's broken detection rule, close the admission gap that let the intruder slip through, and seal the census archive against unauthorized access."

audience: >-
Security engineers and platform engineers who want to explore the boundary between admission control
and runtime security. Completing the Intermediate level first is helpful but not required. You should
be comfortable reading Kyverno ValidatingPolicies and CEL expressions. No prior Falco experience
required; the level introduces it.

backstory:
- >-
The Republic's defences have always rested on the law: block the wrong workloads at the gate, and
nothing bad gets in. But the Senate's Praetorian Guard was built for a different threat: not just the
workload that *should* have been stopped, but the one that slips through and acts badly at runtime.
The Guard watches the provinces through Falco, but tonight, the watchtower is dark. Someone broke
the rule that should fire when the census archive is touched. The Guard sees nothing.
- >-
And while the Guard slept, an intruder crept in. It declared valid labels, passed the census, and
presented itself as a loyal citizen of the Republic: *speculator*, a quiet auxiliary in Gallia. Its
papers were in order. Its power was not. Once inside, it reached straight for the census archive:
the imperial rolls of every citizen, sealed records it had no right to touch. It reads them on a
loop and tries to send them out of the Republic.
- >-
By the time the auditors noticed, three cracks had opened. The Guard's rule does not fire on the
actual breach. The gate has a gap the law overlooked. And the archive's seal is not quite what it
appears. All three must be closed before the intruder escapes with the census rolls.

objective:
- >-
**The Praetorian Guard awake**: Falco fires an alert every time an unauthorized process reads the
census archive, with live alerts streaming into the Falcosidekick UI
- >-
**The gate closed**: the intruder is denied re-admission — the policy that kept privileged
containers out now covers every path to unchecked host power
- >-
**The archive sealed**: the census-archive secret is inaccessible to any workload that does not
bear the Archivist role
- >-
**The empire-wide laws holding**: all intermediate-level checks still green across every province

what_you_learn:
- >-
How [Falco rules](https://falco.org/docs/reference/rules/) are structured: conditions, syscall
events, and kernel-level fields; and how to write a detection rule that captures a specific runtime
behaviour
- >-
Why `privileged: false` is not the same as "safe": how
[Linux capabilities](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-capabilities-for-a-container)
grant containers host-level access without the privileged flag
- >-
How to use [`spec.variables`](https://kyverno.io/docs/policy-types/validating-policy/) in a
`ValidatingPolicy` to define reusable expressions and keep validation logic readable
- >-
How pod volumes reference secrets — and why a volume's name and the secret it actually mounts are
two different fields in the pod spec
- >-
How [Falcosidekick](https://github.com/falcosecurity/falcosidekick) aggregates live Falco alerts
and how to use its UI to watch a runtime incident unfold in real time

architecture:
- >-
The estate inherits the full intermediate topology: four province namespaces (`gallia`, `hispania`,
`britannia`, `aegyptus`) and one infra namespace (`castra`), each labelled as before. Alongside the
Kyverno stack and Policy Reporter, the cluster now runs **Falco** (eBPF-based, as a DaemonSet) and
**Falcosidekick** with its UI at port
**30111**. At startup, the intruder pod `speculator` is already running in `gallia`, quietly reading
the census archive: imperial rolls that only workloads bearing the `republic.rome/role: archivist`
label are permitted to access.
- >-
Your working directory is the challenge root. `manifests/secrets/` and `manifests/workloads/` are
already in place — they define the estate and the intruder, and need no changes. Everything else is
yours to investigate and fix.

toolbox:
- name: kubectl
url: "https://kubernetes.io/docs/reference/kubectl/"
description: Apply and inspect cluster resources, check pod status and security contexts
- name: k9s
url: "https://k9scli.io/"
description: Explore cluster resources, pod logs, and policy reports in a terminal UI
- name: kyverno
url: "https://kyverno.io/docs/kyverno-cli/"
description: Test a policy against a resource locally before applying it to the cluster

services:
- name: Falcosidekick UI
port: 30111
description: Live stream of Falco alerts, your primary instrument for watching the intruder get caught in real time
- name: Policy Reporter
port: 30110
description: Audit the policy estate. Check admission violations and confirm the intermediate-level checks are still green

how_to_play:
- id: survey
title: "Survey the Scene"
content: |
When your Codespace opens, the intruder is already running. Open the **Falcosidekick UI** at
the forwarded port **30111**. It should be streaming alerts about census archive reads,
but it is silent. That silence is your first clue that something is wrong with the Guard.

Start by getting oriented:

```bash
# Can you find the intruder?
kubectl get pods -A

# Read the Falco rule that should be firing
cat falco-rules.yaml

# Which policies are in force?
kubectl get validatingpolicies
kubectl get namespacedvalidatingpolicies -A
```

Open **Policy Reporter** at port **30110** as well. The intermediate estate should look clean —
the intruder left no trace at admission. That is part of the problem.

- id: act1
title: "Act 1: Wake the Praetorian Guard"
content: |
The Falco rule in `falco-rules.yaml` has a defect — find the break, fix it, and run `make apply`;
alerts streaming into the **Falcosidekick UI at port 30111** are your signal. The
[Falco condition fields reference](https://falco.org/docs/reference/rules/supported-fields/)
documents every available field.

- id: act2
title: "Act 2: Close the Gate"
content: |
The intruder passed admission — find the policy gap that let it through, close it, and run
`make apply`; re-admission denied and the **Falcosidekick UI** going quiet confirm Act 2 is done.
The existing policy already uses [`spec.variables`](https://kyverno.io/docs/policy-types/validating-policy/)
to share expressions across validations — a pattern worth exploring.

- id: act3
title: "Act 3: Seal the Archive"
content: |
Open `manifests/policies/` — something is missing; the other policies show the structure, write
what's needed, then run `make apply`.

**Going further:** even with the archive sealed at admission, any workload admitted with the
Archivist role can read the secret. Kubernetes RBAC can restrict which service accounts may
`get` a secret at the API level, a complementary layer that admission control alone cannot
provide.

helpful_links:
- title: Falco Rules Reference
url: "https://falco.org/docs/reference/rules/"
description: "The anatomy of a Falco rule: condition, output, priority, tags, and how rules are evaluated"
- title: Falco Condition Fields
url: "https://falco.org/docs/reference/rules/supported-fields/"
description: "Every field available in Falco rule conditions: syscall events, file descriptors, process info, and Kubernetes metadata"
- title: Linux Capabilities in Kubernetes
url: "https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-capabilities-for-a-container"
description: How Linux capabilities work and how to configure or restrict them in a pod's security context
- title: Kyverno ValidatingPolicy
url: "https://kyverno.io/docs/policy-types/validating-policy/"
description: Reference docs for ValidatingPolicy, including spec.variables for composing reusable CEL expressions
- title: CEL Validation Expressions
url: "https://kubernetes.io/docs/reference/using-api/cel/"
description: "How CEL expressions work in Kubernetes admission: operators, optional chaining, and collection functions"
- title: Falcosidekick
url: "https://github.com/falcosecurity/falcosidekick"
description: The Falco alert aggregator that routes Falco events to sinks including the Falcosidekick UI
64 changes: 64 additions & 0 deletions adventures/05-lex-imperfecta/expert/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
.PHONY: help apply verify

help:
@echo "Lex Imperfecta - Expert: Available Commands:"
@echo " make apply Reload Falco rules, reset workloads, re-apply policies and exceptions"
@echo " make verify Run the verification script"

apply:
@echo "Reloading Falco rules..."
@helm upgrade falco falcosecurity/falco \
--namespace falco \
--reuse-values \
--set-file 'customRules.praetorian-guard\.yaml=./falco-rules.yaml' \
--wait > /dev/null
@echo " praetorian-guard.yaml"
@echo ""
@echo "Applying Policies:"; \
for f in manifests/policies/*.yaml; do \
name=$$(grep '^ name:' "$$f" | head -1 | awk '{print $$2}'); \
out=$$(kubectl apply -f "$$f" 2>&1); rc=$$?; \
if [ $$rc -ne 0 ]; then \
echo ""; \
echo "$$out"; \
exit 1; \
fi; \
echo " $$name"; \
done; \
echo ""
@echo "Applying Exceptions:"; \
for f in manifests/exceptions/*.yaml; do \
name=$$(grep '^ name:' "$$f" | head -1 | awk '{print $$2}'); \
out=$$(kubectl apply -f "$$f" 2>&1); rc=$$?; \
if [ $$rc -ne 0 ]; then \
echo ""; \
echo "$$out"; \
exit 1; \
fi; \
echo " $$name"; \
done; \
echo ""
@echo "Applying Secrets:"; \
for f in manifests/secrets/*.yaml; do \
name=$$(grep '^ name:' "$$f" | head -1 | awk '{print $$2}'); \
out=$$(kubectl apply -f "$$f" 2>&1); rc=$$?; \
if [ $$rc -ne 0 ]; then \
echo ""; \
echo "$$out"; \
exit 1; \
fi; \
echo " $$name"; \
done; \
echo ""
@echo "Deploying Workloads..."
@for ns in gallia hispania aegyptus britannia castra; do \
kubectl delete pods --all -n $$ns --ignore-not-found --grace-period=0 --force \
> /dev/null 2>&1 & \
done; \
wait
@kubectl apply -f manifests/workloads/ 2>&1 || true
@echo ""
@echo "Blocked workloads above mean a policy is enforcing. Run 'make verify' to check your progress."

verify:
@./verify.sh
Loading
Loading