Seccomp bei Kubernetes: 7 Dinge, die Sie von Anfang an wissen müssen

Hinweis perev. : Präsentation einer Übersetzung eines Artikels eines erfahrenen Anwendungssicherheitsingenieurs bei der britischen Firma ASOS.com. Mit ihr beginnt er eine Reihe von Veröffentlichungen zur Verbesserung der Sicherheit in Kubernetes durch den Einsatz von Seccomp. Wenn die Leser die Einführung mögen, werden wir dem Autor folgen und mit seinen zukünftigen Materialien zu diesem Thema fortfahren.



Dieser Artikel ist der erste einer Reihe von Veröffentlichungen zum Erstellen von Geheimprofilen im Geiste von SecDevOps, ohne auf Magie und Hexerei zurückzugreifen. Im ersten Teil werde ich über die Grundlagen und internen Details der Implementierung von seccomp in Kubernetes sprechen.

Das Kubernetes-Ökosystem bietet eine Vielzahl von Möglichkeiten, um die Sicherheit und Isolierung von Containern zu gewährleisten. In diesem Artikel geht es um den sicheren Computermodus, auch als " seccomp" bezeichnet . Das Wesentliche besteht darin, Systemaufrufe zu filtern, die für die Ausführung von Containern verfügbar sind.

Warum ist das wichtig? Ein Container ist nur ein Prozess, der auf einem bestimmten Computer ausgeführt wird. Und es nutzt den Kernel auf einer Stufe mit anderen Anwendungen. Wenn die Container Systemaufrufe durchführen könnten, würde Malware dies sehr bald ausnutzen, um die Isolation des Containers zu umgehen und andere Anwendungen zu beeinflussen: Informationen abfangen, Systemeinstellungen ändern usw.

Die seccomp-Profile legen fest, welche Systemaufrufe zugelassen oder abgelehnt werden sollen. Die Container-Laufzeit aktiviert sie beim Start, damit der Kernel ihre Ausführung steuern kann. Die Verwendung solcher Profile ermöglicht es Ihnen, den Angriffsvektor einzuschränken und den Schaden zu reduzieren, wenn ein Programm im Container (dh Ihre Abhängigkeiten oder deren Abhängigkeiten) anfängt, das zu tun, was es nicht tun darf.

Grundlagen verstehen


Das seccomp-Basisprofil enthält drei Elemente: defaultAction , architectures (oder archMap ) und 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 bestimmt das Standardschicksal eines Systemaufrufs, der nicht im Abschnitt syscalls ist. Um die Aufgabe zu vereinfachen, konzentrieren wir uns auf zwei Hauptwerte, die verwendet werden:

  • SCMP_ACT_ERRNO - blockiert die Ausführung eines Systemaufrufs,
  • SCMP_ACT_ALLOW - erlaubt.

Der Abschnitt architectures listet die Zielarchitekturen auf. Dies ist wichtig, da der Filter selbst, der auf Kernelebene angewendet wird, von den Kennungen der Systemaufrufe und nicht von deren Namen abhängt, die im Profil registriert sind. Vor der Verwendung ordnet die Container-Laufzeit diese Bezeichnern zu. Der Punkt ist, dass Systemaufrufe abhängig von der Systemarchitektur völlig unterschiedliche IDs haben können. Beispielsweise hat der recvfrom (der zum recvfrom Informationen von einem Socket verwendet wird) auf x64-Systemen die ID = 64 und auf x86 die ID = 517. Hier finden Sie eine Liste aller Systemaufrufe für x86-x64-Architekturen.

Der Abschnitt syscalls listet alle Systemaufrufe auf und gibt an, was mit ihnen zu tun ist. Beispielsweise können Sie eine defaultAction SCMP_ACT_ERRNO , indem Sie defaultAction auf SCMP_ACT_ERRNO und SCMP_ACT_ERRNO Aufrufe für den Abschnitt SCMP_ACT_ALLOW . Sie erlauben also nur Anrufe, die im Bereich syscalls registriert sind, und verbieten alle anderen. Für die Blacklist sollten Sie die defaultAction Werte und -Aktionen auf das Gegenteil ändern.

