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:
- Erforderliche Systemaufrufe zulassen
- Blockieren Sie Anrufsysteme, von denen bekannt ist, dass sie nicht nützlich sind.
- 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: