Pozn. přek.: Představujeme překlad článku staršího inženýra bezpečnosti aplikací britské společnosti ASOS.com. Tímto začíná sérii publikací věnovaných zvyšování bezpečnosti v Kubernetes pomocí seccomp. Pokud se úvod zalíbí čtenářům, budeme následovat autora a pokračovat v jeho budoucích materiálech na toto téma.

Tento článek je prvním z řady publikací o tom, jak vytvářet profily seccomp ve stylu SecDevOps, aniž bychom se uchýlili k magii a kouzlení. V první části se zaměřím na základy a vnitřní detaily implementace seccomp v Kubernetes.
Ekosystém Kubernetes nabízí dostatečné množství způsobů, jak zajistit bezpečnost a izolaci kontejnerů. Článek se věnuje Secure Computing Mode, také známému jako seccomp. Jeho podstatou je filtrování systémových volání, která mohou být kontejnery vykonávána.
Proč je to důležité? Kontejner je vlastně proces spuštěný na určitém stroji. A používá jádro společně s ostatními aplikacemi. Kdyby kontejnery mohly vykonávat jakákoli systémová volání, brzy by toho zneužily škodlivé programy, aby obešly izolaci kontejneru a ovlivnily jiné aplikace: odchytávaly informace, měnily nastavení systému atd.
Profily seccomp určují, která systémová volání by měla být povolena nebo zakázána. Prostředí spouštění kontejneru je aktivuje během jeho spuštění, aby jádro mohlo sledovat jejich vykonávání. Použití takových profilů umožňuje omezit vektor útoku a snížit škody v případě, že by nějaký program uvnitř kontejneru (to znamená vaše závislosti nebo jejich závislosti) začal dělat něco, co mu nebylo povoleno.
Zkoumáme základy
Základní profil seccomp zahrnuje tři prvky: defaultAction, architectures (nebo archMap) a syscalls:
{
"defaultAction": "SCMP_ACT_ERRNO",
"architectures": [
"SCMP_ARCH_X86_64",
"SCMP_ARCH_X86",
"SCMP_ARCH_X32"
],
"syscalls": [
{
"names": [
"arch_prctl",
"sched_yield",
"futex",
"write",
"mmap",
"exit_group",
"madvise",
"rt_sigprocmask",
"getpid",
"gettid",
"tgkill",
"rt_sigaction",
"read",
"getpgrp"
],
"action": "SCMP_ACT_ALLOW"
}
]
}()
defaultAction určuje výchozí osud jakéhokoli systémového volání, které není uvedeno v sekci syscalls. Aby bylo úkol snadnější, zaměřme se na dvě základní hodnoty, které budou použity:
-
SCMP_ACT_ERRNO— zablokuje provedení systémového volání, -
SCMP_ACT_ALLOW— povolí.
V sekci architectures jsou uvedeny cílové architektury. Je to důležité, protože samotný filtr, aplikovaný na úrovni jádra, závisí na identifikátorech systémových volání, nikoli na jejich názvech uvedených v profilu. Před použitím prostředí běhu kontejneru je mapuje na identifikátory. Smyslem je, že systémová volání mohou mít zcela odlišné ID v závislosti na architektuře systému. Například systémové volání recvfrom (používá se k získávání informací ze socketu) má ID = 64 v x64 systémech a ID = 517 v x86. můžete najít seznam všech systémových volání pro architektury x86-x64.
V sekci syscalls jsou uvedena všechna systémová volání a je uvedeno, co s nimi dělat. Například můžete vytvořit bílou listinu tím, že nastavíte defaultAction na SCMP_ACT_ERRNO, a systémovým voláním v sekci syscalls přidělíte SCMP_ACT_ALLOW. Tímto způsobem povolíte pouze volání uvedená v sekci syscalls, a zakážete všechna ostatní. Pro černou listinu byste měli změnit hodnoty defaultAction a akce na opačné.
Nyní bych rád řekl pár slov o nuancích, které nejsou tak zřejmé. Všimněte si, že níže uvedená doporučení vycházejí z toho, že rozvíjíte řadu obchodních aplikací v Kubernetes a je pro vás důležité, aby pracovaly s co nejnižšími privilegiemi.
1. AllowPrivilegeEscalation=false
V securityContext kontejner má parametr AllowPrivilegeEscalation. Pokud je nastaven na false, kontejnery se spustí s nastaveným (on) bitem . Smysl tohoto parametru je zřejmý z názvu: neumožňuje kontejneru spouštět nové procesy s privilegii, které jsou vyšší než ty, které již má.
Vedlejším efektem tohoto parametru, nastaveného na true (výchozí hodnota), je, že runtime kontejneru aplikuje profil seccomp na samém začátku procesu spuštění. Tímto způsobem musí být všechna systémová volání, která jsou potřebná k spuštění interních procesů prostředí běhu (například nastavování identifikátorů uživatelů/skupin, odmítnutí některých capabilities), povolena v profilu.
Kontejner, který vykonává banální echo hi, bude potřebovat následující povolení:
{
"defaultAction": "SCMP_ACT_ERRNO",
"architectures": [
"SCMP_ARCH_X86_64",
"SCMP_ARCH_X86",
"SCMP_ARCH_X32"
],
"syscalls": [
{
"names": [
"arch_prctl",
"brk",
"capget",
"capset",
"chdir",
"close",
"execve",
"exit_group",
"fstat",
"fstatfs",
"futex",
"getdents64",
"getppid",
"lstat",
"mprotect",
"nanosleep",
"newfstatat",
"openat",
"prctl",
"read",
"rt_sigaction",
"statfs",
"setgid",
"setgroups",
"setuid",
"stat",
"uname",
"write"
],
"action": "SCMP_ACT_ALLOW"
}
]
}()
… místo toho:
{
"defaultAction": "SCMP_ACT_ERRNO",
"architectures": [
"SCMP_ARCH_X86_64",
"SCMP_ARCH_X86",
"SCMP_ARCH_X32"
],
"syscalls": [
{
"names": [
"arch_prctl",
"brk",
"close",
"execve",
"exit_group",
"futex",
"mprotect",
"nanosleep",
"stat",
"write"
],
"action": "SCMP_ACT_ALLOW"
}
]
}()
Ale proč je to problém? Osobně bych se vyvaroval přidání těchto systémových volání do whitelistu, pokud to není skutečně nezbytné: capset, set_tid_address, setgid, setgroups a setuid. Skutečný problém spočívá v tom, že povolením procesů, které naprosto neřídíte, vážete profily na implementaci prostředí běhu kontejnerů. Jinými slovy, můžete se jednoho dne setkat s tím, že po aktualizaci runtime prostředí kontejneru (vámi nebo, což je pravděpodobnější, poskytovatelem cloudových služeb) se kontejnery náhle přestanou spouštět.
Tip č. 1: Spouštějte kontejnery s AllowPrivilegeEscaltion=false. To zmenší velikost profilů seccomp a učiní je méně citlivými na změny prostředí běhu kontejneru.
2. Nastavení profilů seccomp na úrovni kontejneru
Profil seccomp lze nastavit na úrovni podu:
annotations:
seccomp.security.alpha.kubernetes.io/pod: "localhost/profile.json"… nebo na úrovni kontejneru:
annotations:
container.security.alpha.kubernetes.io/<container-name>: "localhost/profile.json"Mějte na paměti, že uvedená syntaxe se změní, až bude Kubernetes seccomp (tato událost se očekává v nadcházející verzi Kubernetes — 1.18 — pozn. překl.).
Málokdo ví, že Kubernetes vždy měl , kvůli kterému se profily seccomp používaly na . Prostředí provádění částečně kompenzuje tento nedostatek, avšak tento kontejner nikam nezmizí z podů, protože je používán k nastavení jejich infrastruktury.
Problém spočívá v tom, že tento kontejner se vždy spouští s AllowPrivilegeEscalation=true, což vede k problémům zmíněným v bodě 1, a nelze to změnit.
Používáním seccomp profilů na úrovni kontejneru se vyhnete této pastí a můžete vytvořit profil, který bude "zaměřen" na konkrétní kontejner. To bude nutné dělat, dokud vývojáři neopraví chybu a nová verze (možná 1.18?) nebude dostupná pro všechny zájemce.
Tip č. 2: Nastavte profily seccomp na úrovni kontejneru.
V praktickém smyslu je toto pravidlo obvykle univerzální odpovědí na otázku: "Proč můj seccomp profil funguje s docker run, ale nefunguje po nasazení v clusteru Kubernetes?".
3. Používejte runtime/default pouze v krajním případě.
V Kubernetes jsou k dispozici dvě varianty vestavěných profilů: runtime/default a docker/default. Oba jsou implementovány prostředím provádění kontejneru, nikoli Kubernetes. Proto se mohou lišit v závislosti na používaném prostředí provádění a jeho verzi.
Jinými slovy, v důsledku výměny runtime může kontejner získat přístup k jinému souboru systémových volání, které může využívat nebo ne. Většina prostředí provádění používá . Pokud si přejete tento profil používat, ujistěte se, že vám vyhovuje.
Profil docker/default je považován za zastaralý od Kubernetes 1.11, proto se vyhněte jeho použití.
Podle mého názoru je profil runtime/default skvěle vhodný pro ty účely, pro které byl vytvořen: ochranu uživatelů před riziky spojenými s prováděním příkazů docker run na jejich strojích. Pokud však mluvíme o podnikových aplikacích běžících v clusterech Kubernetes, troufám si tvrdit, že tento profil je příliš otevřený a vývojáři by se měli zaměřit na vytváření profilů pro své aplikace (nebo typy aplikací).
Tip č. 3: Vytvářejte seccomp profily pro konkrétní aplikace. Pokud to není možné, zaměřte se na profily pro typy aplikací, například vytvořte rozšířený profil, který zahrne všechny webové API aplikace v Golangu. Pouze jako krajní řešení použijte runtime/default.
V budoucích publikacích vám ukážu, jak vytvářet profily seccomp ve stylu SecDevOps, automatizovat je a testovat v pipelinech. Jinými slovy, nebudete mít žádné výmluvy, proč nepřejít na profily specifické pro konkrétní aplikace.
4. Unconfined — to NENÍ možnost
Z ukázalo se, že ve výchozím nastavení . To znamená, že pokud jej nezapnete PodSecurityPolicy, která jej povolí v klastru, všechny podů, pro které není definován profil seccomp, budou běžet v režimu seccomp=unconfined.
Práce v takovém režimu znamená, že se ztrácí celá vrstva izolace, která poskytuje ochranu klastru. Takový přístup není odborníky na bezpečnost doporučován.
Rada č. 4: Žádný kontejner v klastru by neměl běžet v režimu seccomp=unconfined, zvláště v produkčních prostředích.
5. „Režim auditu“
Tento bod není unikátní pro Kubernetes, ale přesto spadá do kategorie „co byste měli vědět ještě před začátkem“.
Je obvyklé, že vytváření profilů seccomp bylo vždy obtížným úkolem a do značné míry se zakládalo na metodě pokusu a omylu. Faktem je, že uživatelé prostě nemají možnost je ověřit v produkčních prostředích, aniž by riskovali „zhroucení“ aplikace.
Po příchodu jádra Linux 4.14 se objevila možnost spouštět části profilu v režimu auditu, zapisující do syslog informace o všech systémových voláních, ale neblokující je. Tento režim lze aktivovat pomocí parametru SCMT_ACT_LOG:
SCMP_ACT_LOG: seccomp nebude mít vliv na práci vlákna, které provádí systémové volání, pokud nespadá pod nějaké pravidlo z filtru, avšak informace o systémovém volání bude zaznamenána do protokolu.
Toto je typová strategie využití této možnosti:
- Povolit systémová volání, která jsou nezbytná.
- Zablokovat systémová volání, o kterých je známo, že nejsou potřeba.
- Informace o všech ostatních voláních zapisovat do protokolu.
Zjednodušený příklad vypadá následovně:
{
"defaultAction": "SCMP_ACT_LOG",
"architectures": [
"SCMP_ARCH_X86_64",
"SCMP_ARCH_X86",
"SCMP_ARCH_X32"
],
"syscalls": [
{
"names": [
"arch_prctl",
"sched_yield",
"futex",
"write",
"mmap",
"exit_group",
"madvise",
"rt_sigprocmask",
"getpid",
"gettid",
"tgkill",
"rt_sigaction",
"read",
"getpgrp"
],
"action": "SCMP_ACT_ALLOW"
},
{
"names": [
"add_key",
"keyctl",
"ptrace"
],
"action": "SCMP_ACT_ERRNO"
}
]
}()
Ale pamatujte, že je třeba zablokovat všechny volání, o kterých víte, že nebudou použita, a které potenciálně mohou poškodit cluster. Dobrou základnou pro sestavení seznamu je oficiální . V ní je podrobně vysvětleno, které systémové volání jsou ve výchozím profilu zablokovaná a proč.
Je tu však jeden háček. Ačkoli SCMT_ACT_LOG je podporováno jádrem Linux od konce roku 2017, do ekosystému Kubernetes se dostalo teprve relativně nedávno. Proto pro použití této metody budete potřebovat jádro Linux 4.14 a runC verze alespoň .
Tip č. 5: Profil režimu auditu pro testování v produkci lze vytvořit kombinováním černého a bílého seznamu, a všechny výjimky zapisovat do deníku.
6. Používejte bílé seznamy
Tvorba bílých seznamů vyžaduje dodatečné úsilí, protože je třeba identifikovat každé volání, které může aplikace potřebovat, nicméně tento přístup výrazně zvyšuje bezpečnost:
Důrazně se doporučuje používat přístup založený na bílých seznamech, protože je jednodušší a spolehlivější. Černý seznam bude potřeba aktualizovat pokaždé, když do něj přidáte potenciálně nebezpečné systémové volání (nebo nebezpečnou vlajku/ možnost, pokud jsou v černém seznamu). Kromě toho je často možné změnit vyjádření parametru, aniž byste změnili jeho podstatu a tím obejít omezení černého seznamu.
Pro aplikace napsané v Go jsem vyvinul speciální nástroj, který sleduje aplikaci a sbírá všechna volání provedená během provádění. Například pro následující aplikaci:
package main
import "fmt"
func main() {
fmt.Println("test")
} … spustíme gosystract takto:
go install https://github.com/pjbgf/gosystract
gosystract --template='{{- range . }}{{printf ""%s",n" .Name}}{{- end}}' application-path… a dostaneme následující výsledek:
"sched_yield",
"futex",
"write",
"mmap",
"exit_group",
"madvise",
"rt_sigprocmask",
"getpid",
"gettid",
"tgkill",
"rt_sigaction",
"read",
"getpgrp",
"arch_prctl",Zatím je to jen příklad – podrobnosti o nástrojích budou následovat.
Tip č. 6: Povolené by měly být pouze ty volání, které skutečně potřebujete, a všechna ostatní zablokujte.
7. Položte správné základy (nebo se připravte na nečekané chování)
Jádro bude sledovat dodržování profilu bez ohledu na to, co v něm máte. I když to není přesně to, co byste si přáli. Například, pokud zablokujete přístup k voláním jako exit nebo exit_group, kontejner nebude schopný správně ukončit svou činnost a dokonce i jednoduchý příkaz jako je echo hi na neurčito. V důsledku toho získáte vysoké zatížení CPU v clusteru:

V takových případech může pomoci nástroj strace – ukáže, v čem může být problém:

sudo strace -c -p 9331
Ujistěte se, že profily obsahují všechny systémové volání potřebné aplikaci během jejího provozu.
Tip č. 7: Věnujte pozornost detailům a zkontrolujte, že všechny potřebné systémové volání jsou zařazeny na whitelist.
Tímto končí první část cyklu článků o použití seccomp v Kubernetes v duchu SecDevOps. V dalších částech si povíme, proč je to důležité a jak proces automatizovat.
P.S. od překladatele
Přečtěte si také náš blog:
- «»;
- «»;
- «»;
- «».
Zdroj: habr.com
