Seccomp a Kubernetesben: 7 dolog, amit a kezdetektől tudnod kell

Jegyzet. ford.: Bemutatjuk figyelmükbe a brit ASOS.com cég vezető alkalmazásbiztonsági mérnökének cikkének fordítását. Ezzel elindítja a publikációk sorozatát, amelyek célja a Kubernetes biztonságának a seccomp használatával történő javítása. Ha az olvasóknak tetszik a bevezető, követjük a szerzőt, és folytatjuk a témával kapcsolatos további anyagait.

Seccomp a Kubernetesben: 7 dolog, amit a kezdetektől tudnod kell

Ez a cikk az első a bejegyzések sorában, amelyek arról szólnak, hogyan lehet seccomp profilokat létrehozni a SecDevOps szellemében, varázslat és boszorkányság igénybevétele nélkül. Az XNUMX. részben a seccomp Kubernetesben való megvalósításának alapjait és belső részleteit ismertetem.

A Kubernetes ökoszisztéma a konténerek rögzítésének és elkülönítésének sokféle módját kínálja. A cikk a biztonságos számítástechnikai módról szól, más néven seccomp. Lényege a végrehajtható rendszerhívások konténerek szerinti szűrése.

Miért fontos? A konténer csak egy folyamat, amely egy adott gépen fut. És ugyanúgy használja a kernelt, mint a többi alkalmazás. Ha a konténerek bármilyen rendszerhívást végrehajthatnának, a rosszindulatú programok hamarosan kihasználnák ezt, hogy megkerüljék a konténerek elkülönítését, és hatással legyenek más alkalmazásokra: elfogják az információkat, módosítsák a rendszerbeállításokat stb.

A seccomp profilok határozzák meg, hogy mely rendszerhívásokat kell engedélyezni vagy letiltani. A tároló futtatókörnyezete indításkor aktiválja őket, így a kernel figyelni tudja a végrehajtásukat. Az ilyen profilok használata lehetővé teszi a támadási vektor korlátozását és a károk csökkentését, ha a tárolón belüli bármely program (vagyis az Ön függőségei vagy azok függőségei) olyasmit kezd el, amit nem szabad.

Eljutni az alapokhoz

Az alap seccomp profil három elemből áll: defaultAction, architectures (vagy archMap) És 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"
        }
    ]
}

(medium-basic-seccomp.json)

defaultAction meghatározza a szakaszban nem szereplő rendszerhívások alapértelmezett sorsát syscalls. A dolgok megkönnyítése érdekében összpontosítsunk a két fő értékre, amelyet használni fogunk:

  • SCMP_ACT_ERRNO – blokkolja a rendszerhívás végrehajtását,
  • SCMP_ACT_ALLOW - lehetővé tesz.

Szakaszban architectures célarchitektúrák vannak felsorolva. Ez azért fontos, mert maga a kernel szinten alkalmazott szűrő a rendszerhívás-azonosítóktól függ, nem pedig a profilban megadott nevektől. A tároló futtatókörnyezete használat előtt egyezteti őket az azonosítókkal. Az ötlet az, hogy a rendszerhívásoknak a rendszer architektúrától függően teljesen eltérő azonosítói lehetnek. Például rendszerhívás recvfrom (az aljzatból való információ fogadására szolgál) ID = 64 x64 rendszereken és ID = 517 x86 rendszeren. Itt megtalálja az x86-x64 architektúrák összes rendszerhívásának listáját.

A szakaszban syscalls felsorolja az összes rendszerhívást, és meghatározza, hogy mit kell tenni velük. Például beállítással létrehozhat egy engedélyezőlistát defaultAction on SCMP_ACT_ERRNO, és felhívja a szekciót syscalls hozzárendelni SCMP_ACT_ALLOW. Így csak a szakaszban meghatározott hívásokat engedélyezi syscalls, és tiltsa meg az összes többit. A feketelistához módosítani kell az értékeket defaultAction és cselekvések az ellenkezőjére.