Nun sollten ein paar Worte über die Nuancen gesagt werden, die nicht so offensichtlich sind. Beachten Sie, dass die folgenden Empfehlungen auf der Tatsache beruhen, dass Sie eine Reihe von Geschäftsanwendungen in Kubernetes bereitstellen, und es für Sie wichtig ist, dass diese mit den geringsten Berechtigungen arbeiten.

1. AllowPrivilegeEscalation = false


Im securityContext Containers befindet sich ein AllowPrivilegeEscalation Parameter. Wenn es auf false , beginnen die Container mit dem Bit no_new_priv das auf ( on ) gesetzt ist. Die Bedeutung dieses Parameters ist aus dem Namen ersichtlich: Der Container kann keine neuen Prozesse mit höheren Rechten als den vorhandenen starten.

Ein Nebeneffekt dieses Parametersatzes auf true (Standardwert) ist, dass die Container-Laufzeit das seccomp-Profil zu Beginn des Startvorgangs anwendet. Daher müssen alle Systemaufrufe, die zum Starten der internen Prozesse der Laufzeit erforderlich sind (z. B. Festlegen von Benutzer- / Gruppen-IDs, Löschen einiger Funktionen), im Profil zulässig sein.

Der Container, der das banale echo hi ausführt, benötigt die folgenden Berechtigungen:

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

... statt dieser:

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

Aber warum ist das ein Problem? Persönlich würde ich vermeiden, die folgenden Systemaufrufe auf die capset set_tid_address (wenn sie nicht wirklich benötigt werden): capset , set_tid_address , setgid , setgroups und setuid . Die eigentliche Schwierigkeit besteht jedoch darin, dass Sie Profile an die Implementierung der Containerlaufzeit binden, indem Sie Prozesse zulassen, über die Sie absolut keine Kontrolle haben. Mit anderen Worten, eines Tages kann es vorkommen, dass die Container nach dem Aktualisieren der Laufzeitumgebung des Containers (von Ihnen oder, wahrscheinlicher, vom Cloud-Dienstanbieter) plötzlich nicht mehr gestartet werden.

Tipp 1 : Führen Sie Container mit AllowPrivilegeEscaltion=false . Dies reduziert die Größe von Seccomp-Profilen und macht sie weniger anfällig für Änderungen in der Container-Laufzeit.

2. Festlegen von Seccomp-Profilen auf Containerebene


Das Seccomp-Profil kann auf Pod-Ebene festgelegt werden:

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

... oder auf Containerebene:

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

Bitte beachten Sie, dass sich die Syntax ändert, wenn Kubernetes seccomp zu GA wird (dieses Ereignis wird voraussichtlich in der nächsten Kubernetes-Version - 1.18 - ca. übersetzt).

Nur wenige Leute wissen, dass Kubernetes immer einen Fehler hatte , der dazu führte, dass Seccomp-Profile auf den Pausencontainer angewendet wurden. Die Laufzeit kompensiert diesen Nachteil teilweise, aber dieser Container verschwindet nicht aus den Pods, da er zur Konfiguration ihrer Infrastruktur verwendet wird.

Das Problem ist, dass dieser Container immer mit AllowPrivilegeEscalation=true beginnt, was zu den in Absatz 1 AllowPrivilegeEscalation=true Problemen führt. Dies kann nicht geändert werden.

Wenn Sie seccomp-Profile auf Containerebene anwenden, vermeiden Sie diese Überfüllung und können ein Profil erstellen, das für einen bestimmten Container „geschärft“ wird. Dies muss getan werden, bis die Entwickler den Fehler behoben haben und die neue Version (möglicherweise 1.18?) Für alle verfügbar ist.

Tipp 2 : Legen Sie Seccomp-Profile auf Containerebene fest.

In praktischer Hinsicht dient diese Regel in der Regel als universelle Antwort auf die Frage: "Warum wird mein Seccomp-Profil mit docker run , funktioniert aber nach der Bereitstellung in einem Kubernetes-Cluster nicht?"

