Hinweis.: Wir präsentieren die Übersetzung eines Artikels des leitenden Anwendungssicherheitsingenieurs des britischen Unternehmens ASOS.com. Mit diesem Artikel beginnt er eine Reihe von Veröffentlichungen, die sich der Sicherheit in Kubernetes widmen und sich auf den Einsatz von seccomp konzentrieren. Wenn die Einleitung den Lesern gefällt, werden wir dem Autor folgen und mit seinen künftigen Materialien zu diesem Thema fortfahren.

Dieser Artikel ist der erste in einer Reihe von Veröffentlichungen darüber, wie man seccomp-Profile im Geiste von SecDevOps erstellt, ohne auf Magie und Zauberei zurückzugreifen. Im ersten Teil werde ich die Grundlagen und die internen Details der Implementierung von seccomp in Kubernetes erläutern.
Das Kubernetes-Ökosystem bietet eine ausreichende Vielfalt an Möglichkeiten zur Sicherstellung der Sicherheit und Isolation von Containern. Der Artikel behandelt den Secure Computing Mode, auch bekannt als seccomp. Seine Hauptidee besteht darin, die für Container verfügbaren Systemaufrufe zu filtern.
Warum ist das wichtig? Ein Container ist lediglich ein Prozess, der auf einer bestimmten Maschine ausgeführt wird. Er nutzt den Kernel ebenso wie andere Anwendungen. Wenn Container beliebige Systemaufrufe tätigen könnten, würden bösartige Programme dies schnell ausnutzen, um die Isolation des Containers zu umgehen und andere Anwendungen zu beeinflussen: Informationen abzufangen, Systemeinstellungen zu ändern usw.
Seccomp-Profile definieren, welche Systemaufrufe erlaubt oder verboten werden sollen. Die Runtime-Umgebung des Containers aktiviert diese beim Start, damit der Kernel deren Ausführung überwachen kann. Der Einsatz solcher Profile hilft, den Angriffswinkel zu reduzieren und den Schaden zu minimieren, falls ein Programm innerhalb des Containers (also Ihre Abhängigkeiten oder deren Abhängigkeiten) beginnt, unerlaubte Aktionen auszuführen.
Lernen Sie die Grundlagen kennen
Das Basis-seccomp-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 Standardschicksal jedes Systemaufrufs, der nicht im Abschnitt angegeben ist syscalls. Um es einfacher zu machen, konzentrieren wir uns auf zwei Hauptwerte, die verwendet werden:
-
SCMP_ACT_ERRNO— blockiert die Ausführung des Systemaufrufs, -
SCMP_ACT_ALLOW— erlaubt.
Im Abschnitt architectures die Zielarchitekturen werden aufgelistet. Dies ist wichtig, da der angewandte Filter auf Kernel-Ebene von den Identifikatoren der Systemaufrufe abhängt und nicht von ihren in den Profilen angegebenen Namen. Vor der Anwendung wird die Container-Ausführungsumgebung sie mit den Identifikatoren abgleichen. Der Sinn dahinter ist, dass Systemaufrufe je nach Systemarchitektur ganz andere IDs haben können. Zum Beispiel hat der Systemaufruf recvfrom (zum Abrufen von Informationen von einem Socket verwendet) die ID = 64 in x64-Systemen und die ID = 517 in x86. Sie können eine Liste aller Systemaufrufe für die Architekturen x86-x64 finden.
Im Abschnitt syscalls Alle Systemaufrufe werden aufgeführt, und es wird angegeben, was damit zu tun ist. Zum Beispiel können Sie eine Whitelist erstellen, indem Sie defaultAction findet man SCMP_ACT_ERRNOund den Aufrufen im Abschnitt syscalls einen Wert zuweisen. SCMP_ACT_ALLOWDamit erlauben Sie nur die im Abschnitt aufgeführten Aufrufe syscalls, und verbieten alle anderen. Für die Blacklist sollten die Werte defaultAction und die Aktionen umgekehrt werden.
Jetzt sollten wir einige Worte zu Nuancen sagen, die nicht so offensichtlich sind. Beachten Sie, dass die folgenden Empfehlungen davon ausgehen, dass Sie eine Reihe von Geschäftsapplikationen in Kubernetes bereitstellen und es wichtig ist, dass sie mit minimalen Rechten arbeiten.
1. AllowPrivilegeEscalation=false
In securityContext Der Container hat einen Parameter AllowPrivilegeEscalation.Wenn er auf falsegesetzt ist, werden Container mit dem gesetzten (on) Bit . Der Sinn dieses Parameters ist aus dem Namen offensichtlich: Er erlaubt dem Container nicht, neue Prozesse mit höheren Rechten als den eigenen zu starten.
Ein Nebeneffekt dieses Parameters, der auf true (der Standardwert) bedeutet, dass der Runtime-Container das seccomp-Profil zu Beginn des Startvorgangs anwendet. Daher müssen alle Systemaufrufe, die zum Starten interner Prozesse der Laufzeitumgebung erforderlich sind (z. B. das Setzen von Benutzer-/Gruppen-IDs, das Abweisen bestimmter Berechtigungen), im Profil erlaubt sein.
Ein Container, der den Befehl echo hiausführt, benötigt folgende 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"
}
]
}()
… anstelle 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 ein Problem? Persönlich würde ich davon absehen, die folgenden Systemaufrufe auf die Whitelist zu setzen (es sei denn, es gibt wirklich einen Bedarf): capset, set_tid_address, setgid, setgroups und setuid. Die eigentliche Herausforderung besteht jedoch darin, dass Sie durch das Zulassen von Prozessen, die Sie absolut nicht kontrollieren, die Profile an die Implementierung der Containerlaufzeit binden. Mit anderen Worten, es könnte sein, dass nach dem Update der Containerlaufzeit (durch Sie oder wahrscheinlicher durch Ihren Cloud-Anbieter) die Container plötzlich nicht mehr starten.
Tipp Nr. 1: Starten Sie Container mit AllowPrivilegeEscaltion=false. Dadurch wird die Größe der Seccomp-Profile reduziert und sie werden weniger anfällig für Änderungen in der Containerlaufzeit.
2. Definieren von Seccomp-Profilen auf Pod-Ebene
Das Seccomp-Profil kann auf der Pod-Ebene definiert werden:
Anmerkungen:
seccomp.security.alpha.kubernetes.io/pod: "localhost/profile.json"… oder auf Container-Ebene:
Anmerkungen:
container.security.alpha.kubernetes.io/: "localhost/profile.json"Bitte beachten Sie, dass sich die oben genannte Syntax ändern wird, wenn Kubernetes seccomp (dieses Ereignis wird im nächsten Kubernetes-Release – 1.18 – erwartet – Anm. d. Übers.).
Wenig bekannt ist, dass es in Kubernetes immer , aufgrund dessen seccomp-Profile auf angewendet wurden. Die Ausführungsumgebung kompensiert diesen Nachteil teilweise, jedoch verschwindet dieser Container nicht aus Pods, da er zur Einrichtung ihrer Infrastruktur verwendet wird.
Das Problem ist, dass dieser Container immer mit AllowPrivilegeEscalation=truegestart wird, was zu den im Punkt 1 genannten Problemen führt, und dies kann nicht geändert werden.
Durch die Anwendung von seccomp-Profilen auf Container-Ebene umgehen Sie diese Falle und können ein Profil erstellen, das auf einen spezifischen Container „zugeschnitten“ ist. Das wird notwendig sein, solange die Entwickler den Bug nicht beheben und eine neue Version (vielleicht 1.18?) für alle verfügbar ist.
Tipp Nr. 2: Legen Sie 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, funktioniert aber nicht nach der Bereitstellung im Kubernetes-Cluster?".
3. Verwenden Sie runtime/default nur im äußersten Notfall
In Kubernetes gibt es zwei eingebaute Profiloptionen: runtime/default und docker/default. Beide werden von der Container-Laufzeitumgebung und nicht von Kubernetes implementiert. Daher können sie je nach verwendeter Laufzeitumgebung und ihrer Version variieren.
Mit anderen Worten, durch den Wechsel der Container-Laufzeit kann der Container auf einen anderen Satz von Systemaufrufen zugreifen, die er nutzen kann oder nicht. Die meisten Laufzeitumgebungen verwenden . Wenn Sie dieses Profil verwenden möchten, stellen Sie sicher, dass es für Ihre Bedürfnisse geeignet ist.
Das Profil docker/default wird seit Kubernetes 1.11 als veraltet betrachtet, daher sollten Sie die Verwendung vermeiden.
Meiner Meinung nach eignet sich das Profil runtime/default hervorragend für die Zielsetzungen, für die es entwickelt wurde: den Benutzern Schutz vor Risiken im Zusammenhang mit der Ausführung von Befehlen zu bieten. docker run auf ihren Maschinen. Wenn es jedoch um Geschäftsapplikationen geht, die in Kubernetes-Clustern laufen, würde ich sogar 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: Erstellen Sie seccomp-Profile für spezifische Anwendungen. Wenn das nicht möglich ist, arbeiten Sie an Profilen für Anwendungstypen, zum Beispiel erstellen Sie ein erweitertes Profil, das alle Web-APIs der Anwendung in Golang umfasst. Verwenden Sie runtime/default nur als letztes Mittel.
In zukünftigen Veröffentlichungen werde ich erklären, wie man seccomp-Profile im Geiste von SecDevOps erstellt, automatisiert und in Pipelines testet. Mit anderen Worten, Sie werden keine Ausreden mehr haben, um nicht auf anwendungsspezifische Profile umzusteigen.
4. Unconfined ist KEINE Option
Von wurde festgestellt, dass standardmäßig Das bedeutet, dass, wenn Sie nicht festlegen PodSecurityPolicy, das es im Cluster aktiviert, alle Pods, für die kein seccomp-Profil definiert ist, im Modus seccomp=unconfined.
Das Arbeiten in diesem Modus bedeutet, dass eine ganze Schicht der Isolation 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=unconfined, insbesondere nicht in Produktionsumgebungen, betrieben werden.
5. „Audit-Modus“
Dieser Punkt ist nicht einzigartig für Kubernetes, fällt jedoch dennoch in die Kategorie „was man wissen sollte, bevor man beginnt“.
Es hat sich so ergeben, dass das Erstellen von seccomp-Profilen immer eine Herausforderung war und weitgehend auf dem Versuch-und-Irrtum-Prinzip basierte. Das Problem besteht darin, dass Benutzer diese in Produktionsumgebungen nicht testen können, ohne das Risiko einzugehen, die Anwendung „abzustürzen“.
Mit dem Erscheinen des Linux-Kernels 4.14 wurde es möglich, Teile des Profils im Auditing-Modus auszuführen, wobei alle Systemaufrufe im Syslog protokolliert werden, jedoch nicht blockiert werden. Dieser Modus kann mit dem Parameter SCMT_ACT_LOG:
SCMP_ACT_LOG: seccomp hat keinen Einfluss auf den laufenden Stream, der den Systemaufruf tätigt, sofern dieser nicht unter eine Regel des Filters fällt. Dennoch wird die Information über den Systemaufruf protokolliert.
Hier ist eine typische Strategie zur Nutzung dieser Möglichkeit:
- Erlauben Sie die notwendigen Systemaufrufe.
- Sperren Sie Systemaufrufe, von denen bekannt ist, dass sie nicht benötigt werden.
- Protokollieren Sie alle anderen Aufrufe.
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"
}
]
}()
Denken Sie daran, dass alle Aufrufe, von denen bekannt ist, dass sie nicht verwendet werden, blockiert werden müssen, insbesondere solche, die potenziell dem Cluster schaden könnten. Eine gute Grundlage für die Erstellung einer Liste ist die offizielle . Sie erklärt ausführlich, welche Systemaufrufe im Standardprofil gesperrt sind und warum.
Es gibt jedoch einen Haken. Obwohl SCMT_ACT_LOG der Linux-Kernel seit Ende 2017 unterstützt wird, ist er erst relativ kürzlich in das Kubernetes-Ökosystem integriert worden. Daher benötigen Sie für die Verwendung dieser Methode einen Linux-Kernel 4.14 und runC in der Version .
Tipp Nr. 5: Das Profil für den Audit-Modus zur Testung in der Produktion kann erstellt werden, indem schwarze und weiße Listen kombiniert werden, wobei alle Ausnahmen protokolliert werden.
6. Verwenden Sie weiße Listen
Die Erstellung weißer Listen erfordert zusätzlichen Aufwand, da jeder Aufruf, den die Anwendung benötigt, identifiziert werden muss, jedoch erhöht dieser Ansatz erheblich die Sicherheit:
Es wird dringend empfohlen, einen Ansatz auf Basis weißer Listen zu verwenden, da dieser einfacher und zuverlässiger ist. Die schwarze Liste muss jedes Mal aktualisiert werden, wenn ein potenziell gefährlicher Systemaufruf (oder ein gefährliches Flag/Option, wenn diese auf der schwarzen Liste stehen) hinzugefügt wird. Darüber hinaus kann oft die Darstellung eines Parameters geändert werden, ohne dessen Inhalt zu ändern, um somit 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 während der Ausführung getätigten Aufrufe sammelt. Zum Beispiel, für die folgende Anwendung:
package main
import "fmt"
func main() {
fmt.Println("test")
} … lasst uns ausführen gosystract so aus:
go install https://github.com/pjbgf/gosystract
gosystract --template='{{- range . }}{{printf "%s", .Name}}{{- end}}' anwendungs-pfad… und wir erhalten folgendes Ergebnis:
"sched_yield",
"futex",
"write",
"mmap",
"exit_group",
"madvise",
"rt_sigprocmask",
"getpid",
"gettid",
"tgkill",
"rt_sigaction",
"read",
"getpgrp",
"arch_prctl",Bis Jetzt ist das nur ein Beispiel – Details zum Tool werden weiter unten folgen.
Tipp Nr. 6: Erlauben Sie nur die Aufrufe, die Sie wirklich benötigen, und blockieren Sie alle anderen.
7. Legen Sie die richtigen Grundlagen (oder bereiten Sie sich auf unerwartetes Verhalten vor)
Der Kernel wird sicherstellen, dass das Profil eingehalten wird, unabhängig davon, was Sie darin festgelegt haben. Auch wenn es nicht ganz Ihren Vorstellungen entspricht. Wenn Sie beispielsweise den Zugriff auf Aufrufe wie exit oder exit_groupblockieren, kann der Container nicht ordnungsgemäß beendet werden, und selbst ein einfacher Befehl wie echo hi für unbestimmte Zeit hängen lassen. Infolgedessen erhalten Sie eine hohe CPU-Auslastung im Cluster:

In solchen Fällen kann ein Tool hilfreich sein. strace — sie zeigt, worin das Problem liegen könnte:

sudo strace -c -p 9331
Stellen Sie sicher, dass die Profile alle Systemaufrufe enthalten, die die Anwendung während ihrer Ausführung benötigt.
Tipp Nr. 7: Achten Sie auf die Details und überprüfen Sie, dass alle erforderlichen Systemaufrufe in der Whitelist enthalten sind.
Mit diesem Artikel endet der erste Teil unserer Reihe über die Verwendung von seccomp in Kubernetes im Sinne von SecDevOps. In den nächsten Teilen werden wir darüber sprechen, warum das wichtig ist und wie man den Prozess automatisiert.
P.S. vom Übersetzer
Lesen Sie auch in unserem Blog:
- «»;
- «»;
- «»;
- «».
Quelle: habr.com
