Capítulo 4. Seguridad del tiempo de ejecución de la carga de trabajo

Este trabajo se ha traducido utilizando IA. Agradecemos tus opiniones y comentarios: translation-feedback@oreilly.com

Nota

Este capítulo habla de las políticas de seguridad de pods (PSP), una función que ha quedado obsoleta en Kubernetes v1.25 (octubre de 2022) y se ha sustituido por la admisión de seguridad de pods (PSA). Hemos dejado intactas las secciones de este capítulo que hablan de PSP, ya que reconocemos que no todos los desarrolladores pasarán a la v1.25 de inmediato. Para más información, consulta la documentación actualizada de Kubernetes sobre las políticas de seguridad de los pods.

El mecanismo de aprovisionamiento de pods por defecto de Kubernetes tiene una amplia superficie de ataque que puede ser utilizada por los adversarios para explotar el clúster o escapar del contenedor. En este capítulo aprenderás a implantar políticas de seguridad de pods (PSP) para limitar la superficie de ataque de los pods y a monitorizar los procesos (por ejemplo, los privilegios de los procesos), el acceso a los archivos y la seguridad en tiempo de ejecución de tus cargas de trabajo. He aquí algunos detalles de lo que trataremos:

  • Cubriremos los detalles de implementación de las PSP, como los contextos de seguridad de los pods, y también explicaremos las limitaciones de las PSP. Ten en cuenta que las PSP están obsoletas a partir de Kubernetes v1.21; sin embargo, trataremos este tema en este capítulo, ya que somos conscientes de que las PSP se utilizan ampliamente.

  • Hablaremos del monitoreo de procesos, que se centra en la necesidad de un monitoreo nativo de Kubernetes para detectar actividades sospechosas. Cubriremos el monitoreo y la aplicación en tiempo de ejecución utilizando funciones de seguridad del núcleo como seccomp, SELinux y AppArmor para evitar que los contenedores accedan a los recursos del host.

  • Cubriremos tanto la detección como la defensa en tiempo de ejecución contra las vulnerabilidades, el aislamiento de la carga de trabajo y la contención de un radio de explosión.

Políticas de seguridad del módulo

Kubernetes proporciona una forma de embarcar de forma segura tus pods y contenedores utilizando PSPs. Son un recurso de ámbito de clúster que comprueba un conjunto de condiciones antes de que un pod sea admitido y programado para ejecutarse en un clúster. Esto se consigue a través de un controlador de admisión de Kubernetes, que evalúa cada solicitud de creación de pods para comprobar si cumple el PSP asignado al pod.

Ten en cuenta que las PSP están obsoletas con la versión 1.21 de Kubernetes y está previsto que se eliminen en la versión 1.25. Sin embargo, se utilizan mucho en los clústeres de producción, por lo que esta sección te ayudará a entender cómo funcionan y cuáles son las buenas prácticas para implantar las PSP.

Las PSP te permiten aplicar reglas con controles como los pods no deben ejecutarse como root o los pods no deben utilizar la red del host, el espacio de nombres del host o ejecutarse como privilegiados. Las políticas se aplican en el momento de la creación del pod. Utilizando las PSP puedes asegurarte de que los pods se crean con los privilegios mínimos necesarios para su funcionamiento, lo que reduce la superficie de ataque de tu aplicación. Además, este mecanismo te ayuda a cumplir diversas normas, como PCI, SOC 2 o HIPAA, que exigen el uso del principio de acceso con mínimos privilegios. Como su nombre indica, el principio exige que a cualquier proceso, usuario o, en nuestro caso, carga de trabajo se le conceda la menor cantidad de privilegios necesarios para que funcione.

Uso de las políticas de seguridad del Pod

Se recomiendan las PSP de Kubernetes, pero se implementan mediante un controlador de admisión opcional. La aplicación de las PSP puede activarse habilitando un controlador de admisión. Esto significa que el manifiesto del servidor API de Kubernetes debe tener un complemento PodSecurityPolicy en su lista --enable-admission-plugins. Muchas distribuciones de Kubernetes no admiten PSP o las desactivan por defecto, por lo que merece la pena comprobarlo al elegir las distribuciones de Kubernetes.

