Seccomp en Kubernetes: 7 cosas que debes saber desde el principio

Nota. traducir: Presentamos a su atención la traducción de un artículo de un ingeniero senior de seguridad de aplicaciones de la empresa británica ASOS.com. Con él inicia una serie de publicaciones dedicadas a mejorar la seguridad en Kubernetes mediante el uso de seccomp. Si a los lectores les gusta la introducción, seguiremos al autor y continuaremos con sus materiales futuros sobre este tema.

Seccomp en Kubernetes: 7 cosas que debes saber desde el principio

Este artículo es el primero de una serie de publicaciones sobre cómo crear perfiles seccomp en el espíritu de SecDevOps, sin recurrir a magia ni brujería. En la Parte XNUMX, cubriré los conceptos básicos y los detalles internos de la implementación de seccomp en Kubernetes.

El ecosistema de Kubernetes ofrece una amplia variedad de formas de proteger y aislar contenedores. El artículo trata sobre el modo de computación segura, también conocido como segundo. Su esencia es filtrar las llamadas al sistema disponibles para su ejecución por contenedores.

¿Por qué es importante? Un contenedor es solo un proceso que se ejecuta en una máquina específica. Y utiliza el kernel al igual que otras aplicaciones. Si los contenedores pudieran realizar llamadas al sistema, muy pronto el malware aprovecharía esto para evitar el aislamiento de los contenedores y afectar a otras aplicaciones: interceptar información, cambiar la configuración del sistema, etc.

Los perfiles seccomp definen qué llamadas al sistema deben permitirse o deshabilitarse. El tiempo de ejecución del contenedor los activa cuando se inicia para que el kernel pueda monitorear su ejecución. El uso de dichos perfiles le permite limitar el vector de ataque y reducir el daño si algún programa dentro del contenedor (es decir, sus dependencias o sus dependencias) comienza a hacer algo que no está permitido hacer.

Llegando a lo básico

El perfil seccomp básico incluye tres elementos: defaultAction, architectures (o archMap) Y 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"
        }
    ]
}

(medio-básico-seccomp.json)

defaultAction determina el destino predeterminado de cualquier llamada al sistema no especificada en la sección syscalls. Para facilitar las cosas, centrémonos en los dos valores principales que se utilizarán:

  • SCMP_ACT_ERRNO — bloquea la ejecución de una llamada al sistema,
  • SCMP_ACT_ALLOW - permite.

En la sección architectures Se enumeran las arquitecturas de destino. Esto es importante porque el filtro en sí, aplicado a nivel del kernel, depende de los identificadores de llamadas del sistema y no de sus nombres especificados en el perfil. El tiempo de ejecución del contenedor los relacionará con los identificadores antes de su uso. La idea es que las llamadas al sistema puedan tener ID completamente diferentes según la arquitectura del sistema. Por ejemplo, llamada al sistema recvfrom (utilizado para recibir información del socket) tiene ID = 64 en sistemas x64 e ID = 517 en x86. es Puede encontrar una lista de todas las llamadas al sistema para arquitecturas x86-x64.

En la sección syscalls enumera todas las llamadas al sistema y especifica qué hacer con ellas. Por ejemplo, puede crear una lista blanca configurando defaultAction en SCMP_ACT_ERRNO, y llamadas en la sección syscalls para asignar SCMP_ACT_ALLOW. Por lo tanto, solo permites las llamadas especificadas en la sección syscallsy prohibir todos los demás. Para la lista negra debes cambiar los valores. defaultAction y acciones en sentido contrario.

Ahora deberíamos decir algunas palabras sobre los matices que no son tan obvios. Tenga en cuenta que las recomendaciones siguientes suponen que está implementando una línea de aplicaciones comerciales en Kubernetes y desea que se ejecuten con la menor cantidad de privilegios posible.

1. AllowPrivilegeEscalation=falso

В securityContext El contenedor tiene un parámetro. AllowPrivilegeEscalation. Si está instalado en false, los contenedores comenzarán con (on) poco no_new_priv. El significado de este parámetro es obvio por el nombre: evita que el contenedor inicie nuevos procesos con más privilegios de los que tiene.

Un efecto secundario de que esta opción esté configurada en true (predeterminado) es que el tiempo de ejecución del contenedor aplica el perfil seccomp al comienzo del proceso de inicio. Por lo tanto, todas las llamadas al sistema necesarias para ejecutar procesos de ejecución internos (por ejemplo, configurar ID de usuario/grupo, eliminar ciertas capacidades) deben estar habilitadas en el perfil.

A un contenedor que hace cosas triviales. echo hi, se requerirán los siguientes permisos:

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

