Capítulo 4. Endurecimiento del sistema

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

El dominio "endurecimiento del sistema" se ocupa de los aspectos de seguridad relevantes para el sistema host subyacente que ejecuta los nodos del clúster Kubernetes. Los temas tratados aquí tocan técnicas y opciones de configuración que son fundamentalmente funciones básicas de Linux. Esto incluye deshabilitar servicios y eliminar paquetes, gestionar usuarios y grupos, deshabilitar puertos y configurar reglas de cortafuegos. Por último, este capítulo trata de las herramientas de endurecimiento del núcleo Linux que pueden restringir las operaciones que un proceso que se ejecuta en un contenedor puede realizar a nivel de host.

A alto nivel, este capítulo abarca los siguientes conceptos:

  • Minimizar la huella del SO anfitrión

  • Minimizar los roles IAM

  • Minimizar el acceso externo a la red

  • Utilizar herramientas de endurecimiento del núcleo como AppArmor y seccomp

Minimizar la huella del SO anfitrión

Los nodos del clúster se ejecutan en máquinas físicas o virtuales. En la mayoría de los casos, el sistema operativo de esas máquinas es una distribución de Linux. Evidentemente, el sistema operativo puede exponer vulnerabilidades de seguridad.

Con el tiempo, tendrás que mantener actualizada la versión del sistema operativo con las últimas correcciones de seguridad. Este proceso podría implicar actualizar el sistema operativo de un nodo de Ubuntu 18 a 22, por ejemplo. La actualización del sistema operativo está fuera del alcance de este libro; para más información, consulta la documentación pertinente de Linux.

Muchas distribuciones de Linux, como Ubuntu, vienen con herramientas, aplicaciones y servicios adicionales que no son necesariamente necesarios para el funcionamiento del clúster Kubernetes. Es tu trabajo como administrador identificar los riesgos de seguridad, desactivar o eliminar cualquier funcionalidad específica del sistema operativo que pueda exponer vulnerabilidades, y mantener el sistema operativo parcheado para incorporar las últimas correcciones de seguridad. Cuanta menos funcionalidad tenga un sistema operativo, menor será el riesgo.

Benchmark CIS para Ubuntu Linux

Como guía de referencia, puedes comparar la configuración de tu sistema operativo con el punto de referencia CIS para Ubuntu Linux.

Escenario: Un atacante explota la vulnerabilidad de un paquete

La Figura 4-1 ilustra a un atacante explotando una vulnerabilidad de un paquete instalado en el sistema. Por ejemplo, la aplicación podría ser el gestor de paquetes snapd. Supongamos que el atacante aprovecha la vulnerabilidad conocida USN-5292-1 que tiene el potencial de exponer información sensible a un atacante.

ckss 0401
Figura 4-1. Un atacante explota una vulnerabilidad a nivel de sistema operativo

La siguiente sección explicará cómo minimizar los riesgos de seguridad de los servicios y paquetes que no son realmente necesarios para el funcionamiento de Kubernetes, simplemente desactivándolos o eliminándolos.

Desactivar Servicios

En Linux, muchas aplicaciones se ejecutan como servicios en segundo plano. Los servicios pueden gestionarse mediante la herramienta de línea de comandos systemctl. El siguiente comando systemctl lista todos los servicios en ejecución:

$ systemctl | grep running
...
snapd.service   loaded active running   Snap Daemon

Uno de los servicios que no necesitaremos para hacer funcionar un nodo de clúster es el gestor de paquetes snapd. Para más detalles sobre el servicio, obtén su estado con el subcomando status:

$ systemctl status snapd
● snapd.service - Snap Daemon
     Loaded: loaded (/lib/systemd/system/snapd.service; enabled; vendor \
     preset: enabled)
     Active: active (running) since Mon 2022-09-19 22:49:56 UTC; 30min ago
TriggeredBy: ● snapd.socket
   Main PID: 704 (snapd)
      Tasks: 12 (limit: 2339)
     Memory: 45.9M
     CGroup: /system.slice/snapd.service
             └─704 /usr/lib/snapd/snapd

Puedes detener el servicio utilizando el subcomando systemctl stop :

$ sudo systemctl stop snapd
Warning: Stopping snapd.service, but it can still be activated by:
  snapd.socket

Ejecuta el subcomando disable para evitar que el servicio se inicie de nuevo al reiniciarse el sistema:

$ sudo systemctl disable snapd
Removed /etc/systemd/system/multi-user.target.wants/snapd.service.

El servicio se ha detenido y desactivado:

$ systemctl status snapd
● snapd.service - Snap Daemon
     Loaded: loaded (/lib/systemd/system/snapd.service; disabled; vendor \
     preset: enabled)
     Active: inactive (dead) since Mon 2022-09-19 23:22:22 UTC; 4min 4s ago
TriggeredBy: ● snapd.socket
   Main PID: 704 (code=exited, status=0/SUCCESS)

