Cómo mejorar la seguridad de tus despliegues en Kubernetes con KubeArmor

Índice

  1. Introducción
  2. ¿Por qué no usar Security Context?
  3. Ventajas de KubeArmor
  4. Instalación de KubeArmor
  5. KubeArmorPolicy y KubeArmorHostPolicy
  6. Security Postures
  7. Bonus track: fortificar una instalación de Kubernetes
  8. Conclusiones

1. Introducción

En este artículo vamos a hablar de KubeArmor, una herramienta muy potente para mejorar la seguridad de nuestros despliegues en Kubernetes mediante políticas que restringen el acceso a recursos (ficheros, capacidades del sistema operativo o recursos de red).

Haciendo uso de KubeArmor una organización puede definir políticas centralizadas que apliquen a todos los pods de un cluster o a pods que cumplan ciertas características y sin necesidad de que los desarrolladores tengan que modificar sus despliegues.

2. ¿Por qué no usar Security Context?

Antes que nada hay que aclarar que en un Kubernetes estándar ya es posible definir ciertas restricciones sobre el acceso a recursos dentro de un pod mediante el campo securityContext, sin necesidad de instalar ningún software externo.

Este Security Context permite definir políticas mediante SELinux, AppArmor, Seccomp y otras tecnologías para restringir el acceso a ficheros, la ejecución de programas o las capacidades del sistema operativo. Así pues, ¿por qué usar KubeArmor?

La respuesta es que Security Context tiene ciertas limitaciones o desventajas:

  • Definir políticas mediante AppArmor o SELinux es bastante complicado.
  • No es portable, es decir, si por ejemplo defines una política mediante AppAmor necesitas que tu cluster soporte esa tecnología, y si mueves tu pod a otro cluster que no la soporte, fallará. Esto resulta particularmente importante en entornos multi-cloud, donde por ejemplo un nodo de tu cluster podría estar en AWS (que soporta SELinux por defecto) y otro en Google Cloud (que soporta APPArmor).
  • A día de hoy no permite definir políticas con BPF-LSM (eBPF), que es una tecnología cada vez más popular y que a mi particularmente me gusta mucho 🙂

3. Ventajas de KubeArmor

La principal ventaja de KubeArmor es que nos permite definir políticas de una manera agnóstica a la tecnología subyacente (SELinux, AppArmor, BPF-LSM) usada por los nodos de nuestro cluster. De esta forma nuestras políticas seran portables (podríamos moverlas de un EKS a un AKS pasando por un GKE y seguirían funcionando de la misma manera, e incluso podríamos tener cargas de trabajo funcionando en nubes de distintos vendors simultáneamente).

Además de esto, la forma en la que se definen las políticas de KubeArmor es mucho más sencilla que en cualquiera de las tecnologías mencionadas anteriormente.

Otra ventaja de KubeArmor en comparación con otras aplicaciones es que se instala como un DaemonSet no privilegiado (al contrario que Tetragon, por ejemplo). Tener pods privilegiados en un cluster siempre entraña riesgos de seguridad, ya que si un atacante consigue acceder a los mismos puede ejecutar comandos como root e incluso acceder a los propios nodos del cluster sin mucho esfuerzo.

Si ya te he convencido de las bondades de KubeArmor, sigue leyendo 🙂

4. Instalación de KubeArmor