...en lugar de estos:

{
    "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"
        }
    ]
}

(hola-contenedor-seccomp.json)

Pero nuevamente, ¿por qué es esto un problema? Personalmente, evitaría incluir en la lista blanca las siguientes llamadas al sistema (a menos que exista una necesidad real de ellas): capset, set_tid_address, setgid, setgroups и setuid. Sin embargo, el verdadero desafío es que al permitir procesos sobre los que no tienes absolutamente ningún control, estás vinculando perfiles a la implementación del tiempo de ejecución del contenedor. En otras palabras, un día puede descubrir que después de actualizar el entorno de ejecución del contenedor (ya sea por usted o, más probablemente, por el proveedor de servicios en la nube), los contenedores dejan de ejecutarse repentinamente.

Consejo número 1: Ejecute contenedores con AllowPrivilegeEscaltion=false. Esto reducirá el tamaño de los perfiles seccomp y los hará menos sensibles a los cambios en el entorno de ejecución del contenedor.

2. Configuración de perfiles seccomp a nivel de contenedor

El perfil seccomp se puede configurar a nivel de pod:

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

...o a nivel de contenedor:

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

Tenga en cuenta que la sintaxis anterior cambiará cuando Kubernetes seccomp se convertirá en GA (Este evento se espera en la próxima versión de Kubernetes - 1.18 - aprox. traducción).

Pocas personas saben que Kubernetes siempre ha tenido errorlo que provocó que se aplicaran perfiles seccomp a pausar el contenedor. El entorno de ejecución compensa parcialmente esta deficiencia, pero este contenedor no desaparece de los pods, ya que se utiliza para configurar su infraestructura.

El problema es que este contenedor siempre comienza con AllowPrivilegeEscalation=true, lo que lleva a los problemas mencionados en el apartado 1, y esto no se puede cambiar.

Al utilizar perfiles seccomp a nivel de contenedor, se evita este problema y se puede crear un perfil que se adapta a un contenedor específico. Esto deberá hacerse hasta que los desarrolladores solucionen el error y la nueva versión (¿quizás la 1.18?) esté disponible para todos.

Consejo número 2: establece perfiles seccomp a nivel de contenedor.

En un sentido práctico, esta regla suele servir como respuesta universal a la pregunta: "¿Por qué mi perfil seccomp funciona con docker run¿Pero no funciona después de implementarlo en un clúster de Kubernetes?

3. Utilice el tiempo de ejecución/predeterminado solo como último recurso

Kubernetes tiene dos opciones para perfiles integrados: runtime/default и docker/default. Ambos son implementados por el tiempo de ejecución del contenedor, no por Kubernetes. Por lo tanto, pueden diferir según el entorno de ejecución utilizado y su versión.

En otras palabras, como resultado del cambio en el tiempo de ejecución, el contenedor puede tener acceso a un conjunto diferente de llamadas al sistema, que puede usar o no. La mayoría de los tiempos de ejecución utilizan implementación de ventana acoplable. Si desea utilizar este perfil, asegúrese de que sea adecuado para usted.

Perfil docker/default Ha quedado obsoleto desde Kubernetes 1.11, así que evite usarlo.

En mi opinión, perfil. runtime/default Perfectamente adecuado para el propósito para el que fue creado: proteger a los usuarios de los riesgos asociados con la ejecución de un comando. docker run en sus autos. Sin embargo, cuando se trata de aplicaciones empresariales que se ejecutan en clústeres de Kubernetes, me atrevería a argumentar que dicho perfil es demasiado abierto y que los desarrolladores deberían centrarse en crear perfiles para sus aplicaciones (o tipos de aplicaciones).

Consejo número 3: cree perfiles seccomp para aplicaciones específicas. Si esto no es posible, cree perfiles para los tipos de aplicaciones, por ejemplo, cree un perfil avanzado que incluya todas las API web de la aplicación Golang. Utilice el tiempo de ejecución/predeterminado únicamente como último recurso.

En publicaciones futuras, cubriré cómo crear perfiles seccomp inspirados en SecDevOps, automatizarlos y probarlos en canalizaciones. En otras palabras, no tendrás excusa para no actualizar a perfiles específicos de la aplicación.

4. Ilimitado NO es una opción.

de primera auditoría de seguridad de Kubernetes resultó que por defecto segundo deshabilitado. Esto significa que si no configura PodSecurityPolicy, que lo habilitará en el clúster, todos los pods para los cuales el perfil seccomp no está definido funcionarán en seccomp=unconfined.

Operar en este modo significa que se pierde toda una capa de aislamiento que protege el clúster. Los expertos en seguridad no recomiendan este enfoque.

