Anmerkung des Übersetzers.: Wir präsentieren die Übersetzung des Artikels eines Senior Application Security Engineers des britischen Unternehmens ASOS.com. Mit diesem Artikel beginnt er eine Reihe von Veröffentlichungen, die sich mit der Verbesserung der Sicherheit in Kubernetes durch die Verwendung von seccomp befassen. Wenn das Einführungsstück den Lesern gefällt, werden wir dem Autor folgen und mit seinen zukünftigen Materialien zu diesem Thema fortfahren.

Dieser Artikel ist der erste aus einer Serie von Veröffentlichungen über die Erstellung von seccomp-Profilen im Geiste von SecDevOps, ohne auf Magie und Zauberei zurückzugreifen. Im ersten Teil werde ich die Grundlagen und technischen Details der Implementierung von seccomp in Kubernetes erläutern.
Das Kubernetes-Ökosystem bietet eine ausreichende Vielfalt an Möglichkeiten zur Gewährleistung der Sicherheit und Isolation von Containern. Der Artikel widmet sich dem Secure Computing Mode, der auch bekannt ist als seccomp. Sein Zweck besteht darin, die Systemaufrufe zu filtern, die für die Ausführung von Containern verfügbar sind.
Warum ist das wichtig? Ein Container ist lediglich ein Prozess, der auf einer bestimmten Maschine ausgeführt wird. Er verwendet den Kernel wie andere Anwendungen. Wenn Container beliebige Systemaufrufe ausführen könnten, würden schnell schadhafte Programme dies ausnutzen, um die Isolierung des Containers zu umgehen und auf andere Anwendungen einzuwirken: Informationen abzufangen, Systemkonfigurationen zu ändern usw.
Seccomp-Profile definieren, welche Systemaufrufe erlaubt oder verboten sind. Die Container-Ausführungsumgebung aktiviert sie beim Starten, damit der Kernel die Ausführung überwachen kann. Der Einsatz solcher Profile hilft, den Angriffsvektor einzuschränken und den Schaden zu begrenzen, falls ein Programm innerhalb des Containers (das heißt Ihre Abhängigkeiten oder deren Abhängigkeiten) Dinge tut, die ihm nicht erlaubt sind.
Lass uns mit den Grundlagen vertrautmachen.
Das Basis-secomp-Profil umfasst 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"
}
]
}()
defaultAction bestimmt das Standardverhalten eines Systemaufrufs, der im Abschnitt nicht angegeben ist, syscalls. Um die Aufgabe zu vereinfachen, konzentrieren wir uns auf zwei grundlegende Werte, die verwendet werden:
-
SCMP_ACT_ERRNO— blockiert die Ausführung des Systemaufrufs, -
SCMP_ACT_ALLOW— erlaubt.
Im Abschnitt architectures werden die Zielarchitekturen aufgelistet. Dies ist wichtig, da der Filter, der auf Kernel-Ebene angewendet wird, von den Identifikatoren der Systemaufrufe abhängt und nicht von ihren Namen, die im Profil angegeben sind. Bevor die Container-Ausführungsumgebung diese anwendet, vergleicht sie sie mit den Identifikatoren. Der Sinn ist, dass Systemaufrufe ganz unterschiedliche IDs haben können, je nach Systemarchitektur. Zum Beispiel hat der Systemaufruf recvfrom (wird verwendet, um Informationen von einem Socket zu erhalten) die ID = 64 in x64-Systemen und die ID = 517 in x86. finden Sie eine Liste aller Systemaufrufe für die Architekturen x86-x64.
Im Abschnitt syscalls werden alle Systemaufrufe aufgelistet und es wird angegeben, was mit ihnen zu tun ist. Beispielsweise kann man eine Whitelist erstellen, indem man defaultAction auf SCMP_ACT_ERRNO, und den Aufrufen im Abschnitt syscalls die SCMP_ACT_ALLOWzuweist. Damit erlauben Sie nur die im Abschnitt syscallsaufgeführten Aufrufe und verbieten alle anderen. Für eine Blacklist sollten die Werte defaultAction und die Aktionen umgekehrt werden.
Jetzt sollten wir ein paar Worte über Nuancen verlieren, die nicht so offensichtlich sind. Beachten Sie, dass die folgenden Empfehlungen davon ausgehen, dass Sie eine Reihe von Geschäftsapplikationen in Kubernetes aufsetzen und es wichtig ist, dass sie mit minimalen Rechten arbeiten.
1. AllowPrivilegeEscalation=false
Im securityContext des Containers gibt es einen Parameter AllowPrivilegeEscalation. Wenn er auf falsegesetzt ist, werden die Container mit dem gesetzten (on) Bit . Der Sinn dieses Parameters ist aus dem Namen ersichtlich: Er erlaubt es dem Container nicht, neue Prozesse mit höheren Rechten als den vorhandenen zu starten.
Ein Nebeneffekt dieses Parameters, der auf true gesetzt ist (Standardwert), ist, dass die Runtime des Containers das seccomp-Profil zu Beginn des Startvorgangs anwendet. Daher müssen alle Systemaufrufe, die für den Start interner Prozesse der Ausführungsumgebung erforderlich sind (z. B. Festlegen von Benutzer-/Gruppen-IDs, Wegwerfen bestimmter Berechtigungen), im Profil erlaubt sein.
Ein Container, der einfaches echo hi, es werden die folgenden Berechtigungen benötigt:
{
"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"
}
]
}()
… 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"
}
]
}()
Aber warum ist das wieder ein Problem? Persönlich würde ich die folgenden Systemaufrufe (es sei denn, es besteht wirklich Bedarf) nicht in die Whitelist aufnehmen: capset, set_tid_address, setgid, setgroups und setuid. Das eigentliche Problem besteht darin, dass Sie, indem Sie Prozessen erlauben, über die Sie keine Kontrolle haben, Profile an die Ausführungsumgebung von Containern binden. Mit anderen Worten, es kann passieren, dass Ihre Container plötzlich nicht mehr starten, nachdem die Runtime-Umgebung des Containers (von Ihnen oder, wahrscheinlicher, von Ihrem Cloud-Anbieter) aktualisiert wurde.
Tipp Nr. 1: Starten Sie Container mit AllowPrivilegeEscaltion=false. Das reduziert die Größe der Seccomp-Profile und macht sie weniger anfällig für Änderungen in der Ausführungsumgebung des Containers.
2. Festlegung von Seccomp-Profilen auf Container-Ebene
Das Seccomp-Profil kann auf Pod-Ebene festgelegt werden:
annotations:
seccomp.security.alpha.kubernetes.io/pod: "localhost/profile.json"… oder auf Container-Ebene:
annotations:
container.security.alpha.kubernetes.io/<container-name>: "localhost/profile.json"Bitte beachten Sie, dass die oben angegebene Syntax sich ändert, wenn Kubernetes Seccomp (dieses Ereignis wird bereits im nächsten Kubernetes-Release — 1.18 — erwartet — Anm. d. Übers).
Wenige wissen, dass es in Kubernetes immer einen , aufgrund dessen Seccomp-Profile auf . Die Ausführungsumgebung kompensiert teilweise diesen Nachteil, jedoch verschwindet dieser Container nicht aus den Pods, da er zur Konfiguration ihrer Infrastruktur verwendet wird.
Das Problem ist, dass dieser Container immer mit AllowPrivilegeEscalation=truegestart wird, was zu den in Punkt 1 genannten Problemen führt, und das kann nicht geändert werden.
Durch die Anwendung von Seccomp-Profilen auf Container-Ebene umgehst du diese Falle und kannst ein Profil erstellen, das speziell auf einen bestimmten Container zugeschnitten ist. Das wird notwendig sein, bis die Entwickler den Fehler beheben und eine neue Version (vielleicht 1.18?) für alle zur Verfügung steht.
Tipp Nr. 2: Setze Seccomp-Profile auf Container-Ebene fest.
In praktischer Hinsicht dient diese Regel normalerweise als universelle Antwort auf die Frage: „Warum funktioniert mein Seccomp-Profil mit docker run, aber nicht nach der Bereitstellung im Kubernetes-Cluster?“
3. Verwende runtime/default nur im äußersten Notfall
In Kubernetes gibt es zwei Varianten von integrierten Profilen: runtime/default und docker/default. Beide werden von der Container-Ausführungsumgebung und nicht von Kubernetes implementiert. Daher können sie abhängig von der verwendeten Ausführungsumgebung und deren Version variieren.
Anders gesagt, durch den Wechsel der Runtime kann der Container auf eine andere Reihe von Systemaufrufen zugreifen, die er nutzen kann oder nicht. Die meisten Ausführungsumgebungen verwenden . Wenn du dieses Profil verwenden möchtest, stelle sicher, dass es für dich geeignet ist.
Das Profil docker/default ist seit Kubernetes 1.11 veraltet, daher solltest du vermeiden, es zu verwenden.
Meiner Meinung nach ist das Profil runtime/default sehr gut geeignet für die Zwecke, für die es entwickelt wurde: Schutz der Benutzer vor den Risiken, die mit der Ausführung von Kommandos docker run auf ihren Maschinen verbunden sind. Wenn es jedoch um Geschäftsanwendungen in Kubernetes-Clustern geht, würde ich wagen zu behaupten, dass ein solches Profil zu offen ist und die Entwickler sich darauf konzentrieren sollten, Profile für ihre Anwendungen (oder Anwendungstypen) zu erstellen.
Tipp Nr. 3: Erstelle Seccomp-Profile für spezifische Anwendungen. Wenn dies nicht möglich ist, beschäftige dich mit Profilen für Anwendungstypen, wie zum Beispiel ein erweitertes Profil, das alle Web-APIs einer Anwendung in Golang umfasst. Verwende runtime/default nur als letztes Mittel.
In zukünftigen Publikationen werde ich erklären, wie man seccomp-Profile im Geiste von SecDevOps erstellt, sie automatisiert und in Pipelines testet. Mit anderen Worten, es gibt keine Ausreden mehr, nicht auf profiles für bestimmte Anwendungen umzusteigen.
4. Unconfined ist KEINE Option
Aus wurde festgestellt, dass standardmäßig . Das bedeutet, dass, wenn Sie nicht PodSecurityPolicyfestlegen, die es im Cluster aktiviert, alle Pods, für die kein seccomp-Profil definiert ist, im Modus seccomp=unconfined.
laufen. Der Betrieb in diesem Modus bedeutet, dass eine gesamte Isolationsebene verloren geht, die den Cluster schützt. Ein solcher Ansatz wird von Sicherheitsexperten nicht empfohlen.
Tipp Nr. 4: Kein Container im Cluster sollte im Modus seccomp=unconfinedlaufen, insbesondere nicht in Produktionsumgebungen.
5. "Audit-Modus"
Dieser Punkt ist nicht einzigartig für Kubernetes, fällt aber dennoch in die Kategorie "Was man wissen sollte, bevor man anfängt".
Es hat sich etabliert, dass die Erstellung von seccomp-Profilen immer eine schwierige Angelegenheit war und weitgehend auf dem Trial-and-Error-Prinzip basierte. Der Grund ist, dass Benutzer nicht die Möglichkeit haben, sie in Produktionsumgebungen zu testen, ohne das Risiko einzugehen, die Anwendung zum Absturz zu bringen.
Mit dem Erscheinen des Linux-Kernels 4.14 wurde es möglich, Teile des Profils im Audit-Modus auszuführen, indem Informationen über alle Systemaufrufe in syslog aufgezeichnet wurden, ohne diese zu blockieren. Dieser Modus kann mit dem Parameter aktiviert werden, SCMT_ACT_LOG:
SCMP_ACT_LOG: seccomp wird den Betrieb des Threads, der den Systemaufruf durchführt, nicht beeinflussen, wenn er nicht unter eine Regel des Filters fällt, jedoch wird die Information über den Systemaufruf im Protokoll festgehalten.
Hier ist eine typische Strategie zur Nutzung dieser Möglichkeit:
- Erlauben Sie die notwendigen Systemaufrufe.
- Blockieren Sie Systemaufrufe, von denen bekannt ist, dass sie nicht benötigt werden.
- Sämtliche anderen Aufrufe in das Protokoll eintragen.
Ein vereinfachtes Beispiel sieht wie folgt aus:
{
"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"
}
]
}()
Aber denken Sie daran, dass Sie alle Systemaufrufe blockieren müssen, von denen bekannt ist, dass sie nicht verwendet werden, und die potenziell dem Cluster schaden könnten. Eine gute Grundlage für die Erstellung der Liste ist die offizielle . Dort wird ausführlich erklärt, welche Systemaufrufe im Standardprofil blockiert sind und warum.
Es gibt jedoch einen Haken. Obwohl SCMT_ACT_LOG es seit Ende 2017 vom Linux-Kernel unterstützt wird, ist es erst relativ kürzlich in das Kubernetes-Ökosystem eingetreten. Daher benötigen Sie für die Verwendung dieser Methode ein Linux-Kernel 4.14 und eine runC-Version nicht niedriger als .
Tipp Nr. 5: Ein Audit-Modus-Profil für Tests in der Produktion kann erstellt werden, indem Sie schwarzen und weißen Listen kombinieren und alle Ausnahmen protokollieren.
6. Verwenden Sie weiße Listen
Die Erstellung weißer Listen erfordert zusätzlichen Aufwand, da jeder Aufruf identifiziert werden muss, der für die Anwendung erforderlich sein könnte. Dieser Ansatz erhöht jedoch erheblich die Sicherheit:
Es wird dringend empfohlen, einen Ansatz mit weißen Listen zu verwenden, da dieser einfacher und zuverlässiger ist. Bei der schwarzen Liste muss jedes Mal aktualisiert werden, wenn ein potenziell gefährlicher Systemaufruf (oder ein gefährliches Flag/Option, wenn er auf der schwarzen Liste steht) hinzugefügt wird. Darüber hinaus kann oft die Darstellung des Parameters geändert werden, ohne dessen Wesensart zu verändern und so die Einschränkungen der schwarzen Liste zu umgehen.
Für Anwendungen in Go habe ich ein spezielles Tool entwickelt, das die Anwendung begleitet und alle Aufrufe sammelt, die während der Ausführung erfolgen. Zum Beispiel für die folgende Anwendung:
package main
import "fmt"
func main() {
fmt.Println("test")
} … starten wir gosystract so:
go install https://github.com/pjbgf/gosystract
gosystract --template='{{- range . }}{{printf "%s", .Name}}{{- end}}' application-path… und wir erhalten 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 zum Toolset folgen weiterhin.
Tipp Nr. 6: Erlauben Sie nur die Aufrufe, die Sie tatsächlich benötigen, und blockieren Sie alle anderen.
7. Legen Sie die richtigen Grundlagen (oder bereiten Sie sich auf unerwartetes Verhalten vor)
Der Kernel wird die Einhaltung des Profils überwachen, unabhängig davon, was Sie darin festgelegt haben. Selbst wenn das nicht ganz das ist, was gewünscht war. Zum Beispiel, wenn der Zugriff auf Aufrufe wie exit oder exit_group, kann der Container nicht ordnungsgemäß heruntergefahren werden und selbst ein einfacher Befehl wie echo hi für unbestimmte Zeit aufhängen. Infolgedessen erhalten Sie eine hohe CPU-Auslastung im Cluster:

In solchen Fällen kann Ihnen das Tool strace eine Vorstellung davon geben, wo das Problem liegen könnte:

sudo strace -c -p 9331
Stellen Sie sicher, dass die Profile alle Systemaufrufe enthalten, die die Anwendung während des Betriebs benötigt.
Tipp Nr. 7: Achten Sie auf die Details und überprüfen Sie, dass alle erforderlichen Systemaufrufe auf der Whitelist stehen.
Damit endet der erste Teil des Artikelzyklus über die Verwendung von seccomp in Kubernetes im Sinne von SecDevOps. In den folgenden Teilen werden wir darüber sprechen, warum das wichtig ist und wie man den Prozess automatisieren kann.
P.S. vom Übersetzer
Lesen Sie auch in unserem Blog:
- «»;
- «»;
- «»;
- «».
Quelle: habr.com
