Seccomp i Kubernetes: 7 ting, du skal vide helt fra begyndelsen

Bemærk. overs.: Vi præsenterer for din opmærksomhed oversættelsen af ​​en artikel af en senior applikationssikkerhedsingeniør hos det britiske firma ASOS.com. Med den begynder han en række publikationer dedikeret til at forbedre sikkerheden i Kubernetes gennem brug af seccomp. Hvis læserne kan lide introduktionen, vil vi følge forfatteren og fortsætte med hans fremtidige materialer om dette emne.

Seccomp i Kubernetes: 7 ting, du skal vide helt fra begyndelsen

Denne artikel er den første i en række indlæg om, hvordan man opretter seccomp-profiler i SecDevOps-ånden uden at ty til magi og hekseri. I del XNUMX vil jeg dække det grundlæggende og interne detaljer om implementering af seccomp i Kubernetes.

Kubernetes-økosystemet tilbyder en bred vifte af måder at sikre og isolere containere på. Artiklen handler om Secure Computing Mode, også kendt som secomp. Dens essens er at filtrere de systemkald, der er tilgængelige for udførelse af containere.

Hvorfor er det vigtigt? En container er blot en proces, der kører på en bestemt maskine. Og den bruger kernen ligesom andre applikationer. Hvis containere kunne udføre systemkald, ville malware meget snart udnytte dette til at omgå containerisolering og påvirke andre applikationer: opsnappe information, ændre systemindstillinger osv.

seccomp-profiler definerer, hvilke systemopkald der skal tillades eller deaktiveres. Containerruntiden aktiverer dem, når den starter, så kernen kan overvåge deres eksekvering. Brug af sådanne profiler giver dig mulighed for at begrænse angrebsvektoren og reducere skader, hvis et program inde i containeren (det vil sige dine afhængigheder eller deres afhængigheder) begynder at gøre noget, som det ikke er tilladt at gøre.

At komme til det grundlæggende

Den grundlæggende secomp-profil indeholder 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 standardskæbnen for ethvert systemkald, der ikke er angivet i afsnittet syscalls. For at gøre tingene lettere, lad os fokusere på de to hovedværdier, der vil blive brugt:

  • SCMP_ACT_ERRNO — blokerer udførelsen af ​​et systemopkald,
  • SCMP_ACT_ALLOW - tillader.

I afsnit architectures målarkitekturer er anført. Dette er vigtigt, fordi selve filteret, der anvendes på kerneniveau, afhænger af systemopkaldsidentifikatorer og ikke af deres navne angivet i profilen. Beholderens køretid vil matche dem med identifikatorer før brug. Tanken er, at systemopkald kan have helt forskellige ID'er afhængigt af systemarkitekturen. For eksempel systemkald recvfrom (bruges til at modtage information fra stikkontakten) har ID = 64 på x64-systemer og ID = 517 på x86. Her du kan finde en liste over alle systemkald til x86-x64-arkitekturer.

I afsnittet syscalls viser alle systemopkald og specificerer, hvad der skal gøres med dem. For eksempel kan du oprette en hvidliste ved at indstille defaultActionSCMP_ACT_ERRNO, og opkald i afsnittet syscalls tildele SCMP_ACT_ALLOW. Du tillader således kun opkald angivet i afsnittet syscalls, og forbyd alle andre. For sortlisten bør du ændre værdierne defaultAction og handlinger til det modsatte.

Nu skal vi sige et par ord om nuancer, der ikke er så tydelige. Bemærk venligst, at anbefalingerne nedenfor antager, at du implementerer en række forretningsapplikationer på Kubernetes, og du vil have dem til at køre med det mindst mulige antal privilegier.

1. AllowPrivilegeEscalation=falsk

В securityContext container har en parameter AllowPrivilegeEscalation. Hvis det er installeret i false, vil containere starte med (on) lidt no_new_priv. Betydningen af ​​denne parameter fremgår tydeligt af navnet: den forhindrer containeren i at starte nye processer med flere privilegier, end den selv har.

