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

Notiz. übersetzen: Wir präsentieren Ihnen die Übersetzung eines Artikels eines leitenden Ingenieurs für Anwendungssicherheit beim britischen Unternehmen ASOS.com. Damit beginnt er eine Reihe von Veröffentlichungen, die sich der Verbesserung der Sicherheit in Kubernetes durch den Einsatz von seccomp widmen. Wenn den Lesern die Einleitung gefällt, werden wir dem Autor folgen und mit seinen zukünftigen Materialien zu diesem Thema fortfahren.

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

Dieser Artikel ist der erste einer Reihe von Beiträgen darüber, wie man Seccomp-Profile im Geiste von SecDevOps erstellt, ohne auf Magie und Hexerei zurückzugreifen. In Teil XNUMX werde ich die Grundlagen und internen Details der Implementierung von seccomp in Kubernetes behandeln.

Das Kubernetes-Ökosystem bietet vielfältige Möglichkeiten, Container zu sichern und zu isolieren. Der Artikel befasst sich mit dem Secure Computing Mode, auch bekannt als seccomp. Sein Kern besteht darin, die zur Ausführung verfügbaren Systemaufrufe nach Containern zu filtern.

Warum ist es wichtig? Ein Container ist lediglich ein Prozess, der auf einer bestimmten Maschine ausgeführt wird. Und es nutzt den Kernel genau wie andere Anwendungen. Wenn Container beliebige Systemaufrufe ausführen könnten, würde Malware dies sehr bald ausnutzen, um die Containerisolation zu umgehen und andere Anwendungen zu beeinträchtigen: Informationen abfangen, Systemeinstellungen ändern usw.

Seccomp-Profile legen fest, welche Systemaufrufe erlaubt oder deaktiviert werden sollen. Die Container-Laufzeit aktiviert sie beim Start, sodass der Kernel ihre Ausführung überwachen kann. Durch die Verwendung solcher Profile können Sie den Angriffsvektor begrenzen und den Schaden reduzieren, wenn ein Programm im Container (d. h. Ihre Abhängigkeiten oder deren Abhängigkeiten) anfängt, etwas zu tun, was es nicht tun darf.

Zu den Grundlagen kommen

Das grundlegende 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"
        }
    ]
}

(medium-basic-seccomp.json)

defaultAction bestimmt das Standardverhalten aller Systemaufrufe, die nicht im Abschnitt angegeben sind syscalls. Der Einfachheit halber konzentrieren wir uns auf die beiden Hauptwerte, die verwendet werden:

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

Im Abschnitt architectures Zielarchitekturen werden aufgelistet. Dies ist wichtig, da der Filter selbst, der auf Kernelebene angewendet wird, von Systemaufrufkennungen abhängt und nicht von deren im Profil angegebenen Namen. Die Containerlaufzeit ordnet sie vor der Verwendung den Bezeichnern zu. Die Idee dahinter ist, dass Systemaufrufe je nach Systemarchitektur völlig unterschiedliche IDs haben können. Beispiel: Systemaufruf recvfrom (wird zum Empfangen von Informationen vom Socket verwendet) hat die ID = 64 auf x64-Systemen und die ID = 517 auf x86. Hier finden Sie eine Liste aller Systemaufrufe für x86-x64-Architekturen.

Im Bereich syscalls listet alle Systemaufrufe auf und gibt an, was mit ihnen geschehen soll. Sie können beispielsweise eine Whitelist erstellen, indem Sie festlegen defaultAction auf SCMP_ACT_ERRNO, und Anrufe im Abschnitt syscalls zuweisen SCMP_ACT_ALLOW. Somit lassen Sie nur die im Abschnitt angegebenen Aufrufe zu syscallsund alle anderen verbieten. Für die Blacklist sollten Sie die Werte ändern defaultAction und Handlungen zum Gegenteil.

Jetzt sollten wir ein paar Worte zu den Nuancen sagen, die nicht so offensichtlich sind. Bitte beachten Sie, dass bei den folgenden Empfehlungen davon ausgegangen wird, dass Sie eine Reihe von Geschäftsanwendungen auf Kubernetes bereitstellen und diese mit möglichst wenigen Berechtigungen ausgeführt werden sollen.

1. AllowPrivilegeEscalation=false

В securityContext Container hat einen Parameter AllowPrivilegeEscalation. Wenn es installiert ist false, Container beginnen mit (on) bisschen no_new_priv. Die Bedeutung dieses Parameters geht aus dem Namen hervor: Er verhindert, dass der Container neue Prozesse mit mehr Privilegien startet, als er selbst hat.

Ein Nebeneffekt der Einstellung dieser Option auf true (Standard) ist, dass die Containerlaufzeit das Seccomp-Profil gleich zu Beginn des Startvorgangs anwendet. Daher müssen alle Systemaufrufe, die zum Ausführen interner Laufzeitprozesse erforderlich sind (z. B. Festlegen von Benutzer-/Gruppen-IDs, Löschen bestimmter Funktionen), im Profil aktiviert sein.