Consejo número 4: Ningún contenedor en el clúster debería estar ejecutándose seccomp=unconfined, especialmente en entornos de producción.

5. "Modo auditoría"

Este punto no es exclusivo de Kubernetes, pero aún así cae en la categoría de “cosas que debes saber antes de comenzar”.

Da la casualidad de que crear perfiles seccomp siempre ha sido un desafío y depende en gran medida de prueba y error. El hecho es que los usuarios simplemente no tienen la oportunidad de probarlos en entornos de producción sin correr el riesgo de "abandonar" la aplicación.

Después del lanzamiento del kernel de Linux 4.14, fue posible ejecutar partes de un perfil en modo auditoría, registrando información sobre todas las llamadas al sistema en syslog, pero sin bloquearlas. Puede activar este modo usando el parámetro SCMT_ACT_LOG:

SCMP_ACT_LOG: seccomp no afectará el hilo que realiza la llamada al sistema si no coincide con ninguna regla en el filtro, pero se registrará información sobre la llamada al sistema.

A continuación se muestra una estrategia típica para utilizar esta función:

  1. Permitir las llamadas al sistema que sean necesarias.
  2. Bloquea llamadas del sistema que sabes que no serán útiles.
  3. Registre información sobre todas las demás llamadas en el registro.

Un ejemplo simplificado se ve así:

{
    "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"
        }
    ]
}

(medio-mixto-seccomp.json)

Pero recuerde que debe bloquear todas las llamadas que sepa que no se utilizarán y que podrían dañar el clúster. Una buena base para compilar una lista es la información oficial. documentación acoplable. Explica en detalle qué llamadas al sistema están bloqueadas en el perfil predeterminado y por qué.

Sin embargo, hay un inconveniente. A pesar de SCMT_ACT_LOG Compatible con el kernel de Linux desde finales de 2017, ingresó al ecosistema de Kubernetes hace relativamente poco tiempo. Por lo tanto, para utilizar este método necesitarás un kernel Linux 4.14 y una versión runC no inferior. v1.0.0-rc9.

Consejo número 5: Se puede crear un perfil de modo de auditoría para pruebas en producción combinando listas blancas y negras, y se pueden registrar todas las excepciones.

6. Utilice listas blancas

La inclusión en la lista blanca requiere un esfuerzo adicional porque hay que identificar cada llamada que la aplicación pueda necesitar, pero este enfoque mejora enormemente la seguridad:

Se recomienda encarecidamente utilizar el método de lista blanca, ya que es más sencillo y fiable. La lista negra deberá actualizarse cada vez que se agregue una llamada al sistema potencialmente peligrosa (o una marca/opción peligrosa si está en la lista negra). Además, a menudo es posible cambiar la representación de un parámetro sin cambiar su esencia y así evitar las restricciones de la lista negra.

Para las aplicaciones Go, desarrollé una herramienta especial que acompaña a la aplicación y recopila todas las llamadas realizadas durante la ejecución. Por ejemplo, para la siguiente aplicación:

package main

import "fmt"

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

... lancemos gosystract como sigue:

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

... y obtenemos el siguiente resultado:

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

Por ahora, esto es sólo un ejemplo; más adelante se darán más detalles sobre las herramientas.

Consejo número 6: Permita sólo aquellas llamadas que realmente necesite y bloquee todas las demás.

7. Sentar las bases adecuadas (o prepararse para un comportamiento inesperado)

El kernel aplicará el perfil independientemente de lo que escriba en él. Incluso si no es exactamente lo que querías. Por ejemplo, si bloquea el acceso a llamadas como exit o exit_group, el contenedor no podrá cerrarse correctamente e incluso un comando simple como echo hi colgarloo por tiempo indefinido. Como resultado, obtendrá un uso elevado de CPU en el clúster:

Seccomp en Kubernetes: 7 cosas que debes saber desde el principio

En tales casos, una empresa de servicios públicos puede acudir al rescate. strace - mostrará cuál puede ser el problema:

Seccomp en Kubernetes: 7 cosas que debes saber desde el principio
sudo strace -c -p 9331

Asegúrese de que los perfiles contengan todas las llamadas al sistema que la aplicación necesita en tiempo de ejecución.

Consejo número 7: preste atención a los detalles y asegúrese de que todas las llamadas al sistema necesarias estén en la lista blanca.

Con esto concluye la primera parte de una serie de artículos sobre el uso de seccomp en Kubernetes en el espíritu de SecDevOps. En las siguientes partes hablaremos sobre por qué esto es importante y cómo automatizar el proceso.

PD del traductor

Lea también en nuestro blog:

Fuente: habr.com

Añadir un comentario