Nota del traduttore.: Presentiamo la traduzione dell'articolo di un senior application security engineer della britannica ASOS.com. Con questo inizia una serie di pubblicazioni dedicate all'aumento della sicurezza in Kubernetes attraverso l'uso di seccomp. Se l'introduzione piacerà ai lettori, seguiremo l'autore e continueremo con i suoi futuri materiali su questo tema.

Questo articolo è il primo di una serie di pubblicazioni su come creare profili seccomp nello spirito del SecDevOps, senza ricorrere a magie o incantesimi. Nella prima parte parlerò delle basi e dei dettagli interni dell'implementazione di seccomp in Kubernetes.
L'ecosistema Kubernetes offre una sufficiente varietà di modi per garantire la sicurezza e l'isolamento dei container. L'articolo è dedicato alla Modalità di Computazione Sicura, nota anche come seccomp. La sua essenza consiste nella filtrazione delle chiamate di sistema accessibili per l'esecuzione nei container.
Perché è importante? Un container è solo un processo avviato su una macchina specifica. Utilizza il kernel come le altre applicazioni. Se i container potessero eseguire qualsiasi chiamata di sistema, ben presto i malware ne approfitterebbero per bypassare l'isolamento del container e influenzare altre applicazioni: intercettare informazioni, modificare le impostazioni di sistema, ecc.
I profili seccomp definiscono quali chiamate di sistema devono essere consentite o vietate. L'ambiente di esecuzione del container li attiva durante il suo avvio, in modo che il kernel possa monitorarne l'esecuzione. L'uso di profili di questo tipo consente di limitare il vettore di attacco e ridurre i danni se un programma all'interno del container (cioè le tue dipendenze o le loro dipendenze) inizia a fare qualcosa che non gli è consentito.
Comprendere le basi
Il profilo seccomp di base include tre elementi: defaultAction, architectures (o archMap) e 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 determina l'azione predefinita per qualsiasi chiamata di sistema non specificata nella sezione syscalls. Per semplificare le cose, concentriamoci su due significati principali che saranno utilizzati:
-
SCMP_ACT_ERRNO– blocca l'esecuzione della chiamata di sistema, -
SCMP_ACT_ALLOW– consente.
Nella sezione architectures elencano le architetture target. Questo è importante poiché il filtro applicato a livello di kernel dipende dagli identificatori delle chiamate di sistema, e non dai loro nomi scritti nel profilo. Prima di applicarlo, l'ambiente di esecuzione del contenitore li mapperà con gli identificatori. Il punto è che le chiamate di sistema possono avere ID completamente diversi a seconda dell'architettura del sistema. Ad esempio, la chiamata di sistema recvfrom (utilizzata per ottenere informazioni da un socket) ha ID = 64 nei sistemi x64 e ID = 517 nei sistemi x86. puoi trovare un elenco di tutte le chiamate di sistema per le architetture x86-x64.
Nella sezione syscalls tutte le chiamate di sistema vengono elencate e viene specificato cosa fare con esse. Ad esempio, puoi creare una lista bianca impostando defaultAction con SCMP_ACT_ERRNO, e alle chiamate nella sezione syscalls assegnare SCMP_ACT_ALLOW. In questo modo, consenti solo le chiamate elencate nella sezione syscalls, e vieti tutte le altre. Per una lista nera, devi cambiare i valori defaultAction e le azioni al contrario.
Ora, vale la pena dire qualche parola sui dettagli che non sono così ovvi. Tieni presente che le raccomandazioni qui di seguito partono dal presupposto che tu stia distribuendo una serie di applicazioni aziendali in Kubernetes e che sia importante per te che funzionino con il minor numero di privilegi possibile.
1. AllowPrivilegeEscalation=false
In securityContext nel contenitore è presente un parametro AllowPrivilegeEscalation. Se è impostato su false, i contenitori verranno avviati con il bit impostato (on) . Il significato di questo parametro è chiaro dal nome: non consente al contenitore di avviare nuovi processi con privilegi superiori a quelli che ha.
L'effetto collaterale di questo parametro impostato su true (valore predefinito), è ciò che il contenitore runtime applica al profilo seccomp all'inizio del processo di avvio. Pertanto, tutte le chiamate di sistema necessarie per avviare i processi interni dell'ambiente di esecuzione (ad esempio, impostare gli identificatori utente/gruppo, scartare alcune capacità) devono essere consentite nel profilo.
Un contenitore che esegue un banale echo hi, avrà bisogno delle seguenti autorizzazioni:
{
"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"
}
]
}()
… invece di questi:
{
"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"
}
]
}()
Ma perché è un problema? Personalmente, eviterei di includere nell'elenco bianco le seguenti chiamate di sistema (se non sono assolutamente necessarie): capset, set_tid_address, setgid, setgroups e setuidTuttavia, la vera complessità sta nel fatto che, autorizzando processi che non controlli affatto, leggi i profili all'implementazione dell'ambiente di esecuzione dei container. In altre parole, può arrivare un momento in cui, dopo l'aggiornamento dell'ambiente di runtime del container (da parte tua o, più probabilmente, del fornitore del servizio cloud), i container smettono improvvisamente di funzionare.
Consiglio n.1: Esegui i container con AllowPrivilegeEscalation=false. Questo ridurrà la dimensione dei profili seccomp e li renderà meno sensibili alle variazioni dell'ambiente di esecuzione del container.
2. Impostare i profili seccomp a livello di container
Il profilo seccomp può essere impostato a livello di pod:
annotazioni:
seccomp.security.alpha.kubernetes.io/pod: "localhost/profile.json"… o a livello del contenitore:
annotazioni:
container.security.alpha.kubernetes.io/: "localhost/profile.json"Si noti che la sintassi sopra riportata cambierà quando seccomp di Kubernetes (questo evento è atteso nella prossima versione di Kubernetes — 1.18 — nota del traduttore).
Pochi sanno che in Kubernetes è sempre esistito , per cui i profili seccomp venivano applicati a . L'ambiente di esecuzione compensa parzialmente questa mancanza, tuttavia questo contenitore rimane nei pod, poiché viene utilizzato per configurare la loro infrastruttura.
Il problema è che questo contenitore viene sempre avviato con AllowPrivilegeEscalation=true, causando i problemi menzionati nel punto 1, e non è possibile modificarlo.
Applicando profili seccomp a livello del contenitore, eviti questa trappola e puoi creare un profilo specifico per ciascun contenitore. Questo è ciò che bisogna fare finché gli sviluppatori non risolvono il bug e una nuova versione (forse la 1.18?) diventi disponibile per tutti.
Consiglio n. 2: Imposta profili seccomp a livello del contenitore.
In un senso pratico, questa regola di solito funge da risposta universale alla domanda: "Perché il mio profilo seccomp funziona con docker run, ma non funziona dopo il deploy nel cluster Kubernetes?".
3. Utilizza runtime/default solo in casi estremi
In Kubernetes ci sono due varianti di profili predefiniti: runtime/default e docker/default. Entrambi sono implementati dall'ambiente di esecuzione del container, non da Kubernetes. Pertanto, possono variare a seconda dell'ambiente di esecuzione utilizzato e della sua versione.
In altre parole, a seguito del cambio di runtime, il container potrebbe avere accesso a un altro insieme di chiamate di sistema, che potrebbe utilizzare o meno. La maggior parte degli ambienti di esecuzione utilizza . Se desideri utilizzare questo profilo, assicurati che sia adatto alle tue esigenze.
Il profilo docker/default è considerato obsoleto a partire da Kubernetes 1.11, quindi evita il suo utilizzo.
a mio parere, il profilo runtime/default è perfettamente adatto agli scopi per cui è stato creato: proteggere gli utenti dai rischi associati all'esecuzione del comando docker run sulle loro macchine. Tuttavia, se parliamo di applicazioni aziendali che funzionano in cluster Kubernetes, oserei affermare che un profilo del genere è troppo aperto e gli sviluppatori dovrebbero concentrarsi sulla creazione di profili per le proprie applicazioni (o tipi di applicazioni).
Consiglio n. 3: Create profili seccomp per applicazioni specifiche. Se ciò non è possibile, lavorate su profili per categorie di applicazioni; ad esempio, create un profilo avanzato che includa tutte le API web dell'applicazione in Golang. Usate il profilo runtime/default solo come ultima risorsa.
Nelle future pubblicazioni parlerò di come creare profili seccomp in un'ottica SecDevOps, automatizzarli e testarli nei pipeline. In altre parole, non avrete più scuse per non passare a profili per applicazioni specifiche.
4. Unconfined NON è un'opzione
Da è emerso che per impostazione predefinita . Questo significa che se non specificate Politica di Sicurezza dei Pod, che lo abiliterà nel cluster, tutti i pod per i quali non è stato definito un profilo seccomp funzioneranno in modalità seccomp=unconfined.
Operare in questo modo significa perdere uno strato intero di isolamento che protegge il cluster. Questo approccio non è raccomandato dagli esperti di sicurezza.
Consiglio n. 4: Nessun container nel cluster dovrebbe funzionare in modalità seccomp=unconfined, specialmente in ambienti di produzione.
5. "Modalità audit"
Questo punto non è unico per Kubernetes, ma rientra comunque nella categoria "cose da sapere prima di iniziare".
È sempre stato difficile creare profili seccomp e in gran parte si basava sul metodo del tentativo e errore. Infatti, gli utenti non hanno semplicemente la possibilità di testarli in ambienti di produzione senza rischiare di "far cadere" l'applicazione.
Con l'arrivo del kernel Linux 4.14, è diventato possibile eseguire parti del profilo in modalità audit, registrando in syslog tutte le chiamate di sistema, senza però bloccarle. Questa modalità può essere attivata tramite il parametro SCMT_ACT_LOG:
SCMP_ACT_LOG: seccomp non influirà sul thread che esegue la chiamata di sistema, se non rientra in alcuna regola del filtro, tuttavia le informazioni sulla chiamata di sistema saranno registrate nel log.
Ecco una strategia tipica per l'utilizzo di questa possibilità:
- Consentire le chiamate di sistema necessarie.
- Bloccare le chiamate di sistema che si sa non saranno utili.
- Registrare tutte le altre chiamate in un log.
Un esempio semplificato appare come segue:
{
"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"
}
]
}()
Ma ricorda che è necessario bloccare tutte le chiamate di cui si sa che non verranno utilizzate e che potrebbero potenzialmente danneggiare il cluster. Una buona base per stilare l'elenco è la documentazione ufficiale . Essa spiega in dettaglio quali chiamate di sistema sono bloccate nel profilo predefinito e perché.
Tuttavia, c'è un trucco. Anche se SCMT_ACT_LOG è supportato dal kernel di Linux dal 2017, è entrato nell'ecosistema Kubernetes solo recentemente. Pertanto, per utilizzare questo metodo sono necessari il kernel di Linux 4.14 e runC versione non inferiore a .
Suggerimento n. 5: Il profilo della modalità audit per il testing in produzione può essere creato combinando liste nere e liste bianche, registrando tutte le eccezioni nel log.
6. Utilizzare liste bianche
La creazione di liste bianche richiede ulteriori sforzi, poiché è necessario identificare ogni chiamata che l'applicazione potrebbe necessitare, tuttavia questo approccio aumenta notevolmente la sicurezza:
Si raccomanda vivamente di utilizzare un approccio basato su liste bianche, poiché è più semplice e affidabile. La lista nera deve essere aggiornata ogni volta che viene aggiunta una chiamata di sistema potenzialmente pericolosa (o un flag/opzione pericolosi, se sono nella lista nera). Inoltre, spesso è possibile modificare la rappresentazione di un parametro senza alterarne il contenuto, aggirando così le restrizioni della lista nera.
Per le applicazioni in Go, ho sviluppato uno strumento speciale che accompagna l'applicazione e raccoglie tutte le chiamate effettuate durante l'esecuzione. Ad esempio, per la seguente applicazione:
package main
import "fmt"
func main() {
fmt.Println("test")
} … avviamo gosystract così:
go install https://github.com/pjbgf/gosystract
gosystract --template='{{- range . }}{{printf "%s", .Name}}{{- end}}' application-path… e otterremo il seguente risultato:
"sched_yield",
"futex",
"write",
"mmap",
"exit_group",
"madvise",
"rt_sigprocmask",
"getpid",
"gettid",
"tgkill",
"rt_sigaction",
"read",
"getpgrp",
"arch_prctl",Questo è solo un esempio — i dettagli dello strumento arriveranno più avanti.
Consiglio #6: Consentite solo le chiamate che vi servono davvero e bloccate tutte le altre.
7. Stabilite basi solide (o preparatevi a comportamenti imprevisti)
Il kernel monitorerà il rispetto del profilo indipendentemente da ciò che avete definito. Anche se non è esattamente ciò che desideravate. Ad esempio, se bloccate l'accesso a chiamate come exit o exit_group, il contenitore non riuscirà a terminare correttamente e anche un semplice comando come echo hi indefinitamente. Di conseguenza, avrete un carico CPU elevato nel cluster:

In questi casi, un'utilità potrebbe tornarvi utile strace — mostrerà qual è il problema:

sudo strace -c -p 9331
Assicurati che i profili contengano tutte le chiamate di sistema necessarie all'applicazione durante l'esecuzione.
Consiglio №7: Fai attenzione ai dettagli e verifica che tutte le chiamate di sistema necessarie siano incluse nella whitelist.
Con questo, la prima parte del ciclo di articoli sull'uso di seccomp in Kubernetes nello spirito del SecDevOps giunge al termine. Nelle prossime parti discuteremo del perché sia importante e di come automatizzare il processo.
P.S. dal traduttore
Leggete anche nel nostro blog:
- «»;
- «»;
- «»;
- «».
Fonte: habr.com
