Seccomp în Kubernetes: 7 lucruri pe care trebuie să le știi de la bun început

Notă. transl.: Vă prezentăm atenției traducerea unui articol realizat de un inginer senior de securitate a aplicațiilor la compania britanică ASOS.com. Cu acesta, începe o serie de publicații dedicate îmbunătățirii securității în Kubernetes prin utilizarea seccomp. Dacă cititorilor le place introducerea, îl vom urmări pe autor și vom continua cu materialele sale viitoare pe această temă.

Seccomp în Kubernetes: 7 lucruri pe care trebuie să le știi de la bun început

Acest articol este primul dintr-o serie de postări despre cum să creați profiluri seccomp în spiritul SecDevOps, fără a recurge la magie și vrăjitorie. În partea XNUMX, voi acoperi elementele de bază și detaliile interne ale implementării seccomp în Kubernetes.

Ecosistemul Kubernetes oferă o mare varietate de moduri de a securiza și izola containerele. Articolul este despre modul Secure Computing, cunoscut și sub numele de seccomp. Esența acestuia este filtrarea apelurilor de sistem disponibile pentru execuție de către containere.

De ce este important? Un container este doar un proces care rulează pe o anumită mașină. Și folosește nucleul la fel ca alte aplicații. Dacă containerele ar putea efectua apeluri de sistem, foarte curând malware-ul ar profita de acest lucru pentru a ocoli izolarea containerului și a afecta alte aplicații: interceptarea informațiilor, modificarea setărilor sistemului etc.

Profilurile seccomp definesc ce apeluri de sistem trebuie permise sau dezactivate. Runtime-ul containerului le activează când pornește, astfel încât nucleul să poată monitoriza execuția lor. Utilizarea unor astfel de profiluri vă permite să limitați vectorul de atac și să reduceți daunele în cazul în care orice program din interiorul containerului (adică dependențele dvs. sau dependențele lor) începe să facă ceva ce nu are voie să facă.

A ajunge la elementele de bază

Profilul de bază seccomp include trei elemente: defaultAction, architectures (Sau, 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"
        }
    ]
}

(mediu-de bază-seccomp.json)

defaultAction determină soarta implicită a oricărui apel de sistem nespecificat în secțiune syscalls. Pentru a ușura lucrurile, să ne concentrăm asupra celor două valori principale care vor fi folosite:

  • SCMP_ACT_ERRNO — blochează executarea unui apel de sistem,
  • SCMP_ACT_ALLOW - permite.

În secțiunea architectures sunt enumerate arhitecturile țintă. Acest lucru este important deoarece filtrul în sine, aplicat la nivel de kernel, depinde de identificatorii de apel de sistem și nu de numele acestora specificate în profil. Durata de rulare a containerului le va potrivi cu identificatorii înainte de utilizare. Ideea este că apelurile de sistem pot avea ID-uri complet diferite în funcție de arhitectura sistemului. De exemplu, apel de sistem recvfrom (folosit pentru a primi informații de la socket) are ID = 64 pe sistemele x64 și ID = 517 pe x86. Aici puteți găsi o listă cu toate apelurile de sistem pentru arhitecturile x86-x64.

In sectiunea syscalls listează toate apelurile de sistem și specifică ce să facă cu ele. De exemplu, puteți crea o listă albă prin setare defaultAction pe SCMP_ACT_ERRNO, și apeluri în secțiune syscalls adecvat SCMP_ACT_ALLOW. Astfel, permiteți doar apelurile specificate în secțiune syscallsși interziceți toate celelalte. Pentru lista neagră ar trebui să schimbați valorile defaultAction și acțiuni spre opus.

Acum ar trebui să spunem câteva cuvinte despre nuanțe care nu sunt atât de evidente. Rețineți că recomandările de mai jos presupun că implementați o linie de aplicații de afaceri pe Kubernetes și doriți ca acestea să ruleze cu cel mai mic număr de privilegii posibil.

1. AllowPrivilegeEscalation=false

В securityContext containerul are un parametru AllowPrivilegeEscalation. Dacă este instalat în false, containerele vor începe cu (on) bit no_new_priv. Semnificația acestui parametru este evidentă din nume: împiedică containerul să lanseze noi procese cu mai multe privilegii decât are el însuși.

Un efect secundar al acestei opțiuni este setat la true (implicit) este că timpul de rulare al containerului aplică profilul seccomp chiar la începutul procesului de pornire. Astfel, toate apelurile de sistem necesare pentru a rula procesele interne de rulare (de exemplu, setarea ID-urilor de utilizator/grup, eliminarea anumitor capabilități) trebuie să fie activate în profil.

La un container care face lucruri banale echo hi, vor fi necesare următoarele permisiuni:

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

...in loc de acestea:

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