Most néhány szót kell ejtenünk a nem annyira nyilvánvaló árnyalatokról. Kérjük, vegye figyelembe, hogy az alábbi javaslatok azt feltételezik, hogy üzleti alkalmazásokat telepít a Kubernetes rendszeren, és azt szeretné, hogy azok a lehető legkevesebb jogosultsággal fussanak.

1. AllowPrivilegeEscalation=false

В securityContext a tárolónak van egy paramétere AllowPrivilegeEscalation. Ha be van szerelve false, a konténerek a következővel kezdődnek: (on) bit no_new_priv. Ennek a paraméternek a jelentése nyilvánvaló a névből: megakadályozza, hogy a konténer új folyamatokat indítson el több jogosultsággal, mint amilyennel rendelkezik.

Ennek a beállításnak a mellékhatása true (alapértelmezett) az, hogy a tároló futtatókörnyezete az indítási folyamat legelején alkalmazza a seccomp profilt. Így a profilban engedélyezni kell a belső futásidejű folyamatok futtatásához szükséges összes rendszerhívást (pl. felhasználói/csoportazonosítók beállítása, bizonyos képességek eldobása).

Egy olyan konténerbe, amely triviális dolgokat csinál echo hi, a következő engedélyekre lesz szükség:

{
    "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"
        }
    ]
}

(hi-pod-seccomp.json)

...ezek helyett:

{
    "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"
        }
    ]
}

(hi-container-seccomp.json)

De még egyszer: miért probléma ez? Én személy szerint kerülném a következő rendszerhívások engedélyezési listát (hacsak nincs rájuk valós igény): capset, set_tid_address, setgid, setgroups и setuid. Az igazi kihívás azonban az, hogy azáltal, hogy olyan folyamatokat engedélyez, amelyek felett egyáltalán nincs befolyása, profilokat köt a tároló futásidejű megvalósításához. Más szóval, egy nap azt tapasztalhatja, hogy a konténer futtatókörnyezetének frissítése után (akár Ön, akár valószínűbb, hogy a felhőszolgáltató által) a tárolók hirtelen leállnak.

1. tipp: Futtassa a konténereket AllowPrivilegeEscaltion=false. Ez csökkenti a seccomp profilok méretét, és kevésbé érzékenyek a tároló futási környezet változásaira.

2. A seccomp profilok beállítása tároló szinten

A seccomp profil a pod szintjén állítható be:

annotations:
  seccomp.security.alpha.kubernetes.io/pod: "localhost/profile.json"

...vagy tároló szinten:

annotations:
  container.security.alpha.kubernetes.io/<container-name>: "localhost/profile.json"

Kérjük, vegye figyelembe, hogy a fenti szintaxis megváltozik, amikor a Kubernetes seccomp GA lesz (ez az esemény a Kubernetes következő kiadásában várható - 1.18 - kb. ford.).

Kevesen tudják, hogy Kubernetes mindig is az volt bogárami miatt seccomp profilokat kellett alkalmazni konténer szüneteltetése. A futási környezet részben kompenzálja ezt a hiányosságot, de ez a tároló nem tűnik el a podokból, mivel az infrastruktúrájuk konfigurálására szolgál.

A probléma az, hogy ez a tároló mindig ezzel kezdődik AllowPrivilegeEscalation=true, ami az (1) bekezdésben megfogalmazott problémákhoz vezet, és ezen nem lehet változtatni.

A seccomp profilok tárolószintű használatával elkerülheti ezt a buktatót, és egy adott tárolóhoz szabott profilt hozhat létre. Ezt addig kell tenni, amíg a fejlesztők ki nem javítják a hibát és az új verzió (talán 1.18?) mindenki számára elérhetővé válik.

2. tipp: Seccomp profilok beállítása a tároló szintjén.

Gyakorlati értelemben ez a szabály általában univerzális válaszként szolgál a következő kérdésre: „Miért működik a seccomp profilom docker runde nem működik a Kubernetes-fürtbe történő telepítés után?

3. Csak végső megoldásként használja a futásidejű/alapértelmezett beállítást