Eliminar paquetes no deseados

Ahora que el servicio se ha desactivado, ya no tiene sentido mantener el paquete. Puedes eliminar el paquete para liberar más espacio en disco y memoria. Puedes utilizar el comando apt purge para eliminar un paquete y sus paquetes transitivos, como se muestra a continuación:

$ sudo apt purge --auto-remove snapd
Reading package lists... Done
Building dependency tree
Reading state information... Done
The following packages will be REMOVED:
  snapd* squashfs-tools*
0 upgraded, 0 newly installed, 2 to remove and 116 not upgraded.
After this operation, 147 MB disk space will be freed.
Do you want to continue? [Y/n] y
...

Puedes utilizar el mismo comando aunque el paquete no esté controlado por un servicio. Identifica los paquetes que no necesites y elimínalos sin más. Deberías acabar con un espacio mucho más reducido en tu sistema.

Un atacante potencial ya no podrá utilizar el servicio snapd para explotar el sistema. Debes repetir el proceso para cualquier servicio no deseado. Como resultado, el servicio snapd deja de existir en el sistema:

$ systemctl status snapd
Unit snapd.service could not be found.

Minimizar los roles IAM

La gestión de identidades y accesos (IAM) a nivel de sistema implica la gestión de los usuarios de Linux, los grupos a los que pertenecen y los permisos que se les conceden. Cualquier directorio y archivo tendrá permisos asignados a un usuario.

La gestión adecuada de usuarios y accesos es una responsabilidad clásica de todo administrador de sistemas. Aunque puede que tu función como administrador de Kubernetes no implique directamente IAM a nivel de sistema, es importante que comprendas las implicaciones para la seguridad. Es probable que tengas que trabajar con un compañero para reforzar el sistema que ejecuta el clúster de Kubernetes.

Esta sección proporcionará una breve introducción sobre cómo gestionar usuarios y grupos. También hablaremos de cómo establecer los permisos y la propiedad de los archivos para minimizar el acceso en la medida de lo posible. En este libro sólo arañaremos la superficie del tema. Para más información, consulta la documentación de Linux de tu elección.

Escenario: Un Atacante Utiliza Credenciales para Obtener Acceso a Archivos

Una brecha de seguridad puede conducir al robo de credenciales de usuario. Obtener acceso a credenciales de usuario válidas abre la puerta a vectores de ataque adicionales. La Figura 4-2 muestra a un atacante que puede entrar en un nodo del cluster con credenciales de usuario robadas y ahora puede interactuar con todos los archivos y directorios con los permisos concedidos al usuario.

ckss 0402
Figura 4-2. Un atacante utiliza credenciales robadas para acceder a archivos

Se recomienda seguir el principio del menor privilegio. Concede permisos administrativos sólo a un grupo limitado de usuarios. A los demás usuarios sólo se les debe permitir realizar las operaciones necesarias para desempeñar su trabajo.

Comprender la gestión de usuarios

Todo usuario debe autenticarse en un sistema para poder utilizarlo. El usuario autenticado tiene acceso a los recursos en función de los permisos asignados. Esta sección te guiará a través de las principales operaciones necesarias para gestionar usuarios.

Listado de usuarios

Para listar todos los usuarios del sistema, renderiza el contenido del archivo /etc/passwd. Cada entrada sigue el patrón general username:password:UID:GID:com⁠ment:​home:shell. Algunos de los campos del patrón pueden estar vacíos:

$ cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
...

La salida del comando muestra al usuario root en la primera posición de la salida. La última parte de la cadena para el usuario root, /bin/bash, indica que el usuario puede iniciar sesión en el sistema con un intérprete de comandos bash. Es posible que a otros usuarios no se les permita iniciar sesión en absoluto. Para esos usuarios, encontrarás la cadena /usr/sbin/nologin asignada al campo shell.

En un momento dado, puedes ver qué procesos han iniciado los usuarios. El siguiente comando muestra todos los procesos de bash, incluido el usuario correspondiente que lo inició:

$ ps aux | grep bash
root         956  0.0  0.4  22512 19200 pts/0    Ss   17:57   0:00 -bash
root        7064  0.0  0.0   6608  2296 pts/0    S+   18:08   0:00 grep \
--color=auto bash

Añadir un usuario

En algún momento, puede que quieras dar acceso a los miembros del equipo a las máquinas que ejecutan los nodos del clúster, con permisos limitados. Puedes añadir nuevos usuarios al sistema con el comando adduser. Añade la bandera --shell /sbin/nologin para desactivar el acceso shell del usuario. El siguiente comando crea el usuario ben:

$ sudo adduser ben
Adding user ‘ben’ ...
Adding new group ‘ben’ (1001) ...
Adding new user ‘ben’ (1001) with group ‘ben’ ...
Creating home directory ‘/home/ben’ ...
Copying files from ‘/etc/skel’ ...
New password:
Retype new password:
...

La entrada de usuario se ha añadido al archivo /etc/passwd:

$ cat /etc/passwd
...
ben:x:1001:1001:,,,:/home/ben:/bin/bash

Cambiar a un usuario

Puedes cambiar el usuario en un intérprete de comandos utilizando el comando su. El siguiente comando cambia al usuario ben que creamos anteriormente. Se te pedirá que introduzcas la contraseña del usuario:

$ su ben
Password:
ben@controlplane:/root$ pwd
/root

El intérprete de comandos indicará el usuario actual mediante su prompt. Heredarás las variables de entorno de la cuenta que utilizaste al ejecutar el comando su. Para crear un nuevo entorno, añade el guión con el comando su:

$ su - ben
ben@controlplane:~$ pwd
/home/ben

Otra forma de cambiar temporalmente de usuario es utilizando el comando sudo. Necesitarás tener privilegios elevados para ejecutar el comando. Por tanto, el comando sudo equivale a "ejecuta este comando como administrador":

$ sudo -u ben pwd
/root

Eliminar un usuario

Los miembros de un equipo, representados por usuarios en el sistema, pasan a otros equipos o simplemente pueden abandonar la empresa. Querrás revocar el acceso al usuario para evitar el uso no autorizado de las credenciales. El siguiente comando elimina el usuario, incluido su directorio personal:

$ sudo userdel -r ben

Comprender la gestión de grupos

Para un administrador de sistemas es más cómodo agrupar a los usuarios con requisitos de acceso similares que controlar los permisos a nivel de usuario individual. Los sistemas Linux ofrecen el concepto de grupo como forma de organizar a los usuarios en función de equipos o de funciones organizativas específicas. Trataremos brevemente los aspectos más importantes de la gestión de grupos.

Listado de grupos

Los grupos pueden listarse inspeccionando el contenido del archivo /etc/group. Cada entrada sigue el patrón general groupname:password:GID:group members:

$ cat /etc/group
root:x:0:
plugdev:x:46:packer
nogroup:x:65534:
...

Como puedes ver en el resultado, algunos de los campos pueden estar vacíos. El único grupo con un miembro asignado es plugdev, cuyo nombre es packer.

Añadir un grupo

Utiliza el comando groupadd para añadir un nuevo grupo. El siguiente ejemplo añade el grupo kube-developers:

$ sudo groupadd kube-developers

El grupo aparecerá ahora en el archivo /etc/group. Observa que el identificador del grupo es 1004:

$ cat /etc/group
...
kube-developers:x:1004:

Asignar un usuario a un grupo

Para asignar un grupo a un usuario, utiliza el comando usermod. El siguiente comando añade el usuario ben al grupo kube-developers:

$ sudo usermod -g kube-developers ben

El identificador de grupo 1004 actúa como sustituto del grupo kube-developers:

$ cat /etc/passwd | grep ben
ben:x:1001:1004:,,,:/home/ben:/bin/bash

Borrar un grupo

A veces quieres deshacerte por completo de un grupo. Puede que el rol organizativo se refiera a un grupo Linux que ya no existe. Utiliza el comando groupdel para eliminar un grupo. Recibirás un mensaje de error si los miembros siguen formando parte del grupo:

$ sudo groupdel kube-developers
groupdel: cannot remove the primary group of user ben

Antes de eliminar un grupo, debes reasignar a sus miembros a un grupo diferente utilizando el comando usermod. El siguiente comando cambia el grupo de kube-developers a kube-admins. Asume que el grupo kube-admins ya ha sido creado anteriormente:

$ sudo usermod -g kube-admins ben
$ sudo groupdel kube-developers

Comprender los permisos y la propiedad de los archivos

Asignar los permisos de archivo con el mínimo acceso posible es crucial para maximizar la seguridad. Aquí es donde entran en juego los permisos y la propiedad de archivos en Linux. Sólo voy a discutir las operaciones relevantes a alto nivel. Consulta la entrada del blog de la Fundación Linux sobre permisos de archivos Linux para obtener más detalles.

Ver los permisos y la propiedad de los archivos

Todos los usuarios pueden crear nuevos directorios y archivos. Por ejemplo, puedes utilizar el comando touch para crear un archivo vacío. El siguiente comando crea un archivo con el nombre my-file en el directorio actual:

$ touch my-file

Para ver el contenido de un directorio en formato "largo", utiliza el comando ls. El formato largo de la salida solicitada por el parámetro de línea de comandos -l muestra los permisos y la propiedad de los archivos:

$ ls -l
total 0
-rw-r--r-- 1 root root 0 Sep 26 17:53 my-file

La parte importante de la salida es -rw-r--r--. El primer carácter es un carácter de permiso especial que puede variar según el sistema, seguido de tres agrupaciones con la notación rwx. Los tres primeros caracteres corresponden a los permisos del propietario, la segunda agrupación de tres caracteres corresponde a los permisos de grupo y los tres últimos caracteres representan los permisos de todos los usuarios. El símbolo r significa permisos de lectura, w representa permisos de escritura y x se refiere a permisos de ejecución. En el ejemplo anterior, el usuario root puede leer y escribir en el archivo, mientras que el grupo y todos los demás usuarios sólo pueden leer el archivo.