Dar din nou, de ce este aceasta o problemă? Personal, aș evita înscrierea pe lista albă a următoarelor apeluri de sistem (cu excepția cazului în care este nevoie reală de ele): capset, set_tid_address, setgid, setgroups и setuid. Cu toate acestea, adevărata provocare este că, permițând procese asupra cărora nu aveți absolut nici un control, legați profilurile de implementarea de rulare a containerului. Cu alte cuvinte, într-o zi s-ar putea să descoperi că după actualizarea mediului de rulare a containerului (fie de către tine, fie, mai probabil, de către furnizorul de servicii cloud), containerele se opresc brusc să ruleze.

Sfatul nr. 1: Rulați containerele cu AllowPrivilegeEscaltion=false. Acest lucru va reduce dimensiunea profilurilor seccomp și le va face mai puțin sensibile la modificările din mediul de rulare al containerului.

2. Setarea profilurilor seccomp la nivel de container

Profilul seccomp poate fi setat la nivel de pod:

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

...sau la nivelul containerului:

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

Vă rugăm să rețineți că sintaxa de mai sus se va modifica atunci când Kubernetes seccomp va deveni GA (acest eveniment este așteptat în următoarea versiune a Kubernetes - 1.18 - aprox. traducere).

Puțini oameni știu că Kubernetes a avut întotdeauna gândacceea ce a făcut ca profilurile seccomp să fie aplicate la container de pauză. Mediul de rulare compensează parțial acest neajuns, dar acest container nu dispare din pod-uri, deoarece este folosit pentru a configura infrastructura acestora.

Problema este că acest container începe întotdeauna cu AllowPrivilegeEscalation=true, ceea ce duce la problemele exprimate la paragraful 1, iar aceasta nu poate fi schimbată.

Folosind profiluri seccomp la nivel de container, evitați această capcană și puteți crea un profil care este adaptat unui anumit container. Acest lucru va trebui făcut până când dezvoltatorii remediază eroarea și noua versiune (poate 1.18?) devine disponibilă pentru toată lumea.

Sfatul nr. 2: Setați profiluri seccomp la nivel de container.

În sens practic, această regulă servește de obicei ca răspuns universal la întrebarea: „De ce funcționează profilul meu seccomp cu docker rundar nu funcționează după implementarea într-un cluster Kubernetes?

3. Folosiți runtime/implicit doar ca ultimă soluție

Kubernetes are două opțiuni pentru profilurile încorporate: runtime/default и docker/default. Ambele sunt implementate de rularea containerului, nu de Kubernetes. Prin urmare, acestea pot diferi în funcție de mediul de rulare utilizat și de versiunea acestuia.

Cu alte cuvinte, ca urmare a unei modificări a timpului de execuție, containerul poate avea acces la un set diferit de apeluri de sistem, pe care le poate folosi sau nu. Cele mai multe runtime folosesc Implementarea Docker. Dacă doriți să utilizați acest profil, vă rugăm să vă asigurați că este potrivit pentru dvs.

Profil docker/default a fost depreciat de la Kubernetes 1.11, așa că evitați să-l utilizați.

Dupa parerea mea, profil runtime/default perfect potrivit scopului pentru care a fost creat: protejarea utilizatorilor de riscurile asociate executării unei comenzi docker run pe mașinile lor. Cu toate acestea, când vine vorba de aplicații de afaceri care rulează pe clustere Kubernetes, aș îndrăzni să argumentez că un astfel de profil este prea deschis și dezvoltatorii ar trebui să se concentreze pe crearea de profiluri pentru aplicațiile lor (sau tipurile de aplicații).

Sfatul nr. 3: Creați profiluri seccomp pentru aplicații specifice. Dacă acest lucru nu este posibil, creați profiluri pentru tipurile de aplicații, de exemplu, creați un profil avansat care include toate API-urile web ale aplicației Golang. Folosiți doar runtime/implicit ca ultimă soluție.

În postările viitoare, voi vorbi despre cum să creez profiluri seccomp inspirate de SecDevOps, să le automatizez și să le testez în conducte. Cu alte cuvinte, nu veți avea nicio scuză să nu faceți upgrade la profiluri specifice aplicației.

4. Neconfinat NU este o opțiune.

De primul audit de securitate Kubernetes s-a dovedit că implicit seccomp dezactivat. Aceasta înseamnă că dacă nu setați PodSecurityPolicy, care îl va activa în cluster, în care vor funcționa toate podurile pentru care profilul seccomp nu este definit seccomp=unconfined.

Operarea în acest mod înseamnă că se pierde un întreg strat de izolație care protejează clusterul. Această abordare nu este recomandată de experții în securitate.

Sfatul nr. 4: Niciun container din cluster nu ar trebui să ruleze seccomp=unconfined, în special în mediile de producție.

5. „Modul de audit”

Acest punct nu este unic pentru Kubernetes, dar inca se încadrează în categoria „lucruri de știut înainte de a începe”.

După cum se întâmplă, crearea profilurilor seccomp a fost întotdeauna o provocare și se bazează în mare măsură pe încercări și erori. Cert este că utilizatorii pur și simplu nu au posibilitatea de a le testa în medii de producție fără a risca să „aruncă” aplicația.

După lansarea nucleului Linux 4.14, a devenit posibilă rularea unor părți ale unui profil în modul de audit, înregistrând informații despre toate apelurile de sistem în syslog, dar fără a le bloca. Puteți activa acest mod folosind parametrul SCMT_ACT_LOG:

SCMP_ACT_LOG: seccomp nu va afecta firul care efectuează apelul de sistem dacă nu se potrivește cu nicio regulă din filtru, dar informațiile despre apelul de sistem vor fi înregistrate.

Iată o strategie tipică pentru utilizarea acestei funcții:

  1. Permiteți apelurile de sistem necesare.
  2. Blocați apelurile din sistem despre care știți că nu vor fi utile.
  3. Înregistrați informații despre toate celelalte apeluri în jurnal.

Un exemplu simplificat arată astfel:

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

(mediu-mixt-seccomp.json)

Dar rețineți că trebuie să blocați toate apelurile despre care știți că nu vor fi folosite și care ar putea dăuna clusterului. O bază bună pentru alcătuirea unei liste este cea oficială Documentația Docker. Acesta explică în detaliu ce apeluri de sistem sunt blocate în profilul implicit și de ce.

Cu toate acestea, există o captură. Cu toate că SCMT_ACT_LOG susținut de nucleul Linux de la sfârșitul anului 2017, a intrat în ecosistemul Kubernetes doar relativ recent. Prin urmare, pentru a utiliza această metodă veți avea nevoie de un nucleu Linux 4.14 și o versiune runC nu mai mică v1.0.0-rc9.

Sfatul nr. 5: Un profil al modului de auditare pentru testarea în producție poate fi creat prin combinarea listelor albe și negre, iar toate excepțiile pot fi înregistrate.

6. Folosiți liste albe

Lista albă necesită efort suplimentar, deoarece trebuie să identificați fiecare apel de care ar putea avea nevoie aplicația, dar această abordare îmbunătățește foarte mult securitatea:

Este foarte recomandat să utilizați abordarea listei albe, deoarece este mai simplă și mai fiabilă. Lista neagră va trebui actualizată ori de câte ori se adaugă un apel de sistem potențial periculos (sau un semnal/opțiune periculoasă dacă se află pe lista neagră). În plus, este adesea posibil să se schimbe reprezentarea unui parametru fără a-i schimba esența și, prin urmare, să ocoliți restricțiile listei negre.

Pentru aplicațiile Go, am dezvoltat un instrument special care însoțește aplicația și colectează toate apelurile efectuate în timpul execuției. De exemplu, pentru următoarea aplicație:

package main

import "fmt"

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

... hai sa lansam gosystract după cum urmează:

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

... și obținem următorul rezultat:

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

Pentru moment, acesta este doar un exemplu - vor urma mai multe detalii despre instrumente.

Sfatul nr. 6: Permiteți doar acele apeluri de care aveți cu adevărat nevoie și blocați toate celelalte.

7. Puneti bazele potrivite (sau pregatiti-va pentru un comportament neasteptat)

Nucleul va impune profilul indiferent de ceea ce scrieți în el. Chiar dacă nu este exact ceea ce ți-ai dorit. De exemplu, dacă blocați accesul la apeluri precum exit sau exit_group, containerul nu se va putea închide corect și chiar și o simplă comandă ca echo hi închide-lo pe o perioadă nedeterminată. Ca rezultat, veți obține o utilizare ridicată a procesorului în cluster:

Seccomp în Kubernetes: 7 lucruri pe care trebuie să le știi de la bun început

În astfel de cazuri, o utilitate poate veni în ajutor strace - va arăta care ar putea fi problema:

Seccomp în Kubernetes: 7 lucruri pe care trebuie să le știi de la bun început
sudo strace -c -p 9331

Asigurați-vă că profilurile conțin toate apelurile de sistem de care are nevoie aplicația în timpul execuției.

Sfatul nr. 7: Acordați atenție detaliilor și asigurați-vă că toate apelurile de sistem necesare sunt incluse în lista albă.

Aceasta încheie prima parte a unei serii de articole despre utilizarea seccomp în Kubernetes în spiritul SecDevOps. În următoarele părți vom vorbi despre de ce este important acest lucru și despre cum să automatizăm procesul.

PS de la traducator

Citește și pe blogul nostru:

Sursa: www.habr.com

Adauga un comentariu