Instalar Kubearmor en un cluster de Kubernetes es muy sencillo. Para los ejemplos usaremos minikube, pero por supuesto podéis usar EKS, GKE, AKS (tenéis la matriz de compatibilidad aquí: https://docs.kubearmor.io/kubearmor/quick-links/support_matrix).

El primer paso es instalar minikube y configurarlo para que arranque nodos que sean máquinas virtuales (en mi caso con Virtualbox). En caso de que ya tuvieras un cluster creado, deberás recrearlo para que use esa configuración.

sudo apt install minikube
minikube config set driver virtualbox
minikube delete
minikube start

El siguiente paso es instalar el CLI de KubeArmor:

curl -sfL http://get.kubearmor.io/ | sudo sh -s -- -b /usr/local/bin

Y ahora ya podemos instalar KubeArmor mediante el CLI:

kubearmor install


Si no aparecen errores ya tendremos KubeArmor instalado en un nuevo namespace llamado kubearmor.

Como hemos dicho antes, la instalación es un DaemonSet, es decir, los pods se replican en cada nodo del cluster. Podemos verificar tanto la instalación como la configuración de KubeArmor con este comando:

kubearmor probe

Si no queremos instalar el CLI, otra forma de instalar KubeArmor es mediante Helm:

helm repo add kubearmor https://kubearmor.github.io/charts
helm repo update kubearmor
helm upgrade --install kubearmor-operator kubearmor/kubearmor-operator -n kubearmor –create-namespace
kubectl apply -f https://raw.githubusercontent.com/kubearmor/KubeArmor/main/pkg/KubeArmorOperator/config/samples/sample-config.yml

5. KubeArmorPolicy y KubeArmorHostPolicy

En KubeArmor el control de accesos a recursos se define mediante políticas, que pueden ser de estos dos tipos:

  • KubeArmorPolicy
  • KubeArmorHostPolicy

Con KubeArmorPolicy definiremos políticas que apliquen a pods (lo más habitual), mientras que con KubeArmorHostPolicy definiremos políticas que apliquen a los propios nodos del cluster (aquí no veremos ningún ejemplo y además viene desactivado por defecto)

Las políticas de KubeArmor tienen tres partes:

  • Un selector que especifica a qué pods aplica la política (por ejemplo, mediante labels)
  • Otro selector que especifica el evento a detectar (por ejemplo, la ejecución de un comando)
  • La acción a realizar si se cumple lo anterior (autorizar o denegar la operación)
  • Por ejemplo, esta política bloquea la ejecución del comando /usr/bin/wget a pods que tengan una label app=miapp:
apiVersion: security.kubearmor.com/v1
kind: KubeArmorPolicy
metadata:
name: block-curl-wget-exec
spec:
selector:
matchLabels:
app: miapp
process:
matchPaths:
- path: /usr/bin/curl
- path: /usr/bin/wget
action:
Block

En las siguientes secciones mostraremos ejemplos de estas políticas en acción 🙂

6. Security Postures

En el ejemplo anterior usamos una política de KubeArmor para prohibir específicamente la ejecución de un comando determinado, permitiendo cualquier otra ejecución o acceso. En este caso se dice que la security posture de Kubearmor es «audit», ya que se autoriza (y se audita, de ahí el nombre) todo lo que no se deniega explícitamente. Este es el modo por defecto.

En entornos donde se necesita una seguridad elevada normalmente lo que se quiere es justo lo contrario: autorizar explícitamente algunos accesos, y denegar todos los demás. En estos casos debemos configurar KubeArmor con la security posture «block».

Estas security postures se pueden definir a nivel de namespace.

Dando un poco más de detalle, el comportamiento en cada caso es el siguiente:

  • Cuando la postura por defecto es audit y se añade una nueva política («Allow» o «Block»), KubeArmor emite logs de auditoría cada vez que esa política se cumple, y dependiendo del action de la política, la autoriza o la deniega.
  • Cuando la postura por defecto es block y se añaden nuevas políticas «Allow», todas las operaciones se deniegan salvo las que se autorizan mediante esas políticas.

Para ver la security posture que se está usando en cada momento, podemos ejecutar de nuevo el comando karmor probe y fijarnos en la columna DEFAULT POSTURE para el namespace default:

karmor probe

[…]

File(audit), Capabilities(audit), Network (audit)

En este caso, vemos que se está usando audit como postura por defecto para políticas de acceso a ficheros y ejecución de procesos (File), para filtrado por capabilities (Capabilities) y para acceso a recursos de red (Network).

En el ejemplo 2 veremos cómo modificar esta postura por defecto.

Es importante señalar que si la postura por defecto es block pero no hay reglas Allow definidas, no se deniega ningún acceso. Igualmente si la postura por defecto es audit pero no hay reglas definidas no se emite ningún log de auditoría. Lo comento porque puede parecer algo confuso a primera vista 🙂

Ahora que ya hemos explicado todos los conceptos necesarios, vamos a ver algunos ejemplos.

Ejemplo 1: impedir la ejecución de un comando dentro de un pod

El caso más habitual de uso de KubeArmor es restringir la ejecución de comandos para que un posible atacante que haya obtenido acceso a un pod no pueda escalar privilegios o realizar movimientos laterales.

Por ejemplo, resulta muy habitual que un atacante intente descargar algún tipo de malware mediante curl o wget, que suelen estar disponibles en un contenedor. Para impedir esto podemos instalar una nueva KubeArmorPolicy llamada block-curl-wget-exec que tenga esta definición:

cat <<EOF | kubectl apply -f -
apiVersion: security.kubearmor.com/v1
kind: KubeArmorPolicy
metadata:
name: block-curl-wget-exec
spec:
selector:
matchLabels:
app: miapp
process:
matchPaths:
- path: /usr/bin/curl
- path: /usr/bin/wget
action:
Block
EOF

Antes de ponerla a prueba dejaremos lanzado en otra terminal el comando karmor logs para monitorizar los logs de auditoría cada vez que se detecte un acceso cubierto por alguna política:

karmor logs --json

Ahora sí, ponemos a prueba la política creando un nuevo pod y ejecutando el comando «curl» dentro del mismo:

kubectl run ejemplo --image nginx --labels "app=miapp"
kubectl exec -it ejemplo1 -- bash -c "curl"
bash: line 1: /usr/bin/curl: Permission denied
command terminated with exit code 126

¡Ha funcionado! KubeArmor ha impedido la ejecución del comando.
Además podemos ver que karmor logs muestra lo que ha sucedido por pantalla:

{"Timestamp":1734949705,"UpdatedTime":"2024-12-23T10:28:25.581067Z","ClusterName":"default","HostName":"minikube","NamespaceName":"default","Owner":{"Ref":"Pod","Name":"ejemplo1","Namespace":"default"},"PodName":"ejemplo1","Labels":"app=miapp","ContainerID":"71159c0ec27dbaf89377d42637c356d8d29d0d6e649a3bfcda0baafac244dff7","ContainerName":"ejemplo1","ContainerImage":"nginx:latest@sha256:fb197595ebe76b9c0c14ab68159fd3c08bd067ec62300583543f0ebda353b5be","HostPPID":7816,"HostPID":7824,"PPID":7816,"PID":49,"UID":0,"ParentProcessName":"/usr/bin/runc","ProcessName":"/usr/bin/curl","PolicyName":"block-curl-wget-tools-exec","Severity":"1","Type":"MatchedPolicy","Source":"/usr/bin/runc","Operation":"Process","Resource":"/usr/bin/curl","Data":"lsm=SECURITY_BPRM_CHECK","Enforcer":"BPFLSM","Action":"Block","Result":"Permission denied","Cwd":"/"}

Antes de continuar vamos a borrar la política creada anteriormente para no interferir con el siguiente ejemplo:

kubectl delete kubearmorpolicy block-curl-wget-exec

Ejemplo 2: prohibir cualquier acceso a la red salvo vía curl

En este ejemplo vamos prohibir cualquier acceso a la red salvo el que se realice a través del comando curl. Para ello debemos cambiar la security posture.

Para modificar la postura de Network para el namespace «default» ejecutaremos el siguiente comando:

kubectl annotate ns default kubearmor-network-posture=block
karmor probe 
# […]
# File(audit), Capabilities(audit), Network (block)

Como hemos comentado antes, el hecho de cambiar la posture a block no hace que se denieguen todas las peticiones (todavía), ya que todavía no hemos definido ninguna política.
Ahora sí, cargamos una nueva KubeArmorPolicy que permita únicamente el tráfico UDP (para que funcione la resolución de DNS) y el tráfico TCP únicamente desde el comando curl:

cat <<EOF | kubectl apply -f -
apiVersion: security.kubearmor.com/v1
kind: KubeArmorPolicy
metadata:
name: allow-curl
namespace: default
spec:
severity: 8
selector:
matchLabels:
app: miapp
network:
matchProtocols:
- protocol: tcp
fromSource:
- path: /usr/bin/curl
- protocol: udp
action:
Allow
EOF


Para probarla, entramos en el pod que creamos anteriormente e intentamos acceder a la red con diversos comandos:

kubectl exec -it ejemplo -- apt update # no permitido
kubectl exec -it ejemplo -- curl www.google.com # permitido

¡Conseguido!

Para terminar, volvemos a dejar la security posture como estaba para no interferir con el último ejemplo:

kubectl annotate --overwrite ns default kubearmor-network-posture=audit


7. Bonus track: fortificar una instalación de Kubernetes

Para terminar, os presento una funcionalidad de KubeArmor muy interesante que sugiere la aplicación de nuevas políticas de seguridad en función de los pods que tenemos instalados, mediante el comando karmor recommend:

karmor recommend


Este comando va a descargar en una carpeta out/ todas las políticas de seguridad recomendadas por KubeArmor y nos presenta un resumen de las mismas en un fichero report.tx

Si queremos aplicar una, sólo tenemos que hacer un kubectl apply sobre el yaml correspondiente.

Por ejemplo, esta política nos protege de la ejecución de malware relacionado con el minado de criptomonedas, un escenario cada vez más común cuando un hacker ha comprometido un sistema:

kubectl apply -f out/kuberarmor-nginx/kubearmor-nginx/nginx-latest-crypto-miners.yaml

De esta forma, si descargásemos algún software de minado como XMRig y lo intentásemos ejecutar dentro del pod, KubeArmor bloquearía la ejecución.

8. Conclusiones

En este artículo hemos aprendido cómo mejorar la seguridad de nuestras aplicaciones desplegadas en Kubernetes mediante KubeArmor, de forma que estén protegidos frente a accesos malintencionados. A partir de ahora ya no hay excusa para no usarlo 😉

Ramsés Rodríguez
Ramsés Rodríguez
Ingeniero de Telecomunicaciones, actualmente co-CEO & co-Founder de NEXT DIGITAL. Padre de dos hijos, apasionado de la historia y de la cultura clásica: ¡οὐ φροντὶς!

Otros artículos que te pueden interesar

El día en el que comencé a desarrollar todas mis webs...

0
En diciembre de 2022 apareció un nuevo framework llamado Pynecone, en su versión 0.1.8 Alpha. Cero ruido. Ya en julio de 2023, con su cambio de nombre a Reflex, y el lanzamiento de su versión 0.2.0, descubrí por primera vez este framework. Y la comunidad comenzó a hablar de él.
Java 23, claves y datos necesarios de la última versión de java

Java 23: ¿dónde estamos y cómo hemos llegado hasta aquí?

0
Java 23 ya está disponible desde el 17 de septiembre de 2024. Como siempre que se lanza una nueva versión, es útil conocer las novedades que incluye nuestro lenguaje favorito.

Tests de integración con Spring Boot y Testcontainers

0
En el desarrollo de aplicaciones Spring Boot es fundamental asegurar que todo funcione correctamente. Los tests unitarios son esenciales para validar el comportamiento de componentes de manera aislada.