A Kubernetesnek két lehetősége van a beépített profilokhoz: runtime/default и docker/default. Mindkettőt a tároló futtatókörnyezete valósítja meg, nem a Kubernetes. Ezért ezek a használt futási környezettől és annak verziójától függően eltérőek lehetnek.

Más szavakkal, a futási idő változása következtében a tároló más rendszerhívásokhoz férhet hozzá, amelyeket használhat, vagy nem. A legtöbb futásidejű használat Docker megvalósítás. Ha szeretné használni ezt a profilt, győződjön meg arról, hogy megfelelő-e az Ön számára.

Profil docker/default A Kubernetes 1.11 óta elavult, ezért kerülje a használatát.

Véleményem szerint profil runtime/default tökéletesen megfelel arra a célra, amelyre létrehozták: a felhasználók védelme a parancs végrehajtásával kapcsolatos kockázatoktól docker run az autóikon. Ha azonban a Kubernetes fürtökön futó üzleti alkalmazásokról van szó, akkor azt merem vitatkozni, hogy egy ilyen profil túl nyitott, és a fejlesztőknek az alkalmazásaik (vagy alkalmazástípusai) profilok létrehozására kellene összpontosítaniuk.

3. tipp: seccomp profilok létrehozása adott alkalmazásokhoz. Ha ez nem lehetséges, hozzon létre profilokat az alkalmazástípusokhoz, például hozzon létre egy speciális profilt, amely tartalmazza a Golang alkalmazás összes webes API-ját. Csak végső megoldásként használja a futásidejű/alapértelmezett beállítást.

A következő bejegyzésekben kitérek arra, hogyan hozhatok létre SecDevOps-ihlette seccomp-profilokat, hogyan automatizálhatok és tesztelhetek folyamatban. Más szóval, nem lesz mentséged arra, hogy ne frissíts alkalmazás-specifikus profilokra.

4. A Unconfined NEM választható.

Of Az első Kubernetes biztonsági audit alapból kiderült seccomp letiltva. Ez azt jelenti, hogy ha nem állítja be PodSecurityPolicy, amely engedélyezi a fürtben, minden olyan pod, amelyhez nincs megadva seccomp profil, működni fog seccomp=unconfined.

Ebben az üzemmódban egy teljes szigetelési réteg elveszik, amely védi a klasztert. Ezt a megközelítést a biztonsági szakértők nem javasolják.

4. tipp: A fürtben egyetlen tároló sem futhat be seccomp=unconfined, különösen termelési környezetben.

5. "Audit mode"

Ez a pont nem csak a Kubernetesre jellemző, de még mindig a „kezdés előtt tudnivalók” kategóriába tartozik.

A seccomp profilok létrehozása mindig is nagy kihívás volt, és nagymértékben támaszkodik a próbálkozásokra. A helyzet az, hogy a felhasználóknak egyszerűen nincs lehetőségük éles környezetben tesztelni őket az alkalmazás „eldobásának” kockázata nélkül.

A 4.14-es Linux kernel megjelenése után lehetővé vált, hogy egy profil egyes részeit audit módban lehessen futtatni, az összes rendszerhívásról információt rögzítve a syslogban, de ezek blokkolása nélkül. Ezt a módot a paraméterrel aktiválhatja SCMT_ACT_LOG:

SCMP_ACT_LOG: a seccomp nem befolyásolja a rendszerhívást végző szálat, ha az nem egyezik a szűrő egyik szabályával sem, de a rendszerhívással kapcsolatos információk naplózásra kerülnek.

Íme egy tipikus stratégia a funkció használatához:

  1. Engedélyezze a szükséges rendszerhívásokat.
  2. Blokkolja a rendszerből érkező hívásokat, amelyekről tudja, hogy nem lesznek hasznosak.
  3. Rögzítse az összes többi hívás adatait a naplóban.

Egy egyszerűsített példa így néz ki:

{
    "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"
        }
    ]
}

(medium-mixed-seccomp.json)