Cambiar la propiedad de un archivo

Utiliza el comando chown para cambiar la asignación de usuario y grupo de un archivo o directorio. La sintaxis del comando sigue el patrón chown owner:group filename. El siguiente comando cambia la propiedad del archivo al usuario ben pero no reasigna un grupo. El usuario que ejecuta el comando chown necesita tener permisos de escritura:

$ chown ben my-file
$ ls -l
total 0
-rw-r--r-- 1 ben  root 0 Sep 26 17:53 my-file

Cambiar los permisos de los archivos

Puedes añadir o eliminar permisos con el comando chmod en diversas notaciones. Por ejemplo, utiliza el siguiente comando para eliminar los permisos de escritura del propietario del archivo:

$ chmod -w file1
$ ls -l
total 0
-r--r--r-- 1 ben  root 0 Sep 26 17:53 my-file

Minimizar el acceso externo a la red

El acceso externo a los nodos de tu clúster sólo debe permitirse para los puertos necesarios para el funcionamiento de Kubernetes. Ya hemos hablado de los puertos estándar de Kubernetes en "Protección de los metadatos y puntos finales de los nodos". El acceso a todos los demás puertos debe estar bloqueado.

Identificar y desactivar puertos abiertos

Aplicaciones como servidores FTP, servidores web y servicios de archivos e impresión como Samba abren puertos como medio de exponer un punto final de comunicación a los clientes. Ejecutar aplicaciones que abren la comunicación de red puede exponer un riesgo de seguridad. Puedes eliminar el riesgo simplemente desactivando el servicio y desinstalando la aplicación.

Supongamos que instalamos el servidor web Apache 2 HTTP en un nodo del plano de control con los siguientes comandos:

$ sudo apt update
$ sudo apt install apache2

Actualización sobre el comando netstat

El comando netstat ha quedado obsoleto en favor del comando ss, más rápido y más legible. Para más información, consulta la documentación del sistema operativo que estés utilizando.

Podemos inspeccionar todos los puertos abiertos utilizando la herramienta de línea de comandos ss, una utilidad con una funcionalidad similar a netstat. El siguiente comando muestra todos los puertos abiertos, incluidos sus procesos. Entre ellos está el puerto 80, expuesto por Apache 2:

$ sudo ss -ltpn
State    Recv-Q   Send-Q   Local Address:Port   Peer Address:Port   Process
...
LISTEN   0        511      *:80                 *:*                 users: \
(("apache2",pid=18435,fd=4),("apache2",pid=18434,fd=4),("apache2", ]\
pid=18432,fd=4))

Puede que sólo hayas necesitado el servidor web temporalmente y que simplemente te hayas olvidado de instalarlo. Actualmente, el proceso está gestionado por un servidor. Puedes revisar el estado de un servicio con el comando systemctl status:

$ sudo systemctl status apache2
● apache2.service - The Apache HTTP Server
     Loaded: loaded (/lib/systemd/system/apache2.service; enabled; vendor \
     preset: enabled)
     Active: active (running) since Tue 2022-09-20 22:25:25 UTC; 39s ago
       Docs: https://httpd.apache.org/docs/2.4/
   Main PID: 18432 (apache2)
      Tasks: 55 (limit: 2339)
     Memory: 5.6M
     CGroup: /system.slice/apache2.service
             ├─18432 /usr/sbin/apache2 -k start
             ├─18434 /usr/sbin/apache2 -k start
             └─18435 /usr/sbin/apache2 -k start

Kubernetes no necesita Apache 2. Decidimos cerrar el servicio y desinstalar el paquete:

$ sudo systemctl stop apache2
$ sudo systemctl disable apache2
Synchronizing state of apache2.service with SysV service script with \
/lib/systemd/systemd-sysv-install.
Executing: /lib/systemd/systemd-sysv-install disable apache2
Removed /etc/systemd/system/multi-user.target.wants/apache2.service.
$ sudo apt purge --auto-remove apache2

Comprueba que el puerto ya no se utiliza. El comando ss ya no encuentra ninguna aplicación que exponga el puerto 80:

$ sudo ss -ltpn | grep :80

Configurar las reglas del cortafuegos

Otra forma de controlar los puertos es con la ayuda de un cortafuegos a nivel de sistema operativo. En Linux, puedes utilizar el Cortafuegos Sin Complicaciones (UFW). Esta sección te dará una breve introducción sobre cómo activar el UFW y cómo configurar las reglas del cortafuegos.

Siguiendo el principio del menor privilegio, es buena idea empezar por activar el cortafuegos y establecer reglas de denegación para cualquier tráfico de red entrante y saliente. Los siguientes comandos muestran los pasos para conseguirlo:

$ sudo ufw allow ssh
Rules updated
Rules updated (v6)
$ sudo ufw default deny outgoing
Default outgoing policy changed to deny
(be sure to update your rules accordingly)
$ sudo ufw default deny incoming
Default incoming policy changed to deny
(be sure to update your rules accordingly)
$ sudo ufw enable
Command may disrupt existing ssh connections. Proceed with operation (y|n)? y
Firewall is active and enabled on system startup

Querrás permitir que herramientas externas como kubectl se conecten al servidor API que se ejecuta en el puerto 6443. En el nodo del plano de control, ejecuta el siguiente comando para permitir el acceso al puerto del servidor API:

$ sudo ufw allow 6443
Rule added
Rule added (v6)

Tendrás que repetir el mismo proceso para abrir otros puertos en el plano de control y en los nodos trabajadores. Asegúrate de que todos los demás puertos que no sean necesarios para el funcionamiento de Kubernetes estén bloqueados.

Uso de herramientas de endurecimiento del núcleo

Las aplicaciones o procesos que se ejecutan dentro de un contenedor pueden realizar llamadas al sistema. Un ejemplo típico podría ser el comando curl realizando una solicitud HTTP. Una llamada al sistema es una abstracción programática que se ejecuta en el espacio de usuario para solicitar un servicio al núcleo. Podemos restringir qué llamadas al sistema se permite realizar con la ayuda de herramientas de endurecimiento del núcleo. El examen CKS menciona explícitamente dos herramientas en este espacio, AppArmor y seccomp. Hablaremos de ambas herramientas y de la mecánica para integrarlas con Kubernetes.

Uso de AppArmor

AppArmor proporciona control de acceso a los programas que se ejecutan en un sistema Linux. La herramienta implementa una capa de seguridad adicional entre las aplicaciones invocadas en el espacio de usuario y la funcionalidad subyacente del sistema. Por ejemplo, podemos restringir las llamadas a la red o la interacción con el sistema de archivos. Muchas distribuciones de Linux (por ejemplo, Debian, Ubuntu, openSUSE) ya incluyen AppArmor. Por tanto, no es necesario instalarAppArmor manualmente. Las distribuciones de Linux que no son compatibles conAppArmor utilizan en su lugar Security-Enhanced Linux (SELinux), que adopta un enfoque similar al de AppArmor. Entender SELinux está fuera del alcance del examen CKS.

Comprender los perfiles

Las reglas que definen lo que un programa puede o no puede hacer se definen en un perfil de AppArmor. Cada perfil debe cargarse en AppArmor antes de que pueda surtir efecto. AppArmor proporciona una herramienta de línea de comandos para comprobar los perfiles que se han cargado. Ejecuta el comando aa-status para ver un resumen de todos los perfiles cargados. Verás que AppArmor ya viene con un conjunto de perfiles de aplicación predeterminados para proteger los servicios de Linux:

$ sudo aa-status
apparmor module is loaded.
31 profiles are loaded.
31 profiles are in enforce mode.
   /snap/snapd/15177/usr/lib/snapd/snap-confine
   ...
0 profiles are in complain mode.
14 processes have profiles defined.
14 processes are in enforce mode.
   /pause (11934) docker-default
   ...
0 processes are in complain mode.
0 processes are unconfined but have a profile defined.

El modo de perfil determina el tratamiento de las reglas en tiempo de ejecución si se produce un evento coincidente. AppArmor distingue dos tipos de modos de perfil:

Haz cumplir

El sistema aplica las reglas, informa de la infracción y la escribe en el syslog. Querrás utilizar este modo para impedir que un programa realice determinadas llamadas.

Quéjate

El sistema no aplica las reglas, pero escribirá las infracciones en el registro. Este modo es útil si quieres descubrir las llamadas que hace un programa.

El ejemplo 4-1 define un perfil personalizado en el archivo k8s-deny-write para restringir el acceso de escritura a archivos. El archivo debe colocarse en el directorio /etc/apparmor.d de cada nodo trabajador que ejecute carga de trabajo. Está fuera del alcance de este libro explicar todas las reglas en detalle. Para más información, echa un vistazo a lawiki de AppArmor.

Ejemplo 4-1. Un perfil AppArmor para restringir el acceso de escritura a archivos
#include <tunables/global>