Una vez activados los PSP, aplicarlos es un proceso de tres pasos, como se muestra en la Figura 4-1. Una buena práctica es aplicar los PSP a grupos en lugar de a cuentas de servicio individuales.

Figura 4-1. Proceso de aplicación de las PSP

El paso 1 es crear un PSP. El paso 2 es crear ClusterRole con el verbo use, que autoriza a los controladores de implementación de pods a utilizar las políticas. A continuación, el paso 3 es crear ClusterRoleBindings, que se utiliza para aplicar la política a los grupos (es decir, system:authenticated o system:unauthenticated) o cuentas de servicio.

Un buen punto de partida es la plantilla PSP del proyecto Kubernetes:

apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
  name: restricted
  annotations:
    seccomp.security.alpha.kubernetes.io/allowedProfileNames: |
    'docker/default,runtime/default'
    apparmor.security.beta.kubernetes.io/allowedProfileNames: 'runtime/default'
    seccomp.security.alpha.kubernetes.io/defaultProfileName:  'runtime/default'
    apparmor.security.beta.kubernetes.io/defaultProfileName:  'runtime/default'
spec:
  privileged: false
  # Required to prevent escalations to root.
  allowPrivilegeEscalation: false
  # This is redundant with non-root + disallow privilege escalation,
  # but we can provide it for defense in depth.
  requiredDropCapabilities:
    - ALL
  # Allow core volume types.
  volumes:
    - 'configMap'
    - 'emptyDir'
    - 'projected'
    - 'secret'
    - 'downwardAPI'
    # Assume that persistentVolumes set up by the cluster admin are safe to use.
    - 'persistentVolumeClaim'
  hostNetwork: false
  hostIPC: false
  hostPID: false
  runAsUser:
    # Require the container to run without root privileges.
    rule: 'MustRunAsNonRoot'
  seLinux:
    # This policy assumes the nodes are using AppArmor rather than SELinux.
    rule: 'RunAsAny'
  supplementalGroups:
    rule: 'MustRunAs'
    ranges:
      # Forbid adding the root group.
      - min: 1
        max: 65535
  fsGroup:
    rule: 'MustRunAs'
    ranges:
      # Forbid adding the root group.
      - min: 1
        max: 65535
  readOnlyRootFilesystem: false

En el siguiente ejemplo, aplicas esta política a todos los usuarios autenticados mediante el control de acceso basado en roles de Kubernetes:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole  Policy
metadata:
  name: psp-restricted
rules:
- apiGroups:
  - policy
  resourceNames:
  - restricted
  resources:
  - podsecuritypolicies
  verbs:
  - use
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: psp-restricted-binding
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: psp-restricted
subjects:
  - apiGroup: rbac.authorization.k8s.io
    kind : Group
    name: system:authenticated

Capacidades de la Política de Seguridad del Pod

Centrémonos en las capacidades que proporcionan los PSP y que puedes utilizar como requiera tu caso de uso y tu modelo interno de amenazas. Puedes seguir la plantilla de PSP de ejemplo que acabamos de comentar para construir tus propios PSP. En esta plantilla, la mayoría de las funciones del PSP se utilizan para formular una política restrictiva.

Para explicar el impacto de una capacidad, veamos un ejemplo en el que se ven las capacidades concedidas al pod creado con privileged:true y con privileged:false. Se puede utilizar la utilidad de Linux capsh para evaluar los permisos de los usuarios root de los contenedores. Como puedes ver en la Figura 4-2, el pod privilegiado tiene una plétora de capacidades en su espacio de nombres Linux, lo que se traduce en una superficie de ataque más amplia para que un atacante pueda escapar de tu contenedor.

Figura 4-2. Capacidades de los pods por defecto y privilegiados

La Tabla 4-1 resume las capacidades de los pods tal y como se describen en la documentación del PSP de Kubernetes.

