Seccomp at Kubernetes: 7 choses que vous devez savoir depuis le début

Remarque perev. : Présentation d'une traduction d'un article par un ingénieur principal en sécurité des applications de la société britannique ASOS.com. Avec elle, il commence une série de publications sur l'amélioration de la sécurité à Kubernetes grâce à l'utilisation de seccomp. Si les lecteurs aimeront l'introduction, nous suivrons l'auteur et continuerons avec ses futurs documents sur ce sujet.



Cet article est le premier d'une série de publications sur la façon de créer des profils seccomp dans l'esprit de SecDevOps sans recourir à la magie et à la sorcellerie. Dans la première partie, je parlerai des bases et des détails internes de l'implémentation de seccomp dans Kubernetes.

L'écosystème Kubernetes offre une grande variété de façons d'assurer la sécurité et l'isolement des conteneurs. Cet article concerne le mode de calcul sécurisé, également appelé seccomp . Son essence réside dans le filtrage des appels système disponibles pour les conteneurs à exécuter.

Pourquoi est-ce important? Un conteneur n'est qu'un processus exécuté sur une machine spécifique. Et il utilise le noyau sur un pied d'égalité avec d'autres applications. Si les conteneurs pouvaient effectuer des appels système, très vite les malwares en profiteraient pour contourner l'isolement du conteneur et affecter d'autres applications: intercepter des informations, modifier les paramètres système, etc.

Les profils seccomp déterminent quels appels système doivent être autorisés ou refusés. Le runtime du conteneur les active lors de son lancement, afin que le noyau puisse contrôler leur exécution. L'utilisation de tels profils vous permet de limiter le vecteur d'attaque et de réduire les dommages si un programme à l'intérieur du conteneur (c'est-à-dire vos dépendances ou leurs dépendances) commence à faire ce qu'il n'est pas autorisé à faire.

Comprendre les bases


Le profil de base seccomp comprend trois éléments: defaultAction , architectures (ou archMap ) et 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-seccomp.json )

defaultAction détermine le sort par défaut de tout appel système non spécifié dans la section syscalls . Pour simplifier la tâche, nous nous concentrons sur deux valeurs principales qui seront utilisées:

  • SCMP_ACT_ERRNO - bloque l'exécution d'un appel système,
  • SCMP_ACT_ALLOW - autorise.