3. Verwenden Sie Runtime / Default als letzten Ausweg


Kubernetes bietet zwei Optionen für integrierte Profile: runtime/default und docker/default . Beide werden von der Container-Laufzeit implementiert, nicht von Kubernetes. Sie können daher je nach verwendeter Laufzeit und Version unterschiedlich sein.

Mit anderen Worten kann der Container infolge einer Änderung der Laufzeit auf eine andere Gruppe von Systemaufrufen zugreifen, die er verwenden kann oder nicht. Die meisten Laufzeiten verwenden eine Docker-Implementierung . Wenn Sie dieses Profil verwenden möchten, stellen Sie sicher, dass es zu Ihnen passt.

Das docker/default ist seit Kubernetes 1.11 veraltet. Vermeiden Sie es daher, es zu verwenden.

Meiner Meinung nach ist das runtime/default perfekt für den Zweck, für den es erstellt wurde: um Benutzer vor den Risiken zu schützen, die mit der Ausführung des docker run auf ihren Computern verbunden sind. Wenn wir jedoch von Geschäftsanwendungen sprechen, die in Kubernetes-Clustern ausgeführt werden, würde ich behaupten, dass ein solches Profil zu offen ist und Entwickler sich darauf konzentrieren sollten, Profile für ihre Anwendungen (oder Anwendungstypen) zu erstellen.

Tipp 3 : Erstellen Sie separate Profile für bestimmte Anwendungen. Wenn dies nicht möglich ist, bearbeiten Sie Profile für Anwendungstypen. Erstellen Sie beispielsweise ein erweitertes Profil, das alle Golang-Webanwendungs-APIs enthält. Verwenden Sie Runtime / Default nur als letzten Ausweg.

In zukünftigen Veröffentlichungen werde ich Ihnen erklären, wie Sie secccomp-Profile im Sinne von SecDevOps erstellen, automatisieren und in Pipelines testen können. Mit anderen Worten, Sie haben keine Entschuldigung, nicht für bestimmte Anwendungen zu Profilen zu wechseln.

4. Unbeschränkt ist KEINE Option


Bei der ersten Kubernetes-Sicherheitsüberprüfung stellte sich heraus, dass seccomp standardmäßig deaktiviert war . Wenn Sie also keine PodSecurityPolicy angeben, die sie im Cluster PodSecurityPolicy , PodSecurityPolicy alle Pods, für die das seccomp-Profil nicht definiert ist, im seccomp=unconfined .

In diesem Modus zu arbeiten bedeutet, dass eine ganze Isolationsschicht verloren geht, was einen Cluster-Schutz bietet. Dieser Ansatz wird von Sicherheitsexperten nicht empfohlen.

Tipp 4 : Kein Container in einem Cluster sollte im seccomp=unconfined , insbesondere in Produktionsumgebungen.

5. "Überwachungsmodus"


Dieser Punkt gilt nicht nur für Kubernetes, sondern fällt auch in die Kategorie „Was Sie wissen sollten, bevor Sie beginnen“.

So kam es, dass das Erstellen von Seccomp-Profilen immer ein heikles Geschäft war und größtenteils auf Versuch und Irrtum beruhte. Tatsache ist, dass Benutzer einfach nicht die Möglichkeit haben, sie in Produktionsumgebungen zu testen, ohne das Risiko einzugehen, dass die Anwendung gelöscht wird.

Nach dem Aufkommen des Linux 4.14-Kernels wurde es möglich, Teile des Profils im Überwachungsmodus auszuführen und Informationen zu allen Systemaufrufen im Syslog aufzuzeichnen, diese jedoch nicht zu blockieren. Sie können diesen Modus mit dem Parameter SCMT_ACT_LOG aktivieren:

SCMP_ACT_LOG : seccomp hat keine Auswirkungen auf den Betrieb eines Threads, der einen Systemaufruf ausführt, wenn er keiner Filterregel unterliegt. Informationen zum Systemaufruf werden jedoch protokolliert.