De ne feledje, hogy le kell tiltania minden olyan hívást, amelyről tudja, hogy nem kerül felhasználásra, és amelyek károsíthatják a fürtöt. A lista összeállításának jó alapja a hivatalos Docker dokumentáció. Részletesen elmagyarázza, hogy mely rendszerhívások vannak letiltva az alapértelmezett profilban, és miért.

Van azonban egy fogás. Habár SCMT_ACT_LOG 2017 vége óta támogatja a Linux kernel, és csak viszonylag nemrég lépett be a Kubernetes ökoszisztémába. Ezért ennek a módszernek a használatához 4.14-es Linux kernelre és nem régebbi runC verzióra lesz szüksége v1.0.0-rc9.

5. tipp: A fekete-fehér listák kombinálásával létrehozható egy audit mód profil az éles teszteléshez, és minden kivétel naplózható.

6. Használjon fehérlistákat

Az engedélyezési listázás további erőfeszítést igényel, mivel minden hívást azonosítania kell, amelyre az alkalmazásnak szüksége lehet, de ez a megközelítés nagyban javítja a biztonságot:

Erősen ajánlott az engedélyezési lista használata, mivel az egyszerűbb és megbízhatóbb. A feketelistát frissíteni kell, amikor potenciálisan veszélyes rendszerhívást (vagy veszélyes zászlót/opciót, ha a tiltólistán szerepel) adnak hozzá. Ezenkívül gyakran lehetőség van egy paraméter megjelenítésének megváltoztatására anélkül, hogy megváltoztatná a lényegét, és ezáltal megkerülheti a feketelista korlátozásait.

A Go alkalmazásokhoz egy speciális eszközt fejlesztettem, amely az alkalmazást kíséri, és összegyűjti a végrehajtás során kezdeményezett összes hívást. Például a következő alkalmazáshoz:

package main

import "fmt"

func main() {
	fmt.Println("test")
}

... indítsuk el gosystract így:

go install https://github.com/pjbgf/gosystract
gosystract --template='{{- range . }}{{printf ""%s",n" .Name}}{{- end}}' application-path

... és a következő eredményt kapjuk:

"sched_yield",
"futex",
"write",
"mmap",
"exit_group",
"madvise",
"rt_sigprocmask",
"getpid",
"gettid",
"tgkill",
"rt_sigaction",
"read",
"getpgrp",
"arch_prctl",

Egyelőre ez csak egy példa – az eszközökről további részleteket fogunk közölni.

6. tipp: Csak azokat a hívásokat engedélyezi, amelyekre valóban szüksége van, és blokkolja az összes többit.

7. Rakja le a megfelelő alapot (vagy készüljön fel a váratlan viselkedésre)

A kernel kényszeríti a profilt, függetlenül attól, hogy mit írsz bele. Még akkor is, ha nem pontosan azt akartad. Például, ha letiltja a hozzáférést olyan hívásokhoz, mint pl exit vagy exit_group, a tároló nem tud megfelelően leállni, és még egy egyszerű parancs is, mint pl echo hi akassza felo határozatlan időre. Ennek eredményeként magas CPU-használatot fog kapni a fürtben:

Seccomp a Kubernetesben: 7 dolog, amit a kezdetektől tudnod kell

Ilyen esetekben egy segédprogram segíthet strace - megmutatja, mi lehet a probléma:

Seccomp a Kubernetesben: 7 dolog, amit a kezdetektől tudnod kell
sudo strace -c -p 9331

Győződjön meg arról, hogy a profilok tartalmazzák az összes rendszerhívást, amelyre az alkalmazásnak futás közben szüksége van.

7. tipp: Ügyeljen a részletekre, és győződjön meg arról, hogy az összes szükséges rendszerhívás az engedélyezési listán szerepel.

Ezzel zárul a SecDevOps szellemében a seccomp Kubernetesben való használatáról szóló cikksorozat első része. A következő részekben arról lesz szó, hogy ez miért fontos, és hogyan automatizálható a folyamat.

PS a fordítótól

Olvassa el blogunkon is:

Forrás: will.com

Hozzászólás