Tabla 4-1. Capacidades del módulo
Campo Utiliza
privilegiado Permite que los contenedores obtengan capacidades que incluyen el acceso a los montajes del host, al sistema de archivos para cambiar la configuración, y muchas más. Puedes comprobar las capacidades con el comando capsh --print.
hostPID, hostIPC Dar acceso al contenedor a los espacios de nombres del host en los que las interfaces de proceso y Ethernet son visibles para él.
hostRed, hostPuertos Da acceso a la IP del contenedor a la red y puertos del host.
volúmenes Permite volúmenes de tipo ConfigMap, emtyDir o secret.
allowedHostPaths Permite la lista blanca de rutas de host que pueden utilizar los volúmenes hostPath (por ejemplo, /tmp).
volúmenes flexibles permitidos Permitir controladores FlexVolume específicos (por ejemplo, azure/kv).
fsGroup Establece un GID o rango de GID propietario de los volúmenes del pod.
readOnlyRootFilesystem Establece el sistema de archivos raíz del contenedor como de sólo lectura.
runAsUser, runAsGroup, supplementalGroups Define el UID y el GID de los contenedores. Aquí puedes especificar usuarios o grupos que no sean root.
allowPrivilegeEscalation, defaultAllowPrivilegeEscalation Restringe la escalada de privilegios por proceso.
defaultAddCapabilities, requiredDropCapabilities, allowedCapabilities Añade o elimina capacidades de Linux según necesites.
SELinux Define el contexto SELinux del contenedor.
allowedProcMountTypes Tipos de montaje proc permitidos por contenedor.
sysctl prohibidos,sysctl no seguros permitidos Establece el perfil sysctl utilizado por el contenedor.
anotaciones Establece los perfiles AppArmor y seccomp utilizados por los contenedores.

Los perfiles AppArmor y seccomp se utilizan con la anotación PSP, donde puedes utilizar el perfil predeterminado del tiempo de ejecución (Docker, CRI) o elegir un perfil personalizado cargado en el host por ti. Verás más sobre estas defensas en "Monitoreo de procesos".

Contexto de seguridad del Pod

A diferencia de los PSP, que se definen en todo el clúster, un pod securityContext puede definirse en tiempo de ejecución mientras se crea una implementación o un pod. Aquí tienes un ejemplo sencillo de pod securityContext en acción, en el que el pod se crea con el usuario raíz (uid=0) y sólo permite cuatro capacidades:

kind: Pod
apiVersion: v1
metadata:
  name: attacker-privileged-test
  namespace: default
  labels:
    app: normal-app
spec:
  containers:
  - name: attacker-container
    image: alpine:latest
    args: ["sleep", "10000"]
    securityContext:
      runAsUser: 0
      capabilities:
        drop:
          - all
        add:
          - SYS_CHROOT
          - NET_BIND_SERVICE
          - SETGID
          - SETUID

Este fragmento de código muestra cómo puedes crear un pod que se ejecute como root pero limitado a un subconjunto de capacidades, especificando un contexto de seguridad. La Figura 4-3 muestra los comandos que puedes ejecutar para verificar que el pod se ejecuta como root con el conjunto limitado de capacidades.

Figura 4-3. Cuatro capacidades de vaina permitidas

El pod securityContext, como se muestra en la Figura 4-3, puede utilizarse sin habilitar las PSP en todo el clúster, pero una vez habilitadas las PSP, tienes que definir securityContext para asegurarte de que los pods se crean correctamente. Como el securityContext tiene un constructor PSP, todas las capacidades de los PSP se aplican al securityContext.

Limitaciones de los PSP

Algunas de las limitaciones de los PSP son

  • PodSecurityPolicySpec tiene referencias a allowedCapabilities, privileged o hostNetwork. Estos refuerzos sólo pueden funcionar en tiempos de ejecución basados en Linux.

  • Si vas a crear un pod utilizando controladores (por ejemplo, un controlador de replicación), conviene comprobar si los PSP están autorizados para ser utilizados por esos controladores.

  • Una vez que las PSP están habilitadas en todo el clúster y un pod no arranca debido a una PSP incorrecta, se hace agitado solucionar el problema. Además, si las PSP están habilitadas en todo el clúster de producción, tienes que probar todos y cada uno de los componentes de tu clúster, incluidas las dependencias como los controladores de admisión mutantes y los veredictos conflictivos.

  • Azure Kubernetes Service (AKS) ha dejado de admitir PSP y ha preferido OPA Gatekeeper para la aplicación de políticas, a fin de admitir políticas más flexibles utilizando el motor OPA.

  • Las PSP están obsoletas y su eliminación está prevista para Kubernetes v1.25.

  • Kubernetes puede tener casos perimetrales en los que se pueden eludir las PSP (por ejemplo, TOB-K8S-038).