Hier ist eine Beispielstrategie für die Verwendung dieser Funktion:

  1. Erforderliche Systemaufrufe zulassen
  2. Blockieren Sie Anrufsysteme, von denen bekannt ist, dass sie nicht nützlich sind.
  3. Notieren Sie Informationen zu allen anderen Anrufen im Protokoll.

Ein vereinfachtes Beispiel lautet wie folgt:

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

Denken Sie jedoch daran, dass Sie alle Anrufe blockieren müssen, von denen bekannt ist, dass sie nicht verwendet werden und dem Cluster möglicherweise Schaden zufügen können. Eine gute Grundlage für die Auflistung ist die offizielle Docker-Dokumentation . Es wird ausführlich erläutert, welche Systemaufrufe im Standardprofil blockiert sind und warum.

Es gibt jedoch einen Haken. Obwohl SCMT_ACT_LOG seit Ende 2017 vom Linux-Kernel unterstützt wird, ist es erst kürzlich in das Kubernetes-Ökosystem eingetreten. Zur Verwendung dieser Methode benötigen Sie daher den Linux 4.14-Kernel und die runC-Version, die nicht älter als v1.0.0-rc9 ist .

Tipp Nr. 5 : Sie können ein Überwachungsmodusprofil für Tests in der Produktion erstellen, indem Sie schwarze und weiße Listen kombinieren und alle Ausnahmen protokollieren.

6. Verwenden Sie Whitelists


Das Erstellen von Whitelists erfordert zusätzlichen Aufwand, da Sie jeden Anruf identifizieren müssen, den die Anwendung möglicherweise benötigt. Dieser Ansatz verbessert jedoch die Sicherheit erheblich:

Es wird dringend empfohlen, den Whitelisting-Ansatz zu verwenden, da dieser einfacher und zuverlässiger ist. Die Blacklist muss jedes Mal aktualisiert werden, wenn ein potenziell gefährlicher Systemaufruf (oder eine gefährliche Markierung / Option, wenn sie in der Blacklist enthalten sind) hinzugefügt wird. Darüber hinaus können Sie die Darstellung eines Parameters häufig ändern, ohne sein Wesen zu ändern, und so die Einschränkungen der Blacklist umgehen.

Für Go-Anwendungen habe ich ein spezielles Tool entwickelt, das die Anwendung begleitet und alle zur Laufzeit getätigten Aufrufe sammelt. Zum Beispiel für die folgende Anwendung:

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

... gosystract so gosystract :

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

... und erhalte folgendes Ergebnis:

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

Bisher ist dies nur ein Beispiel - Details zu den Werkzeugen werden weiter ausgeführt.

Tipp 6 : Lassen Sie nur Anrufe zu, die Sie wirklich benötigen, und blockieren Sie alle anderen.

7. Legen Sie den Grundstein (oder bereiten Sie sich auf unerwartetes Verhalten vor)


Der Kernel überwacht die Einhaltung des Profils, unabhängig davon, was Sie dort registriert haben. Auch wenn das nicht ganz das ist, was ich wollte. Wenn Sie beispielsweise den Zugriff auf Aufrufe wie exit oder exit_group , kann der Container den Auftrag nicht ordnungsgemäß exit_group , und selbst ein einfacher Befehl wie echo hi ihn auf unbestimmte Zeit aus. Als Ergebnis erhalten Sie eine hohe CPU-Auslastung im Cluster:



In solchen Fällen kann das Dienstprogramm strace - es zeigt, wo das Problem liegen kann:


sudo strace -c -p 9331

Stellen Sie sicher, dass die Profile alle Systemaufrufe enthalten, die die Anwendung während der Ausführung benötigt.

Tipp 7 : Achten Sie auf die kleinen Dinge und stellen Sie sicher, dass alle erforderlichen Systemaufrufe in der Positivliste enthalten sind.

Damit endet der erste Teil einer Artikelserie über die Verwendung von seccomp in Kubernetes im Sinne von SecDevOps. In den folgenden Abschnitten wird erläutert, warum dies wichtig ist und wie der Prozess automatisiert werden kann.

PS vom Übersetzer


Lesen Sie auch in unserem Blog:

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


All Articles