Zu einem Container, der triviale Dinge erledigt echo hi, sind folgende Berechtigungen erforderlich:

{
    "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 noch einmal: Warum ist das ein Problem? Persönlich würde ich es vermeiden, die folgenden Systemaufrufe auf die Whitelist zu setzen (es sei denn, es besteht ein wirklicher Bedarf dafür): capset, set_tid_address, setgid, setgroups и setuid. Die eigentliche Herausforderung besteht jedoch darin, dass Sie durch das Zulassen von Prozessen, über die Sie keinerlei Kontrolle haben, Profile an die Container-Laufzeitimplementierung binden. Mit anderen Worten: Eines Tages stellen Sie möglicherweise fest, dass die Container nach der Aktualisierung der Container-Laufzeitumgebung (entweder durch Sie oder, was wahrscheinlicher ist, durch den Cloud-Dienstanbieter) plötzlich nicht mehr ausgeführt werden.

Tipp # 1: Container ausführen mit AllowPrivilegeEscaltion=false. Dadurch wird die Größe von Seccomp-Profilen reduziert und sie werden weniger anfällig für Änderungen in der Container-Laufzeitumgebung.

2. Festlegen von Seccomp-Profilen auf Containerebene

Das Seccomp-Profil kann auf Pod-Ebene eingestellt 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 obige Syntax ändert, wenn Kubernetes seccomp wird GA (Dieses Ereignis wird in der nächsten Version von Kubernetes erwartet – 1.18 – ca. übersetzt).

Nur wenige Menschen wissen, dass es Kubernetes schon immer gab ein Fehlerwas dazu führte, dass Seccomp-Profile angewendet wurden Container anhalten. Die Laufzeitumgebung gleicht dieses Manko teilweise aus, dieser Container verschwindet jedoch nicht aus den Pods, da er zur Konfiguration ihrer Infrastruktur verwendet wird.

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

Durch die Verwendung von Seccomp-Profilen auf Containerebene vermeiden Sie diese Gefahr und können ein Profil erstellen, das auf einen bestimmten Container zugeschnitten ist. Dies muss so lange erfolgen, bis die Entwickler den Fehler behoben haben und die neue Version (vielleicht 1.18?) für alle verfügbar ist.

Tipp # 2: Seccomp-Profile auf Containerebene festlegen.

Im praktischen Sinne dient diese Regel meist als universelle Antwort auf die Frage: „Warum funktioniert mein Seccomp-Profil mit docker runfunktioniert aber nach der Bereitstellung in einem Kubernetes-Cluster nicht?

3. Verwenden Sie runtime/default nur als letzten Ausweg

Kubernetes bietet zwei Optionen für integrierte Profile: runtime/default и docker/default. Beide werden von der Containerlaufzeit implementiert, nicht von Kubernetes. Daher können sie je nach verwendeter Laufzeitumgebung und deren Version unterschiedlich sein.

Mit anderen Worten: Aufgrund der Änderung der Laufzeit hat der Container möglicherweise Zugriff auf einen anderen Satz von Systemaufrufen, die er möglicherweise verwendet oder nicht. Die meisten Laufzeiten verwenden Docker-Implementierung. Wenn Sie dieses Profil verwenden möchten, stellen Sie bitte sicher, dass es für Sie geeignet ist.

Profil docker/default ist seit Kubernetes 1.11 veraltet, also vermeiden Sie die Verwendung.

Meiner Meinung nach Profil runtime/default Perfekt geeignet für den Zweck, für den es erstellt wurde: Benutzer vor den Risiken schützen, die mit der Ausführung eines Befehls verbunden sind docker run auf ihren Autos. Wenn es jedoch um Geschäftsanwendungen geht, die auf Kubernetes-Clustern laufen, wage ich zu behaupten, dass ein solches Profil zu offen ist und Entwickler sich auf die Erstellung von Profilen für ihre Anwendungen (oder Anwendungstypen) konzentrieren sollten.

Tipp # 3: Erstellen Sie Seccomp-Profile für bestimmte Anwendungen. Wenn dies nicht möglich ist, erstellen Sie Profile für Anwendungstypen, beispielsweise ein erweitertes Profil, das alle Web-APIs der Golang-Anwendung enthält. Verwenden Sie runtime/default nur als letzten Ausweg.

In zukünftigen Beiträgen werde ich behandeln, wie man von SecDevOps inspirierte Seccomp-Profile erstellt, sie automatisiert und in Pipelines testet. Mit anderen Worten: Sie haben keine Ausrede mehr, nicht auf anwendungsspezifische Profile zu aktualisieren.

4. Unbegrenztheit ist KEINE Option.

Von erstes Kubernetes-Sicherheitsaudit Es stellte sich heraus, dass dies standardmäßig der Fall war seccomp deaktiviert. Dies bedeutet, dass Sie dies nicht tun PodSecurityPolicy, wodurch es im Cluster aktiviert wird, funktionieren alle Pods, für die das Seccomp-Profil nicht definiert ist seccomp=unconfined.

Der Betrieb in diesem Modus bedeutet, dass eine gesamte Isolationsschicht verloren geht, die den Cluster schützt. Dieser Ansatz wird von Sicherheitsexperten nicht empfohlen.

Tipp # 4: Es darf kein Container im Cluster ausgeführt werden seccomp=unconfined, insbesondere in Produktionsumgebungen.

5. „Audit-Modus“

Dieser Punkt gilt nicht nur für Kubernetes, fällt aber dennoch in die Kategorie „Dinge, die Sie wissen sollten, bevor Sie beginnen“.

Tatsächlich war die Erstellung von Seccomp-Profilen schon immer eine Herausforderung und beruhte stark auf Versuch und Irrtum. Tatsache ist, dass Benutzer einfach keine Möglichkeit haben, sie in Produktionsumgebungen zu testen, ohne das Risiko einzugehen, die Anwendung „abzuwerfen“.

Nach der Veröffentlichung des Linux-Kernels 4.14 wurde es möglich, Teile eines Profils im Audit-Modus auszuführen und dabei Informationen über alle Systemaufrufe im Syslog aufzuzeichnen, ohne diese jedoch zu blockieren. Sie können diesen Modus über den Parameter aktivieren SCMT_ACT_LOG:

SCMP_ACT_LOG: seccomp wirkt sich nicht auf den Thread aus, der den Systemaufruf ausführt, wenn es keiner Regel im Filter entspricht, aber Informationen über den Systemaufruf werden protokolliert.

Hier ist eine typische Strategie zur Verwendung dieser Funktion:

  1. Erlauben Sie Systemaufrufe, die benötigt werden.
  2. Blockieren Sie Anrufe vom System, von denen Sie wissen, dass sie nicht nützlich sind.
  3. Notieren Sie Informationen zu allen anderen Anrufen im Protokoll.

Ein vereinfachtes Beispiel sieht so 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"
        }
    ]
}