Ahora que conoces los PSP, las buenas prácticas para ponerlos en práctica y las limitaciones de los PSP, veamos el monitoreo de los procesos.

Monitoreo de procesos

Cuando contenedorizas una carga de trabajo y la ejecutas en un host con un orquestador como Kubernetes, hay una serie de capas que debes tener en cuenta para el monitoreo de un proceso dentro de un contenedor. Éstas comienzan con los registros y artefactos del proceso del contenedor, el acceso al sistema de archivos, las conexiones de red, las llamadas al sistema necesarias, el permiso del núcleo (carga de trabajo especializada), los artefactos de Kubernetes y los artefactos de la infraestructura de la nube. Normalmente, la postura de seguridad de tu organización depende de lo buenas que sean tus soluciones a la hora de unir estos diversos contextos de registro. Y aquí es donde el sistema de monitoreo tradicional falla de forma mensurable y surge la necesidad del monitoreo y la observabilidad nativos de Kubernetes. Las soluciones tradicionales, como los sistemas de detección y respuesta de puntos finales (EDR) y de protección de puntos finales, tienen las siguientes limitaciones cuando se utilizan en clústeres Kubernetes:

  • No son conscientes del contenedor.

  • No conocen las redes de contenedores y suelen ver la actividad desde la perspectiva del anfitrión, lo que puede dar lugar a falsos negativos sobre los movimientos laterales de los atacantes .

  • Son ciegos al tráfico entre contenedores y no tienen a la vista subcapas como IPIP o VXLAN.

  • No conocen los privilegios de los procesos ni los permisos de los archivos de los contenedores que acceden al host subyacente.

  • No conocen la interfaz de tiempo de ejecución de contenedores de Kubernetes (IRC) ni sus complejidades y problemas de seguridad, lo que puede llevar a que los contenedores puedan acceder a los recursos del host. Esto también se conoce como escalada de privilegios.

En las siguientes secciones, repasaremos varias técnicas que puedes utilizar para el monitoreo de procesos. Primero veremos el monitoreo mediante varios registros disponibles en Kubernetes; luego exploraremos las funciones seccomp, SELinux y AppArmor que te permiten controlar a qué puede acceder un proceso (por ejemplo, llamadas al sistema, sistema de archivos, etc.).

Monitoreo nativo de Kubernetes

Como se muestra en la Figura 4-4, cada capa que conduce a tu proceso de aplicación en contenedores introduce requisitos de monitoreo y registro y una nueva superficie de ataque que es diferente de lo que los profesionales tradicionales de la seguridad informática están acostumbrados a hacer para monitorear redes y aplicaciones. El reto consiste en reducir esta sobrecarga de monitoreo, ya que puede llegar a ser realmente costosa para los recursos de almacenamiento y computación. El tema de la recopilación de métricas y cómo hacerlo con eficacia se trata en detalle en el Capítulo 5.

Figura 4-4. Monitoreo nativo de Kubernetes

Para construir defensas en cada capa, las siguientes son algunas opciones que deberías considerar incorporar al elegir soluciones:

  • Capacidad para bloquear los procesos generados por cada contenedor u orquestación de Kubernetes que crea contenedores.

  • Monitoriza las llamadas al sistema del núcleo utilizadas por cada proceso contenedor y la capacidad de filtrar, bloquear y alertar sobre llamadas sospechosas para evitar que los contenedores accedan a los recursos del host.

  • Monitorea cada conexión de red (socket) originada por un proceso contenedor y la capacidad de aplicar la política de red.

  • Capacidad para aislar un contenedor mediante una política de red (o un nodo que ejecute este contenedor) y pausarlo para investigar actividades sospechosas y recopilar datos forenses en Kubernetes. El comandopause para contenedores basados en Docker suspende los procesos de un contenedor para permitir un análisis detallado. Ten en cuenta que pausar un contenedor hará que éste suspenda su funcionamiento normal y debe utilizarse como respuesta a un evento (por ejemplo, un incidente de seguridad).

  • Monitorea las lecturas y escrituras del sistema de archivos para conocer los cambios del sistema de archivos (binarios, paquetes) y el aislamiento adicional mediante el control de acceso obligatorio (MAC) para evitar la escalada de privilegios.

  • Monitorea el registro de auditoría de Kubernetes para saber qué peticiones de la API de Kubernetes hacen los clientes y detectar actividades sospechosas.

  • Habilita el registro del proveedor de la nube para tu infraestructura y la capacidad de detectar actividades sospechosas en la infraestructura del proveedor de la nube.