En bivirkning af, at denne indstilling er indstillet til true (standard) er, at containerkørselstiden anvender seccomp-profilen helt i begyndelsen af ​​opstartsprocessen. Således skal alle systemkald, der kræves for at køre interne runtime-processer (f.eks. indstilling af bruger/gruppe-id'er, droppe visse kapaciteter) aktiveres i profilen.

Til en container, der gør trivielle ting echo hi, vil følgende tilladelser være nødvendige:

{
    "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 igen, hvorfor er dette et problem? Personligt ville jeg undgå at hvidliste følgende systemopkald (medmindre der er et reelt behov for dem): capset, set_tid_address, setgid, setgroups и setuid. Men den virkelige udfordring er, at ved at tillade processer, som du absolut ikke har kontrol over, binder du profiler til container-runtime-implementeringen. Med andre ord kan du en dag opdage, at efter opdatering af container-runtime-miljøet (enten af ​​dig eller, mere sandsynligt, af cloud-tjenesteudbyderen), holder containerne pludselig op med at køre.

Tip nr. 1: Kør containere med AllowPrivilegeEscaltion=false. Dette vil reducere størrelsen af ​​seccomp-profiler og gøre dem mindre følsomme over for ændringer i container-runtime-miljøet.

2. Indstilling af seccomp-profiler på containerniveau

Secomp-profilen kan indstilles på pod-niveau:

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

...eller på containerniveau:

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

Bemærk venligst, at ovenstående syntaks ændres, når Kubernetes seccomp bliver GA (denne begivenhed forventes i den næste udgivelse af Kubernetes - 1.18 - ca. oversættelse).

De færreste ved, at Kubernetes altid har haft insekthvilket forårsagede, at secomp-profiler blev påført pause container. Runtime-miljøet kompenserer delvist for denne mangel, men denne beholder forsvinder ikke fra pods, da den bruges til at konfigurere deres infrastruktur.

Problemet er, at denne beholder altid starter med AllowPrivilegeEscalation=true, hvilket fører til problemerne i stk. 1, og dette kan ikke ændres.

Ved at bruge seccomp-profiler på containerniveau undgår du denne faldgrube og kan lave en profil, der er skræddersyet til en specifik container. Dette skal gøres, indtil udviklerne har rettet fejlen, og den nye version (måske 1.18?) bliver tilgængelig for alle.

Tip nr. 2: Indstil secomp-profiler på beholderniveau.

I praktisk forstand tjener denne regel normalt som et universelt svar på spørgsmålet: "Hvorfor fungerer min sekundære profil med docker runmen virker det ikke efter installation til en Kubernetes-klynge?

3. Brug kun runtime/default som en sidste udvej

Kubernetes har to muligheder for indbyggede profiler: runtime/default и docker/default. Begge er implementeret af container-runtime, ikke Kubernetes. Derfor kan de variere afhængigt af det anvendte runtime-miljø og dets version.

Med andre ord, som et resultat af ændring af kørselstid, kan containeren have adgang til et andet sæt systemopkald, som den måske eller måske ikke bruger. De fleste køretider bruger Docker implementering. Hvis du ønsker at bruge denne profil, skal du sørge for, at den passer til dig.

profil docker/default er blevet forældet siden Kubernetes 1.11, så undgå at bruge det.

Efter min mening profil runtime/default perfekt egnet til det formål, som det blev oprettet til: at beskytte brugere mod de risici, der er forbundet med at udføre en kommando docker run på deres biler. Men når det kommer til forretningsapplikationer, der kører på Kubernetes-klynger, vil jeg vove at argumentere for, at en sådan profil er for åben, og udviklere bør fokusere på at skabe profiler til deres applikationer (eller typer applikationer).

Tip nr. 3: Opret seccomp-profiler til specifikke applikationer. Hvis dette ikke er muligt, skal du oprette profiler til applikationstyper, for eksempel oprette en avanceret profil, der inkluderer alle Golang-applikationens web-API'er. Brug kun runtime/default som sidste udvej.

I fremtidige indlæg vil jeg dække, hvordan man opretter SecDevOps-inspirerede seccomp-profiler, automatiserer dem og tester dem i pipelines. Med andre ord har du ingen undskyldning for ikke at opgradere til applikationsspecifikke profiler.

4. Ubegrænset er IKKE en mulighed.

Af første Kubernetes sikkerhedsrevision det viste sig som standard seccomp deaktiveret. Det betyder, at hvis du ikke indstiller PodSecurityPolicy, som vil aktivere det i klyngen, vil alle pods, for hvilke seccomp-profilen ikke er defineret, fungere i seccomp=unconfined.

At arbejde i denne tilstand betyder, at et helt lag isolering går tabt, der beskytter klyngen. Denne tilgang anbefales ikke af sikkerhedseksperter.

Tip nr. 4: Ingen beholder i klyngen må køre ind seccomp=unconfined, især i produktionsmiljøer.

5. "Revisionstilstand"

Dette punkt er ikke unikt for Kubernetes, men falder stadig ind under kategorien "ting at vide, før du starter".

Som det sker, har det altid været udfordrende at oprette seccomp-profiler og er stærkt afhængig af forsøg og fejl. Faktum er, at brugerne simpelthen ikke har mulighed for at teste dem i produktionsmiljøer uden at risikere at "droppe" applikationen.

Efter udgivelsen af ​​Linux-kernen 4.14 blev det muligt at køre dele af en profil i revisionstilstand, registrere information om alle systemkald i syslog, men uden at blokere dem. Du kan aktivere denne tilstand ved hjælp af parameteren SCMT_ACT_LOG:

SCMP_ACT_LOG: seccomp vil ikke påvirke tråden, der foretager systemkaldet, hvis den ikke matcher nogen regel i filteret, men information om systemkaldet vil blive logget.

Her er en typisk strategi for brug af denne funktion:

  1. Tillad systemopkald, der er nødvendige.
  2. Bloker opkald fra systemet, som du ved ikke vil være nyttige.
  3. Registrer oplysninger om alle andre opkald i loggen.

Et forenklet eksempel ser således ud:

{
    "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 skal blokere alle opkald, som du ved ikke vil blive brugt, og som potentielt kan skade klyngen. Et godt grundlag for at lave en liste er den officielle Docker dokumentation. Den forklarer i detaljer, hvilke systemopkald der er blokeret i standardprofilen, og hvorfor.

Der er dog én fangst. Selvom SCMT_ACT_LOG understøttet af Linux-kernen siden slutningen af ​​2017, kom den kun ind i Kubernetes-økosystemet relativt for nylig. For at bruge denne metode skal du derfor bruge en Linux-kerne 4.14 og runC-version, der ikke er lavere v1.0.0-rc9.

Tip nr. 5: En revisionstilstandsprofil til test i produktionen kan oprettes ved at kombinere sorte og hvide lister, og alle undtagelser kan logges.

6. Brug hvidlister

Whitelisting kræver yderligere indsats, fordi du skal identificere hvert opkald, som applikationen kan have brug for, men denne tilgang forbedrer sikkerheden væsentligt:

Det anbefales stærkt at bruge hvidlistetilgangen, da den er enklere og mere pålidelig. Sortlisten skal opdateres, hver gang et potentielt farligt systemkald (eller et farligt flag/valgmulighed, hvis det er på sortlisten) tilføjes. Derudover er det ofte muligt at ændre repræsentationen af ​​en parameter uden at ændre dens essens og derved omgå sortlistens begrænsninger.

Til Go-applikationer udviklede jeg et særligt værktøj, der følger med applikationen og samler alle opkald, der foretages under udførelsen. For eksempel til følgende anvendelse:

package main

import "fmt"

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

... lad os starte gosystract som dette:

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",

For nu er dette kun et eksempel - flere detaljer om værktøjerne følger.

Tip nr. 6: Tillad kun de opkald, du virkelig har brug for, og bloker alle andre.

7. Læg det rigtige grundlag (eller forbered dig på uventet adfærd)

Kernen vil håndhæve profilen uanset hvad du skriver i den. Også selvom det ikke lige er, hvad du ønskede. For eksempel, hvis du blokerer adgangen til opkald som f.eks exit eller exit_group, vil beholderen ikke være i stand til at lukke ned korrekt og endda en simpel kommando som echo hi hæng ham opo på ubestemt tid. Som et resultat vil du få høj CPU-brug i klyngen:

Seccomp i Kubernetes: 7 ting, du skal vide helt fra begyndelsen

I sådanne tilfælde kan et hjælpeprogram komme til undsætning strace - det vil vise, hvad problemet kan være:

Seccomp i Kubernetes: 7 ting, du skal vide helt fra begyndelsen
sudo strace -c -p 9331

Sørg for, at profilerne indeholder alle de systemkald, som applikationen har brug for under kørsel.

Tip nr. 7: Vær opmærksom på detaljer og sørg for, at alle nødvendige systemopkald er hvidlistet.

Dette afslutter første del af en serie artikler om brug af seccomp i Kubernetes i SecDevOps-ånden. I de følgende dele vil vi tale om, hvorfor dette er vigtigt, og hvordan man automatiserer processen.

PS fra oversætteren

Læs også på vores blog:

Kilde: www.habr.com

Tilføj en kommentar