Bem. overs.: Vi præsenterer oversættelsen af en artikel fra en senior applikationssikkerhedsingeniør hos det britiske firma ASOS.com. Med denne artikel indleder han en serie publikationer om, hvordan man kan forbedre sikkerheden i Kubernetes ved hjælp af seccomp. Hvis introduktionen falder i læsernes smag, vil vi følge forfatteren og fortsætte med hans fremtidige materialer om emnet.

Denne artikel er den første i en serie publikationer om, hvordan man opretter seccomp-profiler i ånden af SecDevOps, uden at ty til magi og trylleri. I første del vil jeg fortælle om grundlæggende koncepter og interne detaljer ved implementeringen af seccomp i Kubernetes.
Kubernetes-økosystemet tilbyder et tilstrækkeligt udvalg af metoder til sikkerhed og isolering af containere. Artiklen handler om Secure Computing Mode, også kendt som seccomp. Dets essens ligger i 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 på lige fod med andre applikationer. Hvis containere kunne udføre enhver systemopkald, ville ondsindede programmer hurtigt udnytte dette til at omgå containerens isolation og påvirke andre applikationer: opsnappe oplysninger, ændre systemindstillinger osv.
Seccomp-profiler definerer, hvilke systemkald der skal tillades eller forbydes. Udførelsesmiljøet for containeren aktiverer dem under opstart, så kernen kan føre tilsyn med deres udførelse. Anvendelse af sådanne profiler muliggør begrænsning af angrebsvektoren og reducerer skaden i tilfælde af, at et program inde i containeren (dvs. dine afhængigheder eller deres afhængigheder) begynder at gøre, hvad den ikke har tilladelse til.
Lad os forstå grundlæggende koncepter
En grundlæggende seccomp-profil 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"
}
]
}()
defaultAction bestemmer standardmæssigt resultatet af ethvert systemopkald, der ikke er angivet i afsnittet syscalls. For at forenkle opgaven vil vi fokusere på to grundlæggende værdier, der skal anvendes:
-
SCMP_ACT_ERRNO— blokerer udførelsen af systemopkaldet, -
SCMP_ACT_ALLOW— tillader.
I sektionen architectures de målarkitekturer, der er angivet. Dette er vigtigt, da selve filteret, der anvendes på kerne niveau, afhænger af identifikatorerne for systemopkaldene, ikke deres navne, der står i profilen. Før det anvendes, vil containerens køremiljø matche dem med identifikatorerne. Pointen er, at systemopkald kan have helt forskellige ID'er afhængigt af systemarkitekturen. For eksempel har systemopkaldet recvfrom (som bruges til at modtage information fra en socket) ID = 64 i x64-systemer og ID = 517 i x86. du kan finde en liste over alle systemopkald for x86-x64-arkitekturer.
I sektionen syscalls alle systemopkald opregnes, og der angives, hvad der skal gøres med dem. For eksempel kan du oprette en hvidliste ved at indstille defaultAction og få en fungerende kommando. SCMP_ACT_ERRNO, og give systemopkaldene i afsnittet syscalls tildeles SCMP_ACT_ALLOW. På den måde tillader du kun opkaldene, der er angivet i afsnittet syscalls, og forbyder alle andre. For en sort liste skal du ændre værdierne defaultAction og handlingerne til modsat.
Nu skal vi sige et par ord om de nuancer, der ikke er så åbenlyse. Bemærk, at anbefalingerne nedenfor antager, at du udvikler en række forretningsapplikationer i Kubernetes, og at det er vigtigt for dig, at de fungerer med de laveste privilegier.
1. AllowPrivilegeEscalation=false
I securityContext i containeren er derparameter AllowPrivilegeEscalation. Hvis det er indstillet til false, vil containerne blive startet med den indstillede (on) bit . Pointen med denne parameter er tydelig fra navnet: den tillader ikke containeren at køre nye processer med privilegier, der er større end dem, den selv har.
En bivirkning af denne parameter, når den er indstillet til true (standardværdien), er, at containerens runtime anvender seccomp-profilen i starten af opstartsprocessen. Derfor skal alle systemopkald, der kræves for at starte interne processer i køremiljøet (f.eks. indstilling af bruger-/gruppenummer, fravalg af nogle capabilities), være tilladt i profilen.
En container, der udfører det banale echo hi, vil have brug for følgende tilladelser:
{
"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"
}
]
}()
… 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"
}
]
}()
Men igen, hvorfor er det et problem? Personligt ville jeg undgå at tilføje følgende systemopkald til whitelist (medmindre der er et reelt behov for dem): capset, set_tid_address, setgid, setgroups og setuid. Men den reelle udfordring er, at ved at tillade processer, som du ikke har nogen kontrol over, binder du profilerne til implementeringen af containerens kørsel. Med andre ord kan du pludselig opleve, at efter en opdatering af containerens kørsel (af dig eller, mere sandsynligt, af cloud-udbyderen) stopper containerne med at starte.
Tip nr. 1: Kør containerne med AllowPrivilegeEscaltion=false. Dette vil reducere størrelsen på seccomp-profilerne og gøre dem mindre følsomme over for ændringer i containerens kørsel.
2. Tildele seccomp-profiler på container-niveau
Seccomp-profilen kan angives på pod-niveau:
annotations:
seccomp.security.alpha.kubernetes.io/pod: "localhost/profile.json"… eller på container-niveau:
annotations:
container.security.alpha.kubernetes.io/<container-name>: "localhost/profile.json"Bemærk, at den ovenfor angivne syntaks vil ændre sig, når Kubernetes seccomp (denne begivenhed forventes i den næste Kubernetes-version — 1.18 — bemærk red.).
Få ved, at Kubernetes altid har haft , som gjorde, at seccomp-profilerne blev anvendt til . Execution environment partially compensates for this drawback; however, this container does not disappear from the pods, as it is used to configure their infrastructure.
The problem is that this container always starts with AllowPrivilegeEscalation=true, leading to the issues mentioned in point 1, and this cannot be changed.
By applying seccomp profiles at the container level, you avoid this trap and can create a profile tailored specifically for the given container. This will need to be done until developers fix the bug and a new version (perhaps 1.18?) becomes available for everyone.
Tip #2: Set seccomp profiles at the container level.
In practical terms, this rule generally serves as a universal answer to the question: “Why does my seccomp profile work with docker run, but not work after deployment in a Kubernetes cluster?”.
3. Use runtime/default only as a last resort.
In Kubernetes, there are two built-in profile options: runtime/default og docker/default. Both are implemented by the container runtime, not Kubernetes. Therefore, they may differ depending on the runtime environment used and its version.
In other words, as a result of changing the runtime, the container may gain access to a different set of system calls that it may or may not use. Most runtimes use If you wish to engage this profile, ensure that it suits your needs.
The profile docker/default is considered deprecated with Kubernetes 1.11, so avoid its use.
In my opinion, the profile runtime/default is perfectly suited for the purposes for which it was created: protecting users from the risks associated with executing commands docker run on their machines. However, when it comes to business applications running in Kubernetes clusters, I would dare to say that such a profile is too permissive and developers should focus on creating profiles for their applications (or types of applications).
Tip #3: Create seccomp profiles tailored to specific applications. If this is not possible, work on profiles by application types, for instance, create an extended profile that encompasses all web APIs of an application written in Golang. Only use runtime/default as a last resort.
I kommende publikationer vil jeg fortælle, hvordan man opretter seccomp-profiler i ånden af SecDevOps, automatiserer og tester dem i pipelines. Med andre ord vil du ikke have nogen undskyldninger for ikke at skifte til profiler for specifikke applikationer.
4. Unconfined — det er IKKE en mulighed
Af det viste sig, at som standard . Det betyder, at hvis du ikke angiver PodSecurityPolicy, som aktiverer det i klyngen, vil alle pods, hvor der ikke er defineret en seccomp-profil, køre i tilstand seccomp=unconfined.
At køre i en sådan tilstand betyder, at et helt lag af isolation, der beskytter klyngen, går tabt. En sådan tilgang anbefales ikke af sikkerhedsspecialister.
Tip nr. 4: Ingen container i klyngen bør køre i tilstand seccomp=unconfined, især i produktionsmiljøer.
5. "Audit mode"
Dette skæringspunkt er ikke unikt for Kubernetes, men falder stadig ind under kategorien "hvad man bør vide allerede før man starter."
Det har traditionelt været sådan, at oprettelse af seccomp-profiler altid har været en udfordring og i vid udstrækning har været baseret på en metode med forsøg og fejl. Problemet er, at brugerne simpelthen ikke har mulighed for at teste dem i produktionsmiljøer uden at risikere at "knække" applikationen.
Med introduktionen af Linux-kernen 4.14 blev det muligt at køre dele af profilen i audit-tilstand, hvor alle systemopkald bliver registreret i syslog, men ikke blokeret. Denne tilstand kan aktiveres ved hjælp af parameteren SCMT_ACT_LOG:
SCMP_ACT_LOG: seccomp vil ikke påvirke strømmen, der udfører systemopkaldet, hvis det ikke falder ind under nogen af filtreringsreglerne, men information om systemopkaldet vil blive logget.
Her er en typisk strategi for at bruge denne mulighed:
- Tillad de systemopkald, der er nødvendige.
- Bloker systemopkald, som det er kendt, ikke vil være nyttige.
- Logge information om alle andre opkald.
Et forenklet eksempel ser sådan 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"
}
]
}()
Men husk at du skal blokere alle opkald, som du ved, ikke vil blive brugt, og som potentielt kan skade klyngen. Et godt udgangspunkt for at lave en liste er den officielle . Den forklarer i detaljer, hvilke systemopkald der er blokeret i standardprofilen og hvorfor.
Der er dog en fangst. Selvom SCMT_ACT_LOG det har været understøttet af Linux-kernen siden slutningen af 2017, er det først for nylig kommet ind i Kubernetes-økosystemet. Derfor kræver brugen af denne metode en Linux-kerne 4.14 og runC version ikke lavere end .
Tip nr. 5: En auditprofil til testning i produktion kan oprettes ved at kombinere sorte og hvide lister, mens alle undtagelser registreres i loggen.
6. Brug hvide lister
Oprettelse af hvide lister kræver ekstra indsats, da det er nødvendigt at identificere hver opkald, der kan være brug for applikationen, men denne tilgang øger sikkerheden betydeligt:
Det anbefales stærkt at bruge en tilgang baseret på hvide lister, da det er enklere og mere pålideligt. Den sorte liste skal opdateres hver gang et potentielt farligt systemopkald (eller farlig flag/indstilling, hvis de er i den sorte liste) tilføjes. Derudover kan du ofte ændre repræsentationen af parameteren uden at ændre dens essens og derved omgå begrænsningerne i den sorte liste.
Til applikationer skrevet i Go har jeg udviklet et særligt værktøj, der følger applikationen og indsamler alle opkald, der foretages under eksekvering. For eksempel for følgende applikation:
package main
import "fmt"
func main() {
fmt.Println("test")
} … lad os køre gosystract sådan ud:
go install https://github.com/pjbgf/gosystract
gosystract --template='{{- range . }}{{printf "%s", .Name}}{{- end}}' application-path… og få følgende resultat:
"sched_yield",
"futex",
"write",
"mmap",
"exit_group",
"madvise",
"rt_sigprocmask",
"getpid",
"gettid",
"tgkill",
"rt_sigaction",
"read",
"getpgrp",
"arch_prctl",Dette er kun et eksempel — detaljerne om værktøjerne kommer senere.
Tip nr. 6: Tillad kun de opkald, som du faktisk har brug for, og blokér alle andre.
7. Læg de rette fundamenter (eller forbered dig på uventet adfærd)
Kernen vil overvåge opretholdelsen af profilen uanset hvad du har angivet. Selv hvis det ikke helt er, hvad du ønskede. For eksempel, hvis du blokerer adgangen til opkald som sigreturn eller exit_group, vil containeren ikke kunne afslutte korrekt, og selv en simpel kommando som echo hi i ubestemt tid. Resultatet bliver høj CPU-belastning i klyngen:

I sådanne tilfælde kan værktøjet strace — det vil vise, hvad problemet kan være:

sudo strace -c -p 9331
Sørg for, at profilerne indeholder alle systemopkald, som applikationen har brug for under drift.
Tip nr. 7: Vær opmærksom på detaljerne og kontroller, at alle nødvendige systemopkald er inkluderet i whitelist.
Dette er afslutningen på den første del af artikelserien om brugen af seccomp i Kubernetes i ånden af SecDevOps. I de kommende dele vil vi tale om, hvorfor det er vigtigt, og hvordan man automatiserer processen.
P.S. fra oversætteren
Læs også på vores blog:
- «»;
- «»;
- «»;
- «».
Kilde: habr.com