Hay muchas soluciones empresariales y de código abierto (por ejemplo, Falco) que se dirigen a grupos de capas utilizando diversas herramientas y mecanismos (como ebpf, kprobes, ptrace, tracepoints, etc.) para ayudar a construir la defensa en varias capas. Deberías fijarte en su modelo de amenazas y elegir soluciones que cumplan sus requisitos.

En la siguiente sección verás algunos de los mecanismos que ofrece Kubernetes al acercar las defensas de Linux al contenedor, lo que te ayudará en el monitoreo y a reducir la superficie de ataque en varias capas. La sección anterior se centraba en el monitoreo para permitirte detectar comportamientos no deseados (maliciosos). Los siguientes mecanismos te permiten establecer controles para evitar comportamientos no deseados (maliciosos).

Las funciones de seguridad del kernel, como seccomp, AppArmor y SELinux, pueden controlar qué llamadas al sistema son necesarias para tu aplicación en contenedores, aislar y personalizar virtualmente cada contenedor para la carga de trabajo que está ejecutando, y utilizar MAC para proporcionar acceso a recursos como el volumen o el sistema de archivos que impiden la fuga de contenedores de forma eficaz. El mero uso de la función con la configuración predeterminada puede reducir enormemente la superficie de ataque en todo tu clúster. En las siguientes secciones examinarás cada defensa en profundidad y cómo funciona en el clúster de Kubernetes para que puedas elegir la mejor opción para tu modelo de amenazas.

Seccomp

Seccomp es una función del núcleo de Linux que puede filtrar las llamadas al sistema ejecutadas por el contenedor de forma granular. Kubernetes te permite aplicar automáticamente perfiles seccomp cargados en un nodo por tiempos de ejecución de Kubernetes como Docker, podman o CRI-O. Un perfil seccomp sencillo consiste en una lista de syscalls y la acción apropiada a tomar cuando se invoca una syscall. Esta acción reduce la superficie de ataque a sólo las syscalls permitidas, reduciendo el riesgo de escalada de privilegios y fuga del contenedor.

En el siguiente perfil seccomp, la acción por defecto es SCMP_ACT_ERRNO, que deniega una llamada al sistema. Pero defaultAction para la llamada al sistema chmod se sobrescribe con SCMP_ACT_ALLOW. Normalmente, tus tiempos de ejecución cargan los perfiles seccomp en el directorio /var/lib/kubelet/seccomp en todos los nodos. Puedes añadir tu perfil personalizado en el mismo lugar:

{
    "defaultAction": "SCMP_ACT_ERRNO",
    "architectures": [
        "SCMP_ARCH_X86_64",
        "SCMP_ARCH_X86",
        "SCMP_ARCH_X32"
    ],
    "syscalls": [
        {
            "names": [
                "chmod",
            ],
            "action": "SCMP_ACT_ALLOW"
        }
    ]
}

Para encontrar las llamadas al sistema que utiliza tu aplicación, puedes utilizar strace como se muestra en el siguiente ejemplo. En este ejemplo, puedes listar las llamadas al sistema utilizadas por la utilidad curl de la siguiente manera:

$ strace -c -S name curl -sS google.com

% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
  4.56    0.000242           6        43        43 access
  0.06    0.000003           3         1           arch_prctl
  1.28    0.000068          10         7           brk
  0.28    0.000015          15         1           clone
  4.62    0.000245           5        48           close
  1.38    0.000073          73         1         1 connect
  0.00    0.000000           0         1           execve
  0.36    0.000019          10         2           fcntl
  4.20    0.000223           5        48           fstat
  0.66    0.000035           3        11           futex
  0.23    0.000012          12         1           getpeername
  0.13    0.000007           7         1           getrandom
  0.19    0.000010          10         1           getsockname
  0.24    0.000013          13         1           getsockopt
  0.15    0.000008           4         2           ioctl
 13.96    0.000741           7       108           mmap
 11.94    0.000634           7        85           mprotect
  0.32    0.000017          17         1           munmap
 11.02    0.000585          13        45         1 openat
  0.11    0.000006           6         1           pipe
 19.50    0.001035         115         9           poll
  0.08    0.000004           4         1           prlimit64
  5.43    0.000288           6        45           read
  0.41    0.000022          22         1           recvfrom
 11.47    0.000609          17        36           rt_sigaction
  0.08    0.000004           4         1           rt_sigprocmask
  1.00    0.000053          53         1           sendto
  0.06    0.000003           3         1           set_robust_list
  0.04    0.000002           2         1           set_tid_address
  2.22    0.000118          30         4           setsockopt
  1.60    0.000085          43         2           socket
  0.08    0.000004           4         1         1 stat
  2.35    0.000125          21         6           write
------ ----------- ----------- --------- --------- ----------------
100.00    0.005308                   518        46 total

Los perfiles seccomp predeterminados proporcionados por el tiempo de ejecución de Kubernetes contienen una lista de llamadas al sistema comunes que utilizan la mayoría de las aplicaciones. Sólo con activar esta función se prohíbe el uso de llamadas al sistema peligrosas, que pueden conducir a un exploit del kernel y a una fuga del contenedor. El perfil seccomp predeterminado del tiempo de ejecución de Docker está disponible para tu referencia.

Nota

En el momento de escribir esto, el perfil Docker/default estaba obsoleto, por lo que te recomendamos que utilices runtime/default como perfil seccomp en su lugar.

La Tabla 4-2 muestra las opciones de implementación del perfil seccomp en Kubernetes mediante anotaciones PSP .

Tabla 4-2. Opciones de Seccomp
Valor Descripción
tiempo de ejecución/por defecto Perfil de tiempo de ejecución del contenedor por defecto
no confinado Sin perfil seccomp: esta opción está predeterminada en Kubernetes.
localhost/<ruta> Tu propio perfil ubicado en el nodo, normalmente en el directorio /var/lib/kubelet/seccomp

SELinux

En el pasado reciente, cada fuga de tiempo de ejecución de contenedor (fuga de contenedor o escalada de privilegios) era algún tipo de fuga del sistema de archivos (es decir, CVE-2019-5736, CVE-2016-9962, CVE-2015-3627, y más). SELinux mitiga estos problemas proporcionando control sobre quién puede acceder al sistema de archivos y la interacción entre recursos (es decir, usuario, archivos, directorios, memoria, sockets, etc.). En el contexto de la computación en nube, tiene sentido aplicar perfiles SELinux a las cargas de trabajo para conseguir un mejor aislamiento y reducir la superficie de ataque limitando el acceso al sistema de archivos por parte del núcleo anfitrión.

SELinux fue desarrollado originalmente por la Agencia de Seguridad Nacional a principios de la década de 2000 y se utiliza predominantemente en distros basadas en Red Hat y centOS. La razón por la que SELinux es eficaz es que proporciona un MAC, que aumenta en gran medida el sistema tradicional de control de acceso discrecional (DAC) de Linux.

Tradicionalmente, con el CAD de Linux, los usuarios tienen la capacidad de cambiar los permisos de los archivos, directorios y procesos que les pertenecen. Y un usuario root tiene acceso a todo. Pero con SELinux (MAC), el núcleo asigna una etiqueta a cada recurso del SO, que se almacena como atributos de archivo extendidos. Estas etiquetas se utilizan para evaluar las políticas SELinux dentro del núcleo para permitir cualquier interacción. Con SELinux activado, ni siquiera un usuario root en un contenedor podrá acceder a los archivos de un host en un volumen montado si las etiquetas no son precisas.

