Seccomp u Kubernetesu: 7 stvari koje trebate znati od samog početka

Bilješka. prev.: Vašoj pozornosti predstavljamo prijevod članka višeg inženjera za sigurnost aplikacija britanske tvrtke ASOS.com. Njime započinje seriju publikacija posvećenih poboljšanju sigurnosti u Kubernetesu korištenjem seccompa. Ako se čitateljima svidi uvod, slijedit ćemo autora i nastaviti s njegovim budućim materijalima na ovu temu.

Seccomp u Kubernetesu: 7 stvari koje trebate znati od samog početka

Ovaj je članak prvi u nizu postova o tome kako stvoriti seccomp profile u duhu SecDevOpsa, bez pribjegavanja magiji i vještičarenju. U XNUMX. dijelu pokrit ću osnove i interne detalje implementacije seccompa u Kubernetesu.

Ekosustav Kubernetes nudi širok izbor načina za osiguravanje i izolaciju spremnika. Članak govori o sigurnom načinu rada za računalstvo, također poznatom kao Seccomp. Njegova je bit filtrirati sistemske pozive dostupne za izvršenje po spremnicima.

Zašto je to važno? Spremnik je samo proces koji se izvodi na određenom stroju. I koristi kernel baš kao i druge aplikacije. Kad bi spremnici mogli izvršavati bilo kakve sistemske pozive, vrlo brzo bi zlonamjerni softver to iskoristio da zaobiđe izolaciju spremnika i utjecao na druge aplikacije: presreo informacije, promijenio postavke sustava itd.

seccomp profili definiraju koji pozivi sustava trebaju biti dopušteni ili onemogućeni. Izvršno vrijeme spremnika ih aktivira kada se pokrene kako bi kernel mogao pratiti njihovo izvođenje. Korištenje takvih profila omogućuje vam da ograničite vektor napada i smanjite štetu ako bilo koji program unutar spremnika (to jest, vaše ovisnosti ili njihove ovisnosti) počne raditi nešto što mu nije dopušteno.

Doći do osnova

Osnovni seccomp profil uključuje tri elementa: defaultAction, architectures (Ili archMap) I 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 određuje zadanu sudbinu svakog poziva sustava koji nije naveden u odjeljku syscalls. Da bismo olakšali stvari, usredotočimo se na dvije glavne vrijednosti koje će se koristiti:

  • SCMP_ACT_ERRNO — blokira izvršenje sistemskog poziva,
  • SCMP_ACT_ALLOW - dopušta.

U odjeljku architectures navedene su ciljne arhitekture. Ovo je važno jer sam filtar, primijenjen na razini kernela, ovisi o identifikatorima sistemskih poziva, a ne o njihovim nazivima navedenim u profilu. Vrijeme izvođenja spremnika spojit će ih s identifikatorima prije upotrebe. Ideja je da sistemski pozivi mogu imati potpuno različite ID-ove ovisno o arhitekturi sustava. Na primjer, sistemski poziv recvfrom (koristi se za primanje informacija iz utičnice) ima ID = 64 na x64 sustavima i ID = 517 na x86. Ovdje možete pronaći popis svih sistemskih poziva za x86-x64 arhitekture.

U odjeljku syscalls navodi sve sistemske pozive i navodi što učiniti s njima. Na primjer, možete stvoriti popis dopuštenih postavljanjem defaultAction na SCMP_ACT_ERRNO, i poziva u rubrici syscalls dodijeliti SCMP_ACT_ALLOW. Dakle, dopuštate samo pozive navedene u odjeljku syscalls, a sve ostale zabraniti. Za crnu listu trebali biste promijeniti vrijednosti defaultAction a radnje suprotno.

Sada bismo trebali reći nekoliko riječi o nijansama koje nisu tako očite. Imajte na umu da preporuke u nastavku pretpostavljaju da implementirate niz poslovnih aplikacija na Kubernetes i želite da se izvode s najmanjom mogućom količinom privilegija.

1. AllowPrivilegeEscalation=false

В securityContext spremnik ima parametar AllowPrivilegeEscalation. Ako je instaliran u false, spremnici će započeti s (on) malo no_new_priv. Značenje ovog parametra je očito iz naziva: on sprječava spremnik da pokrene nove procese s više privilegija nego što on sam ima.

Nuspojava postavljanja ove opcije na true (zadano) je da runtime spremnika primjenjuje profil seccomp na samom početku procesa pokretanja. Dakle, svi sistemski pozivi potrebni za pokretanje internih procesa izvođenja (npr. postavljanje ID-ova korisnika/grupa, odbacivanje određenih mogućnosti) moraju biti omogućeni u profilu.

Spremniku koji radi trivijalne stvari echo hibit će potrebna sljedeća dopuštenja:

{
    "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)

...umjesto ovih:

{
    "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)

Ali opet, zašto je to problem? Osobno bih izbjegao stavljanje na popis dopuštenih sljedećih sistemskih poziva (osim ako postoji stvarna potreba za njima): capset, set_tid_address, setgid, setgroups и setuid. Međutim, pravi izazov je da dopuštanjem procesa nad kojima nemate apsolutno nikakvu kontrolu, povezujete profile s implementacijom vremena izvođenja spremnika. Drugim riječima, jednog dana možete otkriti da nakon ažuriranja runtime okruženja spremnika (bilo od vas ili, što je vjerojatnije, od strane pružatelja usluga u oblaku), spremnici odjednom prestaju raditi.

Savjet broj 1: Pokrenite spremnike s AllowPrivilegeEscaltion=false. Ovo će smanjiti veličinu seccomp profila i učiniti ih manje osjetljivima na promjene u okruženju izvođenja spremnika.

2. Postavljanje seccomp profila na razini spremnika

Profil seccomp može se postaviti na razini modula:

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

...ili na razini spremnika:

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

Imajte na umu da će se gornja sintaksa promijeniti kada Kubernetes seccomp postat će GA (ovaj se događaj očekuje u sljedećem izdanju Kubernetesa - 1.18 - pribl. prijevod).

Malo ljudi zna da je Kubernetes oduvijek imao bubašto je uzrokovalo primjenu seccomp profila pauzirati spremnik. Izvršno okruženje djelomično nadoknađuje ovaj nedostatak, ali ovaj spremnik ne nestaje iz podova jer se koristi za konfiguriranje njihove infrastrukture.

Problem je u tome što ovaj spremnik uvijek počinje s AllowPrivilegeEscalation=true, što dovodi do problema navedenih u stavku 1., i to se ne može promijeniti.

Korištenjem seccomp profila na razini spremnika izbjegavate ovu zamku i možete stvoriti profil koji je prilagođen određenom spremniku. To će se morati raditi sve dok programeri ne poprave grešku i nova verzija (možda 1.18?) ne postane dostupna svima.

Savjet broj 2: Postavite seccomp profile na razini spremnika.

U praktičnom smislu, ovo pravilo obično služi kao univerzalni odgovor na pitanje: „Zašto moj seccomp profil radi s docker runali ne radi nakon postavljanja na Kubernetes klaster?

3. Koristite runtime/default samo u krajnjem slučaju

Kubernetes ima dvije opcije za ugrađene profile: runtime/default и docker/default. Oba su implementirana runtimeom spremnika, a ne Kubernetesom. Stoga se mogu razlikovati ovisno o korištenoj runtime okolini i njenoj verziji.

Drugim riječima, kao rezultat promjene vremena izvođenja, spremnik može imati pristup različitom skupu sistemskih poziva koje može, ali i ne mora koristiti. Većina runtimea koristi Docker implementacija. Ako želite koristiti ovaj profil, provjerite je li prikladan za vas.

Profil docker/default je zastario od Kubernetesa 1.11, stoga ga izbjegavajte koristiti.

Po mom mišljenju profil runtime/default savršeno prikladan za svrhu za koju je stvoren: zaštita korisnika od rizika povezanih s izvršavanjem naredbe docker run na svojim automobilima. Međutim, kada je riječ o poslovnim aplikacijama koje rade na Kubernetes klasterima, usudio bih se ustvrditi da je takav profil previše otvoren i da bi se programeri trebali usredotočiti na izradu profila za svoje aplikacije (ili tipove aplikacija).

Savjet broj 3: Stvorite seccomp profile za određene aplikacije. Ako to nije moguće, izradite profile za vrste aplikacija, na primjer, izradite napredni profil koji uključuje sve web API-je aplikacije Golang. Koristite runtime/default samo kao krajnje sredstvo.

U budućim postovima, pokrit ću kako stvoriti seccomp profile inspirirane SecDevOpsom, automatizirati ih i testirati u cjevovodima. Drugim riječima, nećete imati izgovora da ne nadogradite na profile specifične za aplikaciju.

4. Neograničeno NIJE opcija.

Od prvi Kubernetes sigurnosni audit pokazalo se da standardno seccomp onemogućen. To znači da ako ne postavite PodSecurityPolicy, što će ga omogućiti u klasteru, svi podovi za koje seccomp profil nije definiran radit će u seccomp=unconfined.

Rad u ovom načinu rada znači da se gubi cijeli sloj izolacije koji štiti klaster. Sigurnosni stručnjaci ne preporučuju ovaj pristup.

Savjet broj 4: Nijedan spremnik u klasteru ne bi trebao biti pokrenut seccomp=unconfined, posebno u proizvodnim okruženjima.

5. "Način revizije"

Ova točka nije jedinstvena za Kubernetes, ali ipak spada u kategoriju "stvari koje trebate znati prije nego počnete".

Kako to biva, stvaranje seccomp profila uvijek je bilo izazovno i uvelike se oslanja na pokušaje i pogreške. Činjenica je da korisnici jednostavno nemaju priliku testirati ih u produkcijskim okruženjima bez opasnosti od „ispadanja“ aplikacije.

Nakon izdavanja Linux kernela 4.14, postalo je moguće pokrenuti dijelove profila u modu revizije, bilježeći informacije o svim sistemskim pozivima u syslog, ali bez njihovog blokiranja. Ovaj način rada možete aktivirati pomoću parametra SCMT_ACT_LOG:

SCMP_ACT_LOG: seccomp neće utjecati na nit koja upućuje sistemski poziv ako ne odgovara nijednom pravilu u filtru, ali informacije o sistemskom pozivu bit će zabilježene.

Evo tipične strategije za korištenje ove značajke:

  1. Dopusti pozive sustava koji su potrebni.
  2. Blokirajte pozive iz sustava za koje znate da neće biti korisni.
  3. Zabilježite podatke o svim ostalim pozivima u zapisnik.

Pojednostavljeni primjer izgleda ovako:

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

(srednje mješoviti-seccomp.json)

No zapamtite da trebate blokirati sve pozive za koje znate da se neće koristiti i koji bi potencijalno mogli naštetiti klasteru. Dobra osnova za sastavljanje liste je službena Docker dokumentacija. Detaljno objašnjava koji su pozivi sustava blokirani u zadanom profilu i zašto.

Međutim, postoji jedna kvaka. Iako SCMT_ACT_LOG podržan od strane Linux kernela od kraja 2017., u Kubernetes ekosustav ušao je tek relativno nedavno. Stoga, za korištenje ove metode trebat će vam Linux kernel 4.14 i verzija runC ne niža v1.0.0-rc9.

Savjet broj 5: Profil moda revizije za testiranje u proizvodnji može se stvoriti kombiniranjem crnih i bijelih popisa, a sve iznimke mogu se zabilježiti.

6. Koristite bijele liste

Stavljanje na popis dopuštenih zahtjeva dodatni napor jer morate identificirati svaki poziv koji bi aplikaciji mogao biti potreban, ali ovaj pristup uvelike poboljšava sigurnost:

Toplo se preporučuje korištenje pristupa popisa dopuštenih jer je jednostavniji i pouzdaniji. Crni popis morat će se ažurirati svaki put kada se doda potencijalno opasan sistemski poziv (ili opasna zastavica/opcija ako je na crnom popisu). Osim toga, često je moguće promijeniti prikaz parametra bez promjene njegove suštine i time zaobići ograničenja crne liste.

Za Go aplikacije razvio sam poseban alat koji prati aplikaciju i prikuplja sve pozive tijekom izvođenja. Na primjer, za sljedeću aplikaciju:

package main

import "fmt"

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

... lansirajmo gosystract ovako:

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

... i dobivamo sljedeći rezultat:

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

Za sada je ovo samo primjer—više pojedinosti o alatima slijedi.

Savjet broj 6: Dopustite samo one pozive koje stvarno trebate i blokirajte sve ostale.

7. Postavite prave temelje (ili se pripremite za neočekivano ponašanje)

Kernel će nametnuti profil bez obzira što u njemu napišete. Čak i ako nije baš ono što ste htjeli. Na primjer, ako blokirate pristup pozivima poput exit ili exit_group, spremnik se neće moći ispravno zatvoriti, pa čak ni jednostavna naredba poput echo hi objesi gao na neodređeno vrijeme. Kao rezultat toga, dobit ćete visoku upotrebu CPU-a u klasteru:

Seccomp u Kubernetesu: 7 stvari koje trebate znati od samog početka

U takvim slučajevima, komunalna služba može doći u pomoć strace - pokazat će u čemu bi mogao biti problem:

Seccomp u Kubernetesu: 7 stvari koje trebate znati od samog početka
sudo strace -c -p 9331

Provjerite sadrže li profili sve sistemske pozive koje aplikacija treba tijekom izvođenja.

Savjet broj 7: Obratite pozornost na detalje i provjerite jesu li svi potrebni sistemski pozivi na listi dopuštenih.

Ovo zaključuje prvi dio serije članaka o korištenju seccompa u Kubernetesu u duhu SecDevOps-a. U sljedećim dijelovima ćemo govoriti o tome zašto je to važno i kako automatizirati proces.

PS od prevoditelja

Pročitajte i na našem blogu:

Izvor: www.habr.com

Dodajte komentar