Seccomp i Kubernetes: 7 ting du trenger å vite helt fra begynnelsen

Merk. overs.: Vi presenterer for din oppmerksomhet oversettelsen av en artikkel av en senior applikasjonssikkerhetsingeniør hos det britiske selskapet ASOS.com. Med den begynner han en serie publikasjoner dedikert til å forbedre sikkerheten i Kubernetes gjennom bruk av seccomp. Hvis leserne liker introduksjonen, vil vi følge forfatteren og fortsette med hans fremtidige materiale om dette emnet.

Seccomp i Kubernetes: 7 ting du trenger å vite helt fra begynnelsen

Denne artikkelen er den første i en serie med innlegg om hvordan du lager seccomp-profiler i SecDevOps-ånden, uten å ty til magi og hekseri. I del XNUMX vil jeg dekke det grunnleggende og interne detaljene for implementering av seccomp i Kubernetes.

Kubernetes-økosystemet tilbyr en lang rekke måter å sikre og isolere beholdere på. Artikkelen handler om Secure Computing Mode, også kjent som secomp. Essensen er å filtrere systemkallene som er tilgjengelige for kjøring av containere.

Hvorfor er det viktig? En beholder er bare en prosess som kjører på en bestemt maskin. Og den bruker kjernen akkurat som andre applikasjoner. Hvis containere kunne utføre noen systemanrop, ville skadevare snart utnytte dette for å omgå containerisolasjon og påvirke andre applikasjoner: fange opp informasjon, endre systeminnstillinger osv.

seccomp-profiler definerer hvilke systemanrop som skal tillates eller deaktiveres. Beholderens kjøretid aktiverer dem når den starter slik at kjernen kan overvåke utførelsen av dem. Ved å bruke slike profiler kan du begrense angrepsvektoren og redusere skaden hvis et program inne i beholderen (det vil si avhengighetene dine, eller deres avhengigheter) begynner å gjøre noe det ikke er tillatt å gjøre.

Forstå det grunnleggende

Den grunnleggende secomp-profilen inkluderer tre elementer: defaultAction, architectures (eller archMap) Og 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-secomp.json)

defaultAction bestemmer standardskjebnen til et systemanrop som ikke er spesifisert i avsnittet syscalls. For å gjøre ting enklere, la oss fokusere på de to hovedverdiene som vil bli brukt:

  • SCMP_ACT_ERRNO – blokkerer kjøringen av et systemanrop,
  • SCMP_ACT_ALLOW - muliggjør.

I avsnitt architectures målarkitekturer er oppført. Dette er viktig fordi selve filteret, brukt på kjernenivå, avhenger av systemanropsidentifikatorer, og ikke av navnene som er spesifisert i profilen. Beholderens kjøretid vil matche dem med identifikatorer før bruk. Tanken er at systemanrop kan ha helt forskjellige IDer avhengig av systemarkitekturen. For eksempel systemanrop recvfrom (brukes til å motta informasjon fra stikkontakten) har ID = 64 på x64-systemer og ID = 517 på x86. Her du kan finne en liste over alle systemkall for x86-x64-arkitekturer.

I seksjonen syscalls viser alle systemanrop og spesifiserer hva som skal gjøres med dem. Du kan for eksempel opprette en hviteliste ved å stille inn defaultActionSCMP_ACT_ERRNO, og anrop i seksjonen syscalls tildele SCMP_ACT_ALLOW. Dermed tillater du kun anrop spesifisert i avsnittet syscalls, og forby alle andre. For svartelisten bør du endre verdiene defaultAction og handlinger til det motsatte.

Nå skal vi si noen ord om nyanser som ikke er så åpenbare. Vær oppmerksom på at anbefalingene nedenfor forutsetter at du distribuerer en linje med forretningsapplikasjoner på Kubernetes, og du vil at de skal kjøre med minst mulig privilegier.

1. AllowPrivilegeEscalation=false

В securityContext container har en parameter AllowPrivilegeEscalation. Hvis den er installert i false, vil containere starte med (on) bit no_new_priv. Betydningen av denne parameteren er åpenbar fra navnet: den forhindrer beholderen i å starte nye prosesser med flere privilegier enn den selv har.

En bivirkning av at dette alternativet er satt til true (standard) er at beholderens kjøretid bruker seccomp-profilen helt i begynnelsen av oppstartsprosessen. Dermed må alle systemanrop som kreves for å kjøre interne kjøretidsprosesser (f.eks. sette bruker-/gruppe-IDer, droppe visse funksjoner) aktiveres i profilen.

Til en container som gjør trivielle ting echo hi, vil følgende tillatelser kreves:

{
    "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-secomp.json)

...i stedet for disse:

{
    "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-secomp.json)

Men igjen, hvorfor er dette et problem? Personlig ville jeg unngå å hviteliste følgende systemanrop (med mindre det er et reelt behov for dem): capset, set_tid_address, setgid, setgroups и setuid. Den virkelige utfordringen er imidlertid at ved å tillate prosesser som du absolutt ikke har kontroll over, knytter du profiler til containerkjøringsimplementeringen. Med andre ord, en dag kan du oppdage at etter oppdatering av container-runtime-miljøet (enten av deg eller, mer sannsynlig, av skytjenesteleverandøren), slutter containerne plutselig å kjøre.

Tips nr. 1: Kjør containere med AllowPrivilegeEscaltion=false. Dette vil redusere størrelsen på seccomp-profiler og gjøre dem mindre følsomme for endringer i beholderens kjøretidsmiljø.

2. Sette secomp-profiler på beholdernivå

Secomp-profilen kan settes på pod-nivå:

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

...eller på beholdernivå:

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

Vær oppmerksom på at syntaksen ovenfor vil endres når Kubernetes seccomp vil bli GA (denne begivenheten forventes i neste utgivelse av Kubernetes - 1.18 - ca. oversettelse).

De færreste vet at Kubernetes alltid har hatt det feilsom førte til at secomp-profiler ble brukt på pause container. Runtime-miljøet kompenserer delvis for denne mangelen, men denne beholderen forsvinner ikke fra podene, siden den brukes til å konfigurere deres infrastruktur.

Problemet er at denne beholderen alltid starter med AllowPrivilegeEscalation=true, som fører til problemene nevnt i paragraf 1, og dette kan ikke endres.

Ved å bruke secomp-profiler på containernivå unngår du denne fallgruven og kan lage en profil som er skreddersydd for en spesifikk container. Dette må gjøres til utviklerne fikser feilen og den nye versjonen (kanskje 1.18?) blir tilgjengelig for alle.

Tips nr. 2: Sett secomp-profiler på beholdernivå.

I praktisk forstand fungerer denne regelen vanligvis som et universelt svar på spørsmålet: "Hvorfor fungerer min seccomp-profil med docker runmen fungerer ikke etter distribusjon til en Kubernetes-klynge?

3. Bruk kun kjøretid/standard som en siste utvei

Kubernetes har to alternativer for innebygde profiler: runtime/default и docker/default. Begge er implementert av beholderens kjøretid, ikke Kubernetes. Derfor kan de variere avhengig av kjøretidsmiljøet som brukes og versjonen.

Med andre ord, som et resultat av endret kjøretid, kan beholderen ha tilgang til et annet sett med systemanrop, som den kan bruke eller ikke kan bruke. De fleste kjøretider bruker Docker-implementering. Hvis du ønsker å bruke denne profilen, sørg for at den passer for deg.

Profil docker/default har blitt avviklet siden Kubernetes 1.11, så unngå å bruke den.

Etter min mening, profil runtime/default perfekt egnet for formålet den ble opprettet for: å beskytte brukere mot risikoen forbundet med å utføre en kommando docker run på bilene deres. Men når det gjelder forretningsapplikasjoner som kjører på Kubernetes-klynger, vil jeg tørre å hevde at en slik profil er for åpen og utviklere bør fokusere på å lage profiler for sine applikasjoner (eller typer applikasjoner).

Tips nr. 3: Opprett secomp-profiler for spesifikke applikasjoner. Hvis dette ikke er mulig, kan du opprette profiler for applikasjonstyper, for eksempel opprette en avansert profil som inkluderer alle nett-API-ene til Golang-applikasjonen. Bruk kun kjøretid/standard som en siste utvei.

I fremtidige innlegg vil jeg dekke hvordan du lager SecDevOps-inspirerte seccomp-profiler, automatiserer dem og tester dem i pipelines. Du har med andre ord ingen unnskyldning for ikke å oppgradere til applikasjonsspesifikke profiler.

4. Unconfined er IKKE et alternativ.

Av første Kubernetes sikkerhetsrevisjon det viste seg at som standard seccomp deaktivert. Dette betyr at hvis du ikke setter PodSecurityPolicy, som vil aktivere det i klyngen, vil alle pods som seccomp-profilen ikke er definert for, fungere i seccomp=unconfined.

Å operere i denne modusen betyr at et helt lag med isolasjon går tapt som beskytter klyngen. Denne tilnærmingen anbefales ikke av sikkerhetseksperter.

Tips nr. 4: Ingen beholder i klyngen skal kjøres inn seccomp=unconfined, spesielt i produksjonsmiljøer.

5. "Revisjonsmodus"

Dette punktet er ikke unikt for Kubernetes, men faller fortsatt inn i kategorien "ting å vite før du starter".

Som det skjer, har det alltid vært utfordrende å lage seccomp-profiler og er avhengig av prøving og feiling. Faktum er at brukerne rett og slett ikke har mulighet til å teste dem i produksjonsmiljøer uten å risikere å "slippe" applikasjonen.

Etter utgivelsen av Linux-kjernen 4.14 ble det mulig å kjøre deler av en profil i revisjonsmodus, registrere informasjon om alle systemanrop i syslog, men uten å blokkere dem. Du kan aktivere denne modusen ved å bruke parameteren SCMT_ACT_LOG:

SCMP_ACT_LOG: seccomp vil ikke påvirke tråden som gjør systemkallet hvis den ikke samsvarer med noen regel i filteret, men informasjon om systemkallet vil bli logget.

Her er en typisk strategi for bruk av denne funksjonen:

  1. Tillat systemanrop som er nødvendige.
  2. Blokker anrop fra systemet som du vet ikke vil være nyttig.
  3. Registrer informasjon om alle andre samtaler i loggen.

Et forenklet eksempel ser slik ut:

{
    "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-blandet-secomp.json)

Men husk at du må blokkere alle samtaler som du vet ikke vil bli brukt og som potensielt kan skade klyngen. Et godt grunnlag for å sette sammen en liste er den offisielle Docker-dokumentasjon. Den forklarer i detalj hvilke systemanrop som er blokkert i standardprofilen og hvorfor.

Det er imidlertid en hake. Selv om SCMT_ACT_LOG støttet av Linux-kjernen siden slutten av 2017, kom den inn i Kubernetes-økosystemet bare relativt nylig. Derfor, for å bruke denne metoden, trenger du en Linux-kjerne 4.14 og runC-versjon ikke lavere v1.0.0-rc9.

Tips nr. 5: En revisjonsmodusprofil for testing i produksjon kan opprettes ved å kombinere svarte og hvite lister, og alle unntak kan logges.

6. Bruk hvitelister

Hvitelisting krever ekstra innsats fordi du må identifisere alle anrop som applikasjonen kan trenge, men denne tilnærmingen forbedrer sikkerheten betydelig:

Det anbefales sterkt å bruke hvitelistetilnærmingen da den er enklere og mer pålitelig. Svartelisten må oppdateres hver gang et potensielt farlig systemanrop (eller et farlig flagg/alternativ hvis det er på svartelisten) legges til. I tillegg er det ofte mulig å endre representasjonen av en parameter uten å endre dens essens og dermed omgå restriksjonene til svartelisten.

For Go-applikasjoner utviklet jeg et spesielt verktøy som følger med applikasjonen og samler alle anrop som blir gjort under utførelse. For eksempel for følgende applikasjon:

package main

import "fmt"

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

... la oss lansere gosystract som følger:

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

... og vi får følgende resultat:

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

Foreløpig er dette bare et eksempel - flere detaljer om verktøyene vil følge.

Tips nr. 6: Tillat bare de samtalene du virkelig trenger, og blokker alle andre.

7. Legg det rette grunnlaget (eller forbered deg på uventet oppførsel)

Kjernen vil håndheve profilen uavhengig av hva du skriver i den. Selv om det ikke er akkurat det du ønsket. For eksempel hvis du blokkerer tilgang til samtaler som exit eller exit_group, vil beholderen ikke kunne slås av på riktig måte, og til og med en enkel kommando som echo hi heng ham oppo på ubestemt tid. Som et resultat vil du få høy CPU-bruk i klyngen:

Seccomp i Kubernetes: 7 ting du trenger å vite helt fra begynnelsen

I slike tilfeller kan et verktøy komme til unnsetning strace - det vil vise hva problemet kan være:

Seccomp i Kubernetes: 7 ting du trenger å vite helt fra begynnelsen
sudo strace -c -p 9331

Sørg for at profilene inneholder alle systemanrop som applikasjonen trenger ved kjøring.

Tips nr. 7: Vær oppmerksom på detaljer og sørg for at alle nødvendige systemanrop er hvitelistet.

Dette avslutter første del av en serie artikler om bruk av seccomp i Kubernetes i ånden til SecDevOps. I de følgende delene vil vi snakke om hvorfor dette er viktig og hvordan man kan automatisere prosessen.

PS fra oversetter

Les også på bloggen vår:

Kilde: www.habr.com

Legg til en kommentar