SELinux funciona en tres modos: Aplicar, Permisivo y Desactivado. Aplicar activa la aplicación de las políticas SELinux, Permisivo proporciona advertencias y Desactivado consiste en dejar de utilizar las políticas SELinux. Las propias políticas SELinux pueden clasificarse a su vez en Dirigidas y Estrictas, en las que las Dirigidas se aplican a procesos concretos y las Estrictas a todos los procesos.

A continuación se muestra la etiqueta SELinux para los binarios Docker en un host, que consiste en <user:role:type:level>. Aquí verás el tipo, que es container_runtime_exec_t:

$ ls -Z /usr/bin/docker*
-rwxr-xr-x. root root system_u:object_r:container_runtime_exec_t:s0
/usr/bin/docker
-rwxr-xr-x. root root system_u:object_r:container_runtime_exec_t:s0
/usr/bin/docker-current
-rwxr-xr-x. root root system_u:object_r:container_runtime_exec_t:s0
/usr/bin/docker-storage-setup

Para mejorar aún más SELinux, se utiliza la seguridad multicategoría (MCS), que permite a los usuarios etiquetar los recursos con una categoría. Así, a un archivo etiquetado con una categoría sólo pueden acceder los usuarios o procesos de esa categoría.

Una vez activado SELinux, un tiempo de ejecución de contenedor como Docker, podman o CRI-O elige una etiqueta MCS aleatoria para ejecutar el contenedor. Estas etiquetas MCS constan de dos números aleatorios entre 1 y 1023, y llevan como prefijo el carácter "c" (categoría) y un nivel de sensibilidad (es decir, s0). Así, una etiqueta MCS completa tiene el aspecto "s0:c1,c2". Como se muestra en la Figura 4-5, un contenedor no podrá acceder a un archivo en un host o volumen de Kubernetes a menos que esté etiquetado correctamente como necesario. Esto proporciona un aislamiento importante entre la interacción de recursos, que evita muchas vulnerabilidades de seguridad dirigidas a escapar de los contenedores.

Figura 4-5. SELinux reforzando el acceso al sistema de archivos

A continuación se muestra un ejemplo de un pod implementado con perfil SELinux; este pod no podrá acceder a ningún archivo montado en el volumen del host a menos que estén etiquetados como so:c123,c456 en el host. Aunque veas todo el host, el sistema de archivos está montado en el pod:

apiVersion: v1
metadata:
  name: pod-se-linux-label
  namespace: default
  labels:
    app: normal-app
spec:
  containers:
  - name: app-container
    image: alpine:latest
    args: ["sleep", "10000"]
    securityContext:
      seLinuxOptions:
        level: "s0:c123,c456"
  volumes:
    - name: rootfs
      hostPath:
        path: /

La Tabla 4-3 enumera los CVE relativos a la fuga de contenedores que se evitan con sólo activar SELinux en los hosts. Aunque las políticas SELinux pueden ser difíciles de mantener, son fundamentales para una estrategia de defensa en profundidad. Openshift, una distribución de Kubernetes, viene con SELinux activado en su configuración por defecto con políticas específicas; para otras distribuciones merece la pena comprobar el estado.

Tabla 4-3. CVEs relacionados con el escape de contenedores
CVE Descripción Bloqueado por SELinux
CVE-2019-5736 Permite a los atacantes sobrescribir el binario runc del host y, en consecuencia, obtener acceso root al host
CVE-2016-9962 Vulnerabilidad RunC exec
CVE-2015-3627 Explotación insegura del descriptor de archivo

Kubernetes proporciona las siguientes opciones para aplicar SELinux en los PSP:

Valor Descripción
DebeCorrerComo Necesitas tener seLinuxOptions configurado como se muestra en la Figura 4-5.
RunAsAny No se proporcionan valores predeterminados en PSP (pueden configurarse opcionalmente en el pod y en las Implementaciones)

AppArmor

Al igual que SELinux, AppArmor se desarrolló para los sistemas operativos Debian y Ubuntu . AppArmor funciona de forma similar a SELinux, donde un perfil AppArmor define a qué tiene acceso un proceso. Veamos un ejemplo de perfil AppArmor:

#include <tunables/global>
/{usr/,}bin/ping flags=(complain) {
  #include <abstractions/base>
  #include <abstractions/consoles>
  #include <abstractions/nameservice>

  capability net_raw,
  capability setuid,
  network inet raw,

  /bin/ping mixr,
  /etc/modules.conf r,

  # Site-specific additions and overrides. See local/README for details.
  #include <local/bin.ping>
}

Aquí una utilidad ping sólo tiene tres permisos (es decir, net_raw, setuid e inet raw y acceso de lectura a /etc/modules.conf). Con estos permisos, una utilidad ping no puede modificar ni escribir en el sistema de archivos (claves, binarios, configuraciones, persistencia) ni cargar ningún módulo, lo que reduce la superficie de ataque para que la utilidad ping realice cualquier actividad maliciosa en caso de compromiso.

Por defecto, tu tiempo de ejecución de Kubernetes, como Docker, podman o CRI-O, proporciona un perfil AppArmor. El perfil del tiempo de ejecución de Docker se proporciona como referencia.

Dado que AppArmor es mucho más flexible y fácil de trabajar, recomendamos tener una política por microservicio. Kubernetes proporciona las siguientes opciones para aplicar estas políticas mediante anotaciones PSP:

Valor Descripción
tiempo de ejecución/por defecto Política por defecto del tiempo de ejecución
localhost/<nombre_de_perfil> Aplicar el perfil cargado en el host, normalmente en el directorio /sys/kernel/security/apparmor/profiles
no confinado No se cargará ningún perfil

Sysctl

Kubernetes sysctl te permite utilizar la interfaz sysctl para utilizar y configurar los parámetros del núcleo en tu clúster. Un ejemplo de uso de sysctls es la gestión de contenedores con cargas de trabajo que consumen muchos recursos y deben gestionar un gran número de conexiones simultáneas o necesitan un conjunto de parámetros especiales (por ejemplo, el reenvío IPv6) para ejecutarse eficientemente. En estos casos, sysctl proporciona una forma de modificar el comportamiento del núcleo sólo para esas cargas de trabajo sin afectar al resto del clúster.

Los sysctl se clasifican en dos categorías: seguros e inseguros. El sysctl seguro sólo afecta a los contenedores, pero el sysctl inseguro afecta al contenedor y al nodo en el que se ejecuta. Sysctl permite a los administradores establecer ambos cubos de sysctl a su discreción.

Tomemos un ejemplo en el que un servidor web en contenedor necesita gestionar un número elevado de conexiones concurrentes y necesita establecer el valor net.core.somaxconn en un número superior al predeterminado del núcleo. En este caso, se puede establecer de la siguiente manera:

apiVersion: v1
kind: Pod
metadata:
  name: sysctl-example
spec:
  securityContext:
    sysctls:
    - name: net.core.somaxconn
      value: "1024"

Ten en cuenta que te recomendamos que utilices la afinidad de nodo para programar cargas de trabajo en nodos que tengan la sysctl aplicada, en caso de que necesites utilizar una sysctl que se aplique al nodo. El siguiente ejemplo muestra cómo los PSP permiten prohibir o permitir sysctls:

apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
  name: sysctl-psp
spec:
  allowedUnsafeSysctls:
  - kernel.msg*
  forbiddenSysctls:
  - kernel.shm_rmid_forced

Conclusión

En este capítulo hemos tratado las herramientas y buenas prácticas para definir e implementar la seguridad en tiempo de ejecución de tu carga de trabajo. Los puntos más importantes son:

  • Las políticas de seguridad Pod son una forma excelente de habilitar controles de la carga de trabajo en el momento de su creación. Tienen limitaciones, pero pueden utilizarse eficazmente.

  • Tienes que elegir una solución que sea nativa de Kubernetes para los procesos de monitoreo e implantar controles basados en tu modelo de amenazas para tus cargas de trabajo.

  • Te recomendamos que revises las distintas opciones de seguridad disponibles en el núcleo de Linux y aproveches el conjunto adecuado de funciones en función de tu caso de uso.

Get Seguridad y observabilidad de Kubernetes now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.