(medium-mixed-seccomp.json)

Denken Sie jedoch daran, dass Sie alle Anrufe blockieren müssen, von denen Sie wissen, dass sie nicht verwendet werden und dem Cluster möglicherweise schaden könnten. Eine gute Grundlage für die Zusammenstellung einer Liste ist die amtliche Docker-Dokumentation. Es wird ausführlich erläutert, welche Systemaufrufe im Standardprofil blockiert sind und warum.

Allerdings gibt es einen Haken. Obwohl SCMT_ACT_LOG Es wird seit Ende 2017 vom Linux-Kernel unterstützt und ist erst vor relativ kurzer Zeit in das Kubernetes-Ökosystem eingestiegen. Um diese Methode verwenden zu können, benötigen Sie daher einen Linux-Kernel 4.14 und eine RunC-Version (mindestens niedriger). v1.0.0-rc9.

Tipp # 5: Durch die Kombination von Black- und Whitelists kann ein Audit-Modus-Profil für Tests in der Produktion erstellt und alle Ausnahmen protokolliert werden.

6. Verwenden Sie Whitelists

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

Es wird dringend empfohlen, den Whitelist-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 Flagge/Option, wenn sie auf der Blacklist steht) hinzugefügt wird. Darüber hinaus ist es oft möglich, die Darstellung eines Parameters zu ändern, ohne sein Wesen zu verändern und so die Einschränkungen der Blacklist zu umgehen.

Für Go-Anwendungen 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 starten gosystract wie folgt:

go install https://github.com/pjbgf/gosystract
gosystract --template='{{- range . }}{{printf ""%s",n" .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",

Dies ist vorerst nur ein Beispiel – weitere Details zu den Tools folgen.

Tipp # 6: Lassen Sie nur die Anrufe zu, 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 erzwingt das Profil unabhängig davon, was Sie darin schreiben. Auch wenn es nicht genau das ist, was Sie wollten. Wenn Sie beispielsweise den Zugriff auf Anrufe wie blockieren exit oder exit_group, kann der Container nicht ordnungsgemäß heruntergefahren werden, selbst wenn ein einfacher Befehl wie folgt ausgeführt wird echo hi hänge ihn aufo auf unbestimmte Zeit. Dadurch kommt es zu einer hohen CPU-Auslastung im Cluster:

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

In solchen Fällen kann ein Energieversorger Abhilfe schaffen strace - Es wird angezeigt, wo das Problem liegen könnte:

Seccomp in Kubernetes: 7 Dinge, die Sie von Anfang an wissen müssen
sudo strace -c -p 9331

Stellen Sie sicher, dass die Profile alle Systemaufrufe enthalten, die die Anwendung zur Laufzeit benötigt.

Tipp # 7: Achten Sie auf Details und stellen Sie sicher, dass alle notwendigen Systemaufrufe auf der Whitelist stehen.

Damit ist der erste Teil einer Artikelserie über die Verwendung von seccomp in Kubernetes im Sinne von SecDevOps abgeschlossen. In den folgenden Abschnitten werden wir darüber sprechen, warum dies wichtig ist und wie man den Prozess automatisieren kann.

PS vom Übersetzer

Lesen Sie auch auf unserem Blog:

Source: habr.com

Kommentar hinzufügen