La section architectures répertorie les architectures cibles. Ceci est important, car le filtre lui-même, appliqué au niveau du noyau, dépend des identifiants des appels système, et non de leurs noms enregistrés dans le profil. Avant utilisation, le runtime du conteneur les mappe à des identificateurs. Le fait est que les appels système peuvent avoir des ID complètement différents, selon l'architecture du système. Par exemple, l' recvfrom système recvfrom (utilisé pour obtenir des informations à partir d'un socket) a ID = 64 sur les systèmes x64 et ID = 517 sur x86. Vous trouverez ici une liste de tous les appels système pour les architectures x86-x64.

La section syscalls répertorie tous les appels système et indique que faire avec eux. Par exemple, vous pouvez créer une liste blanche en définissant defaultAction sur SCMP_ACT_ERRNO et affecter des appels à la section SCMP_ACT_ALLOW à SCMP_ACT_ALLOW . Ainsi, vous autorisez uniquement les appels enregistrés dans la section des syscalls et interdisez tous les autres. Pour la liste noire, vous devez changer les valeurs et les actions defaultAction par le contraire.

Maintenant, il faut dire quelques mots sur les nuances qui ne sont pas si évidentes. Veuillez noter que les recommandations ci-dessous proviennent du fait que vous déployez une gamme d'applications métier dans Kubernetes et il est important pour vous qu'elles fonctionnent avec le moins de privilèges.

1. AllowPrivilegeEscalation = false


Il existe un paramètre AllowPrivilegeEscalation dans le securityContext conteneur. S'il est défini sur false , les conteneurs commenceront avec le bit no_new_priv défini sur ( on ). La signification de ce paramètre est évidente d'après le nom: il ne permet pas au conteneur de démarrer de nouveaux processus avec des privilèges supérieurs à ceux dont il dispose.

Un effet secondaire de ce paramètre défini sur true (la valeur par défaut) est que le runtime du conteneur applique le profil seccomp au tout début du processus de démarrage. Ainsi, tous les appels système nécessaires pour démarrer les processus internes du runtime (par exemple, définir des identifiants utilisateur / groupe, supprimer certaines fonctionnalités) doivent être autorisés dans le profil.

Le conteneur qui effectue l' echo hi banal echo hi aura besoin des autorisations suivantes:

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

... au lieu de ceux-ci:

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

Mais là encore, pourquoi est-ce un problème? Personnellement, capset set_tid_address liste blanche les appels système suivants (s'ils ne sont pas vraiment nécessaires): capset , set_tid_address , setgid , setgroups et setuid . Cependant, la vraie difficulté est qu'en autorisant des processus sur lesquels vous n'avez absolument aucun contrôle, vous liez des profils à l'implémentation du runtime du conteneur. En d'autres termes, un jour, vous pouvez rencontrer le fait qu'après la mise à jour de l'environnement d'exécution du conteneur (par vous ou, plus probablement, par le fournisseur de services cloud), les conteneurs cesseront soudainement de démarrer.

Conseil n ° 1 : AllowPrivilegeEscaltion=false conteneurs avec AllowPrivilegeEscaltion=false . Cela réduira la taille des profils seccomp et les rendra moins sensibles aux modifications de l'exécution du conteneur.

2. Définition des profils seccomp au niveau du conteneur


Le profil seccomp peut être défini au niveau du pod:

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

... ou au niveau des conteneurs:

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

Veuillez noter que la syntaxe ci-dessus changera lorsque Kubernetes seccomp deviendra GA (cet événement est attendu dans la prochaine version de Kubernetes - 1.18 - approx. Transl.).

Peu de gens savent que Kubernetes a toujours eu un bogue qui entraînait l'application de profils seccomp au conteneur de pause . Le runtime compense partiellement cet inconvénient, mais ce conteneur ne disparaît pas des pods, car il est utilisé pour configurer leur infrastructure.

Le problème est que ce conteneur commence toujours par AllowPrivilegeEscalation=true , conduisant aux problèmes AllowPrivilegeEscalation=true au paragraphe 1, et cela ne peut pas être modifié.

En appliquant des profils seccomp au niveau du conteneur, vous évitez ce piège et pouvez créer un profil qui sera «affiné» pour un conteneur spécifique. Cela devra être fait jusqu'à ce que les développeurs corrigent le bogue et que la nouvelle version (peut-être 1.18?) Devienne disponible pour tout le monde.

Conseil n ° 2 : définissez les profils seccomp au niveau du conteneur.

Dans un sens pratique, cette règle sert généralement de réponse universelle à la question: «Pourquoi mon profil seccomp fonctionne-t-il avec docker run , mais il ne fonctionne pas après le déploiement dans un cluster Kubernetes?»

3. Utilisez runtime / default en dernier recours


Kubernetes a deux options pour les profils intégrés: runtime/default et docker/default . Les deux sont implémentés par le runtime du conteneur, pas Kubernetes. Par conséquent, ils peuvent différer selon le runtime utilisé et sa version.

En d'autres termes, en raison de la modification de l'exécution, le conteneur peut accéder à un autre ensemble d'appels système qu'il peut utiliser ou ne pas utiliser. La plupart des runtimes utilisent une implémentation Docker . Si vous souhaitez utiliser ce profil, assurez-vous qu'il vous convient.

Le profil docker/default est obsolète depuis Kubernetes 1.11, donc évitez de l'utiliser.

À mon avis, le profil d' runtime/default est parfait pour le but pour lequel il a été créé: pour protéger les utilisateurs contre les risques associés à l'exécution de la docker run sur leurs machines. Cependant, si nous parlons d'applications commerciales s'exécutant dans des clusters Kubernetes, j'oserais affirmer qu'un tel profil est trop ouvert et que les développeurs devraient se concentrer sur la création de profils pour leurs applications (ou types d'application).

Conseil n ° 3 : créez des profils seccomp pour des applications spécifiques. Si cela n'est pas possible, traitez les profils pour les types d'applications, par exemple, créez un profil avancé qui inclut toutes les API d'application Web Golang. Uniquement en dernier recours, utilisez runtime / default.

Dans de futures publications, je vous dirai comment créer des profils secccomp dans l'esprit de SecDevOps, les automatiser et les tester dans des pipelines. En d'autres termes, vous n'aurez aucune excuse pour ne pas passer aux profils pour des applications spécifiques.

4. Unfini n'est PAS une option


Dès le premier audit de sécurité de Kubernetes, il s'est avéré que seccomp était désactivé par défaut. Cela signifie que si vous ne spécifiez pas de PodSecurityPolicy qui l' PodSecurityPolicy dans le cluster, tous les pods pour lesquels le profil seccomp n'est pas défini fonctionneront en seccomp=unconfined .

Travailler dans ce mode signifie qu'une couche entière d'isolement est perdue, ce qui assure la protection du cluster. Cette approche n'est pas recommandée par les professionnels de la sécurité.

Conseil n ° 4 : aucun conteneur d'un cluster ne devrait fonctionner en seccomp=unconfined , en particulier dans les environnements de production.

5. "Mode audit"


Ce point n'est pas unique à Kubernetes, mais il tombe toujours dans la catégorie «ce que vous devez savoir avant de commencer».

Il se trouve que la création de profils seccomp a toujours été une entreprise délicate et reposait en grande partie sur des essais et des erreurs. Le fait est que les utilisateurs n'ont tout simplement pas la possibilité de les tester dans des environnements de production sans risquer de «laisser tomber» l'application.

Après l'avènement du noyau Linux 4.14, il est devenu possible d'exécuter des parties du profil en mode audit, en enregistrant des informations sur tous les appels système dans syslog, mais sans les bloquer. Vous pouvez activer ce mode à l'aide du paramètre SCMT_ACT_LOG :

SCMP_ACT_LOG : seccomp n'affectera pas le fonctionnement d'un thread effectuant un appel système s'il ne relève d'aucune règle du filtre, mais les informations sur l'appel système seront enregistrées.

Voici un exemple de stratégie d'utilisation de cette fonctionnalité:

  1. Autorisez les appels système nécessaires.
  2. Bloquer les systèmes d'appel connus pour ne pas être utiles.
  3. Enregistrez des informations sur tous les autres appels dans le journal.

Un exemple simplifié est le suivant:

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

Mais n'oubliez pas que vous devez bloquer tous les appels connus pour être inutilisés et qui pourraient potentiellement endommager le cluster. Une bonne base de cotation est la documentation officielle de Docker . Il explique en détail quels appels système sont bloqués dans le profil par défaut et pourquoi.

Cependant, il y a une prise. Bien que SCMT_ACT_LOG pris en charge par le noyau Linux depuis la fin de 2017, il n'est entré que récemment dans l'écosystème Kubernetes. Par conséquent, pour utiliser cette méthode, vous aurez besoin du noyau Linux 4.14 et de la version runC non inférieure à v1.0.0-rc9 .

Conseil n ° 5 : vous pouvez créer un profil de mode d'audit pour les tests en production en combinant des listes noires et blanches et enregistrer toutes les exceptions.

6. Utilisez des listes blanches


La création de listes blanches nécessite des efforts supplémentaires, car vous devez identifier chaque appel dont l'application peut avoir besoin, mais cette approche améliore considérablement la sécurité:

Il est fortement recommandé d'utiliser l'approche de liste blanche car elle est plus simple et plus fiable. La liste noire devra être mise à jour chaque fois qu'un appel système potentiellement dangereux (ou un indicateur / option dangereux s'ils figurent dans la liste noire) est ajouté. De plus, vous pouvez souvent changer la présentation d'un paramètre sans changer son essence et ainsi contourner les limites de la liste noire.

Pour les applications Go, j'ai développé un outil spécial qui accompagne l'application et collecte tous les appels effectués lors de l'exécution. Par exemple, pour l'application suivante:

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

... exécutez gosystract comme ceci:

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

... et obtenez le résultat suivant:

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

Jusqu'à présent, ce n'est qu'un exemple - les détails sur les outils seront plus loin.

Conseil n ° 6 : autorisez uniquement les appels dont vous avez vraiment besoin et bloquez tous les autres.

7. Poser les bases (ou se préparer à un comportement inattendu)


Le noyau surveillera la conformité avec le profil, peu importe ce que vous y avez enregistré. Même si ce n'est pas tout à fait ce que je voulais. Par exemple, si vous bloquez l'accès à des appels comme exit ou exit_group , le conteneur ne pourra pas terminer le travail correctement et même une simple commande comme echo hi suspendra pour une période indéfinie. En conséquence, vous obtiendrez une utilisation élevée du processeur dans le cluster:



Dans de tels cas, l'utilitaire strace peut venir à la rescousse - il montrera quel est le problème:


sudo strace -c -p 9331

Assurez-vous que les profils contiennent tous les appels système dont l'application a besoin lorsqu'elle est en cours d'exécution.

Astuce # 7 : Faites attention aux petites choses et assurez-vous que tous les appels système nécessaires sont inclus dans la liste blanche.

Avec cela, la première partie d'une série d'articles sur l'utilisation de seccomp dans Kubernetes dans l'esprit de SecDevOps prend fin. Dans les parties suivantes, nous expliquerons pourquoi cela est important et comment automatiser le processus.

PS du traducteur


Lisez aussi dans notre blog:

Source: https://habr.com/ru/post/fr481114/


All Articles