K8s Security Lab Report

📅 2026-03-11 ☁️ GKE Standard · europe-west1-b 🖥️ e2-standard-2 · Ubuntu Containerd ☁️ GCP Free Trial
🎯

Kontekst i cel

Celem tego labu było praktyczne przetestowanie open-source'owych narzędzi do monitorowania bezpieczeństwa Kubernetes — sprawdzenie jak działają, co wykrywają i gdzie są granice każdego z nich. Postawiliśmy celowo dziurawy klaster i zobaczyliśmy co narzędzia faktycznie potrafią wychwycić — zarówno na poziomie statycznej konfiguracji, jak i w czasie rzeczywistym.

Warstwa 1 — Statyczna

Misconfiguracje i CVE wykrywane przed uruchomieniem: privileged containers, HostPath mount, wildcard RBAC, sekrety w env vars. Narzędzia: Trivy Operator, Kubescape.

Warstwa 2 — Runtime

Podejrzane zachowania w czasie rzeczywistym: odczyt /etc/shadow, uruchomienie shella w kontenerze, skanowanie sieci. Narzędzia: Falco (detekcja), Tetragon (enforcement + SIGKILL).

Środowisko testowe

☁️ GKE Standard — nie Autopilot (zbyt restrykcyjny) 🖥️ e2-standard-2 — 2 vCPU, 8 GB RAM 🐧 UBUNTU_CONTAINERD — wymagane przez Tetragon 📍 europe-west1-b 💾 50 GB SSD per node

Do tej konfiguracji doszliśmy iteracyjnie — zaczęliśmy od e2-small, potem e2-medium, ale cztery operatory jednocześnie generują znacznie więcej resource requests niż się wydaje i pody zostawały w stanie Pending. Finalnie e2-standard-2 + Ubuntu node pool (wymagany przez Tetragon ze względu na bug BTF w kernelu GKE 6.12+).

Trivy Operator
12
HIGH findings (misconfig)
Kubescape
80
/ 100 compliance score
Falco
4
reguły wyzwolone
Tetragon
SIGKILL enforcement
🏗️

Instalacja krok po kroku

RUNNING
Krok Komenda / akcja Status
Włączenie GKE API gcloud services enable container.googleapis.com OK
Tworzenie klastra gcloud container clusters create kubescape-vulnerable-lab --zone=europe-west1-b --machine-type=e2-standard-2 OK
Node pool Ubuntu --image-type=UBUNTU_CONTAINERD (wymagane dla Tetragon) OK
Podatne workloady kubectl apply -f vulnerable-workloads.yaml OK
kubectl + helm Instalacja przez gcloud components install kubectl + brew install helm OK

Podatne workloady:

DeploymentPodatnośćKontrolka
privileged-containerPrivileged containerC-0017
hostpath-mountHostPath mount na /C-0045
hardcoded-secretsSekrety w env vars (AWS, DB, GitHub token)C-0012
run-as-rootrunAsUser: 0, allowPrivilegeEscalationC-0044
wildcard-clusterroleRBAC */*/*C-0034
🔍

Trivy Operator — CVE & Misconfiguracje

12 HIGH

Trivy Operator to pierwsza linia obrony — skanuje wszystko co jest w klastrze zanim cokolwiek złego się wydarzy. Po zainstalowaniu automatycznie wykrywa każdy nowy workload i uruchamia skan. Wyniki lądują jako CRDs w klastrze, więc można je odpytywać przez kubectl jak każdy inny zasób. Skonfigurowaliśmy go żeby skupiał się tylko na HIGH i CRITICAL — żeby nie tonąć w szumie przy pierwszym przeglądzie.

# Instalacja
helm repo add aqua https://aquasecurity.github.io/helm-charts/
helm install trivy-operator aqua/trivy-operator \
  --namespace trivy-system --create-namespace \
  --set trivy.ignoreUnfixed=true \
  --set "trivy.severity=HIGH\,CRITICAL" \
  --set operator.scanJobsConcurrentLimit=1
privileged-container
HIGHPrivileged container (AVD-KSV-0017)
HIGHDefault security context (AVD-KSV-0118)
HIGHRoot filesystem not read-only (AVD-KSV-0014)
hostpath-mount
HIGHDisallowed volumes — HostPath (AVD-KSV-0121)
HIGHDefault security context (AVD-KSV-0118)
HIGHRoot filesystem not read-only (AVD-KSV-0014)
hardcoded-secrets
HIGHDefault security context (AVD-KSV-0118)
HIGHRoot filesystem not read-only (AVD-KSV-0014)
run-as-root
HIGHDefault security context (AVD-KSV-0118)
HIGHRoot filesystem not read-only (AVD-KSV-0014)
# Eksport wyników do CSV
kubectl get configauditreports -A -o json \
  | jq -r '.items[] | .metadata.name as $name |
    .report.checks[] | select(.success==false) |
    [$name, .severity, .checkID, .title] | @csv' \
  > trivy-audit.csv
🛡️

Kubescape — Compliance & Posture

Score: 80/100

Kubescape patrzy na klaster z innej perspektywy niż Trivy — zamiast CVE w obrazach, sprawdza czy konfiguracja klastra jest zgodna z frameworkami bezpieczeństwa: CIS Benchmark, NSA-CISA, MITRE ATT&CK. Daje "duży obraz" — jaki procent zasobów spełnia wymagania i które konkretnie kontrolki są naruszone. Operator skanuje cyklicznie (ustawiony na 02:00), do testów uruchamialiśmy go ręcznie przez job.

Overall Compliance Score 80 / 100
0 Critical
73 High
177 Medium
Frameworks: CIS, NSA-CISA, MITRE ATT&CK
SeverityKontrolkaOpisWorkload
HIGHC-0017Privileged containerprivileged-container
HIGHC-0045HostPath mounthostpath-mount
HIGHC-0012Misplaced secrets (env vars)hardcoded-secrets
HIGHC-0034Wildcard RBAC */*/*wildcard-clusterrole
HIGHC-0009Brak CPU/memory limitswszystkie
HIGHCIS-5.7.3Brak security context (0% pass rate)wszystkie
MEDIUMC-0044Non-root containersrun-as-root
MEDIUMIngress/Egress not blocked (0% pass rate)wszystkie
# Ręczne uruchomienie skanu
kubectl create job -n kubescape \
  --from=cronjob/kubescape-scheduler manual-scan-$(date +%s)
👁️

Falco — Runtime Detekcja

RUNNING

Trivy i Kubescape działają statycznie — mówią co jest źle skonfigurowane. Falco wchodzi w grę gdy klaster już działa i trzeba łapać podejrzane zachowania w czasie rzeczywistym. Działa przez eBPF, podpina się do kernela i obserwuje syscalle ze wszystkich kontenerów bez modyfikowania obrazów. Tylko wykrywa i alarmuje — nie blokuje, co pozwala włączyć go w trybie obserwacji bez ryzyka dla produkcji. Użyliśmy Falcosidekick do rozsyłania alertów i jego wbudowanego WebUI. Problem z instalacją: legacy eBPF driver nie miał prebuilt binarki dla kernela GKE 6.12.55+ — fix to przełączenie na modern_ebpf.

# Instalacja z modern eBPF (wymagane na GKE kernel 6.12.55+)
helm install falco falcosecurity/falco \
  --namespace falco --create-namespace \
  --set falco.json_output=true \
  --set falcosidekick.enabled=true \
  --set falcosidekick.webui.enabled=true \
  --set driver.kind=modern_ebpf
WARNING Read sensitive file untrusted privileged-container · cat /etc/shadow ×2
WARNING Search Private Keys or Passwords privileged-container · find / -name id_rsa ×1
NOTICE Contact K8S API Server From Container kubescape / node-agent ×17
NOTICE Packet socket created in container kubescape / node-agent ×4

Falcosidekick WebUI:

kubectl port-forward -n falco svc/falco-falcosidekick-ui 2802:2802
# http://localhost:2802 · login: admin / admin
⚔️

Tetragon — Runtime Enforcement

SIGKILL ✓

Tetragon idzie krok dalej niż Falco — zamiast tylko alarmować, potrafi aktywnie blokować przez SIGKILL zanim cokolwiek złego się stanie. Też eBPF, overhead poniżej 1% CPU. Polityki (TracingPolicy) można targetować po syscallu, namespace, labelu poda. Poważniejszy problem z instalacją niż Falco: crashował na domyślnym nodzie GKE (COS) z błędem BTF dla modułu nls_cp437 — bug obecny we wszystkich wersjach 1.4–1.6. Jedynym obejściem było użycie node poola z UBUNTU_CONTAINERD.

# Wymaga UBUNTU_CONTAINERD node pool (bug BTF na COS kernel 6.12.55+)
helm install tetragon cilium/tetragon --namespace kube-system

TracingPolicy — blokowanie shell w podach z labelem app: privileged-container:

apiVersion: cilium.io/v1alpha1
kind: TracingPolicy
metadata:
  name: block-shell-in-default
spec:
  podSelector:
    matchLabels:
      app: privileged-container
  kprobes:
  - call: "sys_execve"
    syscall: true
    selectors:
    - matchArgs:
      - operator: "Postfix"
        values: ["/bin/sh", "/bin/bash"]
      matchActions:
      - action: Sigkill
🚫
privileged-container
ZABLOKOWANY
exit code 137 (SIGKILL)
hardcoded-secrets
DOZWOLONY
exit code 0
⚠️

Napotkane problemy

Narzędzia są zasobożerne — bardziej niż się wydaje

Każde z czterech narzędzi działa jako osobny operator lub DaemonSet i definiuje własne resource requests. Gdy uruchomimy je wszystkie na raz, suma tych requestów szybko przekracza pojemność małego noda — scheduler K8s zaczyna zostawiać nowe pody w stanie Pending. Nie chodzi o faktyczne zużycie CPU czy RAM w danej chwili, ale o zarezerwowaną pojemność.

Najbardziej zasobożerny moment w całym labie to była chwila, gdy działały jednocześnie: Trivy Operator skanujący wszystkie 5 deploymentów — każdy skan to osobny tymczasowy Job, w sumie do 5 równoległych podów ściągających obraz Trivy i analizujących manifesty; Kubescape Operator wykonujący pierwsze, pełne skanowanie klastra (inicjalny skan jest zawsze najbardziej intensywny) — w tym momencie działały jednocześnie: główny operator, node-agent DaemonSet, kollector, gateway i synchronizer; Falco jako DaemonSet stale monitorujący syscalle przez eBPF; Tetragon jako DaemonSet z kernel-level eBPF hooks.

Suma resource.requests przekroczyła allocatable CPU i RAM na e2-small i e2-medium. Dopiero e2-standard-2 (2 vCPU, ~6.5 GB allocatable) dał wystarczający margines. Przy planowaniu produkcyjnym warto zarezerwować dedykowane nody dla narzędzi monitorujących i nie mieszać ich z workloadami aplikacyjnymi — szczególnie podczas inicjalnych skanów.

Tetragon wymaga Ubuntu — nie działa na domyślnym obrazie GKE

GKE domyślnie używa Container-Optimized OS (COS). Tetragon na tym systemie nie uruchamia się poprawnie ze względu na problem z metadanymi BTF modułu kernela nls_cp437 — znany bug obecny w Tetragon 1.4–1.6, niezależny od wersji GKE. Obejście: node pool z obrazem UBUNTU_CONTAINERD, gdzie kernel ma kompletne BTF i Tetragon startuje bez problemów.

Falco wymaga nowszego sterownika eBPF na GKE

Domyślny driver eBPF Falco (legacy) wymaga prebuilt binarki pod konkretną wersję kernela. GKE regularnie aktualizuje kernele i bywa że dla najnowszych prebuilt binarki jeszcze nie ma. Rozwiązanie: modern_ebpf — sterownik wbudowany bezpośrednio w kernel (Linux 5.13+), który nie wymaga żadnych zewnętrznych plików i działa na wszystkich aktualnych wersjach GKE.

💡

Wnioski

Co działało dobrze

Wszystkie cztery narzędzia wykryły to co miały wykryć. Celowo złe konfiguracje pojawiły się w wynikach Trivy i Kubescape bez dodatkowej konfiguracji. Falco od razu łapał podejrzane syscalle. Tetragon po wgraniu TracingPolicy skutecznie blokował shell (exit 137), podczas gdy inne pody działały normalnie.

Szczególnie warte uwagi: Falco wychwycił aktywność Kubescape node-agenta (połączenia do K8s API Server) jako podejrzaną. W produkcji trzeba skonfigurować białe listy dla własnych operatorów.

Różnice między narzędziami

Trivy i Kubescape pokrywają podobny obszar (statyczna analiza), ale z różnych kątów. Trivy jest bardziej szczegółowy na poziomie pojedynczego workloadu i CVE. Kubescape daje lepszy obraz compliance całego klastra i mapowanie na frameworki (CIS, MITRE) — przydatne przy audytach.

Falco i Tetragon też się uzupełniają: Falco jest lepszy do szerokiej detekcji i integracji z zewnętrznymi systemami (Splunk, Slack), Tetragon do precyzyjnego enforcement na poziomie konkretnych procesów i namespaces.

Co sprawiło trudność

Główne wyzwania były infrastrukturalne, nie związane z samymi narzędziami:

  • Zasoby nodów — cztery operatory jednocześnie na e2-small/e2-medium nie działają. Każdy definiuje własne resource requests i scheduler nie ma gdzie postawić podów.
  • Kompatybilność kernela — GKE 6.12+ ma bug BTF dla modułu nls_cp437, blokuje Tetragon na COS. Ubuntu node pool rozwiązuje problem.
  • Sekwencja instalacji — instalowanie wszystkiego naraz powoduje resource contention. Lepiej wdrażać etapami.

Dalsze kroki

To co przetestowaliśmy to punkt startowy — jeden klaster, cztery narzędzia, ręczna weryfikacja. W środowisku kilkudziesięciu lub kilkuset klastrów potrzebne są dwa niezależne pipeline'y: jeden do agregacji podatności CVE, drugi do routingu runtime eventów do SIEMa.

Agregacja CVE — jeden raport z całego środowiska

Trivy Operator tworzy VulnerabilityReport CRDs lokalnie w każdym klastrze. Żeby zebrać je w jedno miejsce i wygenerować CSV dla całego środowiska, potrzebna jest warstwa agregacyjna:

  • DefectDojo (open source, rekomendowane) — platforma do zarządzania podatnościami z natywną integracją Trivy. Każdy klaster ma CronJob, który pushuje VulnerabilityReports (format SARIF/JSON) do centralnego DefectDojo przez REST API. Eksport CSV, filtrowanie po severity/klastrze/namespace, śledzenie trendu w czasie.
  • Prometheus + Thanos/Mimir — Trivy eksportuje metryki (liczba CVE per severity). Thanos agreguje z wielu klastrów w jeden widok. Dobre do dashboardów i alertów, ale nie zawiera CVE ID ani nazw pakietów — nie zastępuje pełnego raportu.
  • Skrypt przez kubeconfig contexts — dla mniejszej skali: iteracja po kontekstach, kubectl get vulnerabilityreports -A -o json, agregacja do jednego pliku. Szybkie, ale nie skaluje się i wymaga dostępu sieciowego do każdego API Servera.
  • ARMO Platform / Wiz / Lacework — komercyjne, multi-cluster natywnie, eksport CSV out-of-the-box. Opcja gdy nie chcemy budować własnego stacku.

Runtime eventy do SIEMa

Falcosidekick obsługuje dziesiątki outputów out-of-the-box:

  • Splunk — natywna integracja przez HEC (HTTP Event Collector). Każdy alert jako JSON event do wybranego index. Konfiguracja: falcosidekick.config.splunk.hostport + token.
  • Microsoft Sentinel — Falcosidekick → syslog → Azure Monitor Agent → Sentinel, lub przez webhook → Logic Apps.
  • QRadar — Falcosidekick obsługuje syslog output; QRadar przyjmuje eventy przez syslog listener.

Przy setkach klastrów: centralny Falcosidekick jako deployment poza klastrami aplikacyjnymi — każdy Falco wysyła alerty do jednego miejsca zamiast lokalnego sidekicka. Konieczny enrichment metadanych (cluster name, environment) w konfiguracji Falcosidekick, inaczej w SIEMie nie będzie wiadomo z którego klastra przyszedł event. Tetragon JSON logs przez fluentd/fluent-bit jako dodatkowe źródło zdarzeń na poziomie kernela.

🗑️

Cleanup

# Usunięcie klastra (ważne — koszty ~$0.07/h)
gcloud container clusters delete kubescape-vulnerable-lab \
  --zone=europe-west1-b