profile k8s-deny-write flags=(attach_disconnected) { 1
  #include <abstractions/base>

  file, 2

  deny /** w, 3
}
1

El identificador que aparece después de la palabra clave profile es el nombre del perfil.

2

Aplicar a operaciones de archivo.

3

Deniega todas las escrituras de archivos.

Establecer un perfil personalizado

Para cargar el perfil en AppArmor, ejecuta el siguiente comando en el nodo trabajador:

$ sudo apparmor_parser /etc/apparmor.d/k8s-deny-write

Por defecto, el comando utiliza el modo aplicar. Para cargar el perfil en modo quejarse, utiliza la opción -C. El comando aa-status listará ahora el perfil además de los perfiles por defecto. Como puedes ver en la salida, el perfil aparece en modo enforce:

$ sudo aa-status
apparmor module is loaded.
32 profiles are loaded.
32 profiles are in enforce mode.
   k8s-deny-write
   ...

AppArmor admite comandos de conveniencia adicionales como parte de un paquete de utilidades. Puedes instalar manualmente el paquete utilizando los siguientes comandos si quieres utilizarlos:

$ sudo apt-get update
$ sudo apt-get install apparmor-utils

Una vez instalado, puedes utilizar el comando aa-enforce para cargar un perfil en modo enforce, y aa-complain para cargar un perfil en modo complain. Para el examen, probablemente sea más fácil utilizar el comando estándar apparmor_parser.

Aplicar un perfil a un contenedor

Tienes que asegurarte de un par de requisitos previos antes de utilizar las reglas de AppArmor en la definición de un Pod. En primer lugar, el tiempo de ejecución del contenedor debe ser compatible con AppArmor para que las reglas surtan efecto. Además, AppArmor tiene que estar instalado en el nodo trabajador que ejecuta el Pod. Por último, asegúrate de que has cargado el perfil, como se describe en la sección anterior.

El Ejemplo 4-2 muestra un manifiesto YAML para un Pod definido en el archivo pod.yaml. Para aplicar un perfil al contenedor, tendrás que establecer una anotación específica. La clave de la anotación debe utilizar la clave con el formato container.apparmor.security.beta.​kuber⁠netes.io/<container-name>. En nuestro caso, el nombre del contenedor es hello. La clave completa es container.apparmor.security.beta.kubernetes.io/hello. El valor de la anotación sigue el patrón localhost/<profile-name>. El perfil personalizado que queremos utilizar aquí es k8s-deny-write. Para más información sobre las opciones de configuración, consulta la documentación de Kubernetes.

Ejemplo 4-2. Un Pod aplicando un perfil AppArmor a un contenedor
apiVersion: v1
kind: Pod
metadata:
  name: hello-apparmor
  annotations:
    container.apparmor.security.beta.kubernetes.io/hello: \ 1
    localhost/k8s-deny-write 2
spec:
  containers:
  - name: hello 3
    image: busybox:1.28
    command: ["sh", "-c", "echo 'Hello AppArmor!' && sleep 1h"]
1

La clave de la anotación que consiste en un prefijo codificado y el nombre del contenedor separados por un carácter de barra oblicua.

2

El nombre del perfil disponible en el nodo actual indicado por localhost.

3

El nombre del contenedor.

Ya estamos listos para crear el Pod. Ejecuta el comando apply y dirígelo al manifiesto YAML. Espera a que el Pod pase al estado "En ejecución":

$ kubectl apply -f pod.yaml
pod/hello-apparmor created
$ kubectl get pod hello-apparmor
NAME             READY   STATUS    RESTARTS   AGE
hello-apparmor   1/1     Running   0          4s

Ahora puedes entrar en el contenedor y realizar una operación de escritura de archivos:

$ kubectl exec -it hello-apparmor -- /bin/sh
/ # touch test.txt
touch: test.txt: Permission denied

AppArmor impedirá escribir un archivo en el sistema de archivos del contenedor. Se mostrará el mensaje "Permiso denegado" si intentas realizar la operación.

Utilizar seccomp

Seccomp, abreviatura de "Modo de Computación Segura", es otra característica del núcleo Linux que puede restringir las llamadas realizadas desde el espacio de usuario al núcleo. Un perfil seccomp es el mecanismo para definir las reglas para restringir las llamadas al sistema y sus argumentos. Utilizar seccomp puede reducir el riesgo de explotar una vulnerabilidad del núcleo Linux. Para más información sobre seccomp en Kubernetes, consulta ladocumentación.

Aplicar el perfil de tiempo de ejecución de contenedor por defecto a un contenedor

Los tiempos de ejecución de contenedores, como Docker Engine o containerd, se suministran con un perfil seccomp predeterminado. El perfil seccomp por defecto permite las llamadas al sistema más utilizadas por las aplicaciones, al tiempo que prohíbe el uso de llamadas al sistema consideradas peligrosas.

Kubernetes no aplica el perfil de tiempo de ejecución de contenedor predeterminado a los contenedores al crear un Pod, pero puedes activarlo utilizando la puerta de función SeccompDefault. Alternativamente, puedes optar por la característica Pod por Pod estableciendo el tipo de perfil seccomp en RuntimeDefault con la ayuda del atributo de contexto de seguridad seccompProfile. El Ejemplo 4-3 demuestra su uso.

Ejemplo 4-3. Un Pod aplicando el perfil seccomp por defecto proporcionado por el perfil de ejecución del contenedor
apiVersion: v1
kind: Pod
metadata:
  name: hello-seccomp
spec:
  securityContext:
    seccompProfile:
      type: RuntimeDefault 1
  containers:
  - name: hello
    image: busybox:1.28
    command: ["sh", "-c", "echo 'Hello seccomp!' && sleep 1h"]
1

Aplica el perfil de tiempo de ejecución predeterminado del contenedor.

Puedes iniciar el Pod utilizando el comando apply y apuntando al manifiesto YAML. El Pod debería pasar al estado "En ejecución":

$ kubectl apply -f pod.yaml
pod/hello-seccomp created
$ kubectl get pod hello-seccomp
NAME            READY   STATUS    RESTARTS   AGE
hello-seccomp   1/1     Running   0          4s

El comando echo ejecutado en el contenedor es considerado no problemático desde el punto de vista de la seguridad por el perfil seccomp por defecto. El siguiente comando inspecciona los registros del contenedor:

$ kubectl logs hello-seccomp
Hello seccomp!

La llamada estaba permitida y daba como resultado la escritura del mensaje "¡Hola seccomp!" en la salida estándar.

Establecer un perfil personalizado

Puedes crear y establecer tu propio perfil personalizado, además del perfil de tiempo de ejecución del contenedor predeterminado. El directorio estándar para esos archivos es /var/lib/kubelet/seccomp. Organizaremos nuestros perfiles personalizados en el subdirectorio profiles. Crea el directorio si aún no existe:

$ sudo mkdir -p /var/lib/kubelet/seccomp/profiles

Decidimos crear nuestro perfil personalizado en el archivo mkdir-violation.json del directorio de perfiles. El Ejemplo 4-4 muestra los detalles de la definición del perfil. En pocas palabras, el conjunto de reglas no permite el uso de la llamada al sistema mkdir.

Ejemplo 4-4. Un perfil seccomp que impide ejecutar una llamada al sistema mkdir
{
    "defaultAction": "SCMP_ACT_ALLOW", 1
    "architectures": [ 2
        "SCMP_ARCH_X86_64",
        "SCMP_ARCH_X86",
        "SCMP_ARCH_X32"
    ],
    "syscalls": [
        {
            "names": [
                "mkdir"
            ],
            "action": "SCMP_ACT_ERRNO" 3
        }
    ]
}
1

La acción por defecto se aplica a todas las llamadas al sistema. Aquí permitiremos todas las llamadas al sistema utilizando SCMP_ACT_ALLOW.

2

Puedes filtrar por arquitecturas concretas a las que deba aplicarse la acción por defecto. La definición del campo es opcional.

3

La acción por defecto puede sobrescribirse declarando reglas más precisas. La acción SCMP_ACT_ERRNO impedirá la ejecución de la llamada al sistema mkdir.

Colocar un perfil personalizado en el directorio /var/lib/kubelet/seccomp no aplica automáticamente las reglas a un Pod. Todavía tienes que configurar un Pod para utilizarlo.

Aplicar el perfil personalizado a un contenedor

La aplicación de un perfil personalizado sigue un patrón similar al de la aplicación del perfil de tiempo de ejecución del contenedor predeterminado, con pequeñas diferencias. Como puedes ver en el Ejemplo 4-5, apuntamos el atributo seccompProfile del perfil de seguridad al archivo mkdir-violation.json y establecemos el tipo Localhost.

Ejemplo 4-5. Un Pod que aplica un perfil seccomp personalizado impide una llamada al sistema mkdir
apiVersion: v1
kind: Pod
metadata:
  name: hello-seccomp
spec:
  securityContext:
    seccompProfile:
      type: Localhost 1
      localhostProfile: profiles/mkdir-violation.json 2
  containers:
  - name: hello
    image: busybox:1.28
    command: ["sh", "-c", "echo 'Hello seccomp!' && sleep 1h"]
    securityContext:
      allowPrivilegeEscalation: false
1

Hace referencia a un perfil del nodo actual.

2

Aplica el perfil con el nombre mkdir-violation.json en el subdirectorio profiles.

Crea el Pod utilizando el comando declarativo apply. Espera a que el Pod pase al estado "En ejecución":

$ kubectl apply -f pod.yaml
pod/hello-seccomp created
$ kubectl get pod hello-seccomp
NAME            READY   STATUS    RESTARTS   AGE
hello-seccomp   1/1     Running   0          4s

Accede al contenedor para verificar que seccomp ha aplicado correctamente las reglas aplicadas:

$ kubectl exec -it hello-seccomp -- /bin/sh
/ # mkdir test
mkdir: can't create directory test: Operation not permitted

Como puedes ver en la salida, la operación muestra un mensaje de error al intentar ejecutar el comando mkdir. Se ha infringido la regla del perfil personalizado.

Resumen

Abordar los aspectos de seguridad no se limita a los componentes del clúster Kubernetes o a la carga de trabajo. Hay muchas cosas que puedes hacer a nivel del sistema anfitrión. Hablamos de las diferentes capacidades del sistema operativo y de cómo utilizarlas para minimizar las posibles vulnerabilidades de seguridad.

Muchos sistemas operativos vienen con una gran cantidad de paquetes y servicios para ofrecer una experiencia más rica en funciones a los usuarios finales. Es importante identificar la funcionalidad que no se necesita para operar un clúster de Kubernetes. Depura rigurosamente los paquetes y servicios innecesarios y cierra los puertos que no necesites. También querrás limitar qué usuarios pueden acceder a determinados directorios, archivos y aplicaciones. Utiliza la gestión de usuarios de Linux para restringir los permisos.

Es muy habitual que las aplicaciones y procesos que se ejecutan en contenedores realicen llamadas al sistema. Puedes utilizar herramientas de endurecimiento del núcleo de Linux como AppArmor y seccomp para restringir esas llamadas. Sólo permite las llamadas al sistema cruciales para satisfacer las necesidades de tu aplicación que ejecuta el contenedor.

Aspectos esenciales del examen

Tener un conocimiento básico de las herramientas del sistema operativo Linux.

El examen CKS se centra principalmente en la funcionalidad de seguridad en Kubernetes. Este ámbito traspasa la frontera de las funciones de seguridad del SO Linux. No estará de más que explores herramientas y aspectos de seguridad específicos de Linux, independientemente del contenido tratado en este capítulo. A alto nivel, familiarízate con la gestión de servicios, paquetes, usuarios y redes en Linux.

Saber cómo integrar las herramientas de endurecimiento del núcleo Linux con Kubernetes.

AppArmor y seccomp son sólo algunas herramientas de endurecimiento del núcleo que pueden integrarse con Kubernetes para restringir las llamadas al sistema realizadas desde un contenedor. Practica el proceso de cargar un perfil y aplicarlo a un contenedor. Para ampliar tus horizontes, quizá también quieras explorar otras funcionalidades del kernel que funcionan junto con Kubernetes, como SELinux o sysctl.

Ejercicios de muestra

Las soluciones a estos ejercicios están disponibles en el Apéndice.

  1. Navega hasta el directorio app-a/ch04/close-ports del repositorio de GitHub bmuschko/cks-study-guide. Inicia las máquinas virtuales que ejecutan el clúster utilizando el comando vagrant up. El clúster consta de un único nodo del plano de control llamado kube-control-plane y un nodo trabajador llamado kube-worker-1. Una vez hecho esto, apaga el clúster utilizando vagrant destroy -f.

    Identifica el proceso que escucha en el puerto 21 en la VM kube-worker-1. Decidiste no exponer este puerto para reducir el riesgo de que los atacantes lo exploten. Cierra el puerto cerrando el proceso correspondiente.

    Requisitos previos: Este ejercicio requiere la instalación de las herramientas Vagrant y VirtualBox.

  2. Navega hasta el directorio app-a/ch04/apparmor del repositorio de GitHub bmuschko/cks-study-guide. Inicia las máquinas virtuales que ejecutan el clúster utilizando el comando vagrant up. El clúster consta de un único nodo del plano de control llamado kube-control-plane, y un nodo trabajador llamado kube-worker-1. Una vez hecho esto, apaga el clúster utilizando vagrant destroy -f.

    Crea un perfil AppArmor llamadonetwork-deny. El perfil debe impedir cualquier tráfico de red entrante y saliente. Añade el perfil al conjunto de reglas de AppArmor en modo de aplicación. Aplica el perfil al Pod llamado network-call que se ejecuta en el espacio de nombres default. Comprueba los registros del Pod para asegurarte de que ya no se pueden realizar llamadas de red.

    Requisitos previos: Este ejercicio requiere la instalación de las herramientas Vagrant y VirtualBox.

  3. Navega hasta el directorio app-a/ch04/seccomp del repositorio de GitHub bmuschko/cks-study-guide. Inicia las máquinas virtuales que ejecutan el clúster utilizando el comando vagrant up. El clúster consta de un único nodo del plano de control llamado kube-control-plane, y un nodo trabajador llamado kube-worker-1. Una vez hecho esto, apaga el clúster utilizando vagrant destroy -f.

    Crea un archivo de perfil seccomp llamado audit.json que registre todas las syscalls en el directorio seccomp estándar. Aplica el perfil al Pod llamado network-call que se ejecuta en el espacio de nombres default. Comprueba si hay entradas de registro en el archivo de registro /var/log/syslog.

    Requisitos previos: Este ejercicio requiere la instalación de las herramientas Vagrant y VirtualBox.

  4. Crea un nuevo Pod llamado sysctl-pod con la imagen nginx:1.23.1. Establece los parámetros sysctl net.core.somaxconn a 1024 y debug.iotrace a 1. Comprueba el estado del Pod.

Get Guía de estudio del Especialista certificado en seguridad de Kubernetes (CKS) 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.