DEVOPS 17 min

Seguridad en Kubernetes: PSA, RBAC y Redes Zero Trust

Da el salto a la orquestación empresarial. Entiende la arquitectura de Kubernetes: Nodos, Pods, Deployments y Services. Las bases para desplegar aplicaciones tolerantes a fallos a gran escala.

Escrito por Samir El Azem Crovetto • Senior Backend Engineer & DevSecOps

De Docker Compose a Kubernetes: La Realidad Operativa en Producción

El uso de Docker y Docker Compose es idóneo para entornos de desarrollo local y prototipos monomáquina, como vimos al analizar el despliegue de MySQL con Compose. Sin embargo, cuando las organizaciones escalan hacia arquitecturas distribuidas de alta disponibilidad, Docker Compose revela limitaciones estructurales: carece de auto-reparación nativa frente a fallos de hardware del nodo, no ofrece balanceo de carga interno dinámico y no gestiona actualizaciones graduales sin tiempo de inactividad (zero-downtime rolling updates).

Kubernetes (K8s) resuelve estos desafíos orquestando flotas heterogéneas de servidores como un plano de cómputo unificado. No obstante, la complejidad inherente a Kubernetes expande radicalmente la superficie de ataque si se despliega con las configuraciones por defecto:

  • *Redes Planas por Defecto (Default-Allow Network):* En Kubernetes, cualquier Pod de cualquier namespace puede comunicarse libremente por TCP/UDP con cualquier otro Pod del clúster, facilitando el movimiento lateral inmediato tras el compromiso de un único servicio.
  • *Contenedores con Privilegios Excesivos (Privileged Pods):* Despliegues que ejecutan procesos como usuario root (UID 0), con el sistema de archivos en modo lectura/escritura o con capacidades de Linux no restringidas (CAPSYSADMIN), permitiendo el escape del contenedor hacia el kernel del nodo host, según el modelo de aislamiento analizado en qué es Docker.
  • Tokens de ServiceAccount Expuestos: Por defecto, Kubernetes monta un token JWT con la identidad del ServiceAccount en /var/run/secrets/kubernetes.io/serviceaccount/token dentro de cada contenedor. Si un atacante compromete un Pod y el ServiceAccount tiene permisos amplios en la API, puede escalar privilegios en todo el clúster.
  • Acceso No Restringido al API Server: Servidores API expuestos públicamente sin autenticación OIDC estricta o con configuraciones RBAC laxas donde usuarios y bots reciben el rol cluster-admin.

Asegurar Kubernetes requiere una estrategia de defensa en profundidad estructurada en capas concéntricas: desde el control de admisión de la API hasta la monitorización eBPF en tiempo de ejecución.


Arquitectura de Defensa en Profundidad en Kubernetes

La seguridad en Kubernetes no depende de una única barrera, sino de controles interconectados que aíslan la identidad, el tráfico de red y la ejecución del kernel:

graph TD
    subgraph Control_Plane ["🛡️ Plano de Control (API Server)"]
        User["Desarrollador / CI/CD"]
        OIDC["Autenticación OIDC / IAM"]
        RBAC["Autorización RBAC (Mínimo Privilegio)"]
        PSA["Pod Security Admission (Enforce: Restricted)"]
    end

    subgraph Data_Plane ["⚙️ Plano de Datos (Worker Node)"]
        subgraph CNI_Layer ["Filtrado de Red CNI (NetworkPolicy)"]
            NetPol["Default-Deny Ingress & Egress"]
        end

        subgraph Pod_Runtime ["Aislamiento de Pod (Hardened Runtime)"]
            SecComp["Perfil Seccomp: RuntimeDefault"]
            NonRoot["runAsNonRoot: true (UID >= 10000)"]
            ReadOnly["readOnlyRootFilesystem: true"]
            DropCaps["capabilities.drop: ['ALL']"]
        end

        subgraph Kernel_Audit ["Auditoría en Tiempo de Ejecución (eBPF)"]
            Falco["Falco eBPF: Detección de Shells & Anomalías"]
        end
    end

    User -->|TLS 1.3 + Bearer Token| OIDC
    OIDC --> RBAC
    RBAC --> PSA
    PSA -->|Manifest Validado| CNI_Layer
    CNI_Layer -->|Tráfico Whitelist| Pod_Runtime
    Pod_Runtime --> Kernel_Audit
    Kernel_Audit -.->|Alerta Security Incident| SIEM["SIEM / SOC Alert"]

    classDef control fill:#e1f5fe,stroke:#0288d1,stroke-width:2px;
    classDef data fill:#e8f5e9,stroke:#388e3c,stroke-width:2px;
    classDef runtime fill:#fff3e0,stroke:#f57c00,stroke-width:2px;
    classDef audit fill:#ffebee,stroke:#d32f2f,stroke-width:2px;

    class User,OIDC,RBAC,PSA control;
    class CNI_Layer data;
    class SecComp,NonRoot,ReadOnly,DropCaps runtime;
    class Falco,SIEM audit;

1. Pod Security Standards (PSS) y Pod Security Admission (PSA)

Tras la depreciación de las complejas PodSecurityPolicies (PSP) en Kubernetes 1.25, el estándar oficial integrado es Pod Security Admission (PSA), que clasifica las cargas de trabajo en tres niveles de seguridad:

  1. Privileged: Sin restricciones. Destinado exclusivamente a agentes de infraestructura a nivel de sistema (CNI plugins, agentes de almacenamiento CSI).
  2. Baseline: Previene escaladas de privilegios evidentes con una configuración mínima por defecto.
  3. Restricted: Aplica las mejores prácticas de hardening más estrictas del estándar CIS Kubernetes Benchmark.

Aplicación de PSS Restricted a Nivel de Namespace

Para asegurar que ningún contenedor vulnerable pueda ser desplegado en el namespace de producción, se etiquetan los namespaces con las directivas de cumplimiento:

apiVersion: v1
kind: Namespace
metadata:
  name: production-apps
  labels:
    # Bloquear el despliegue de cualquier Pod que viole el estándar Restricted
    pod-security.kubernetes.io/enforce: restricted
    pod-security.kubernetes.io/enforce-version: latest
    # Advertir a los ingenieros si alguna plantilla incumple
    pod-security.kubernetes.io/warn: restricted
    pod-security.kubernetes.io/warn-version: latest
    # Registrar auditorías detalladas
    pod-security.kubernetes.io/audit: restricted
    pod-security.kubernetes.io/audit-version: latest

Manifiesto de Pod con Hardening Completo

Bajo el perfil restricted, cada Pod debe definir explícitamente su bloque securityContext:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: secure-backend-service
  namespace: production-apps
spec:
  replicas: 3
  selector:
    matchLabels:
      app: backend-service
  template:
    metadata:
      labels:
        app: backend-service
    spec:
      # Desactivar el montaje del token de ServiceAccount si la app no llama a la API de K8s
      automountServiceAccountToken: false
      
      securityContext:
        runAsNonRoot: true
        runAsUser: 10001
        runAsGroup: 10001
        fsGroup: 10001
        seccompProfile:
          type: RuntimeDefault

      containers:
        - name: application
          image: ghcr.io/empresa/backend-service:v1.4.2
          imagePullPolicy: IfNotPresent
          
          securityContext:
            allowPrivilegeEscalation: false
            readOnlyRootFilesystem: true
            capabilities:
              drop:
                - ALL

          resources:
            limits:
              cpu: "1"
              memory: "1Gi"
            requests:
              cpu: "250m"
              memory: "512Mi"

          volumeMounts:
            # Directorio temporal efímero en memoria para escrituras necesarias
            - name: tmp-volume
              mountPath: /tmp

      volumes:
        - name: tmp-volume
          emptyDir:
            medium: Memory
            sizeLimit: 64Mi

Parámetros clave de seguridad:

  • allowPrivilegeEscalation: false: Impide que binarios con el bit setuid eleven privilegios dentro del contenedor.
  • readOnlyRootFilesystem: true: Convierte el sistema de archivos en solo lectura. Si un atacante explota una vulnerabilidad en la aplicación web, no puede descargar herramientas de red (curl, nmap), compilar exploits o modificar binarios del sistema.
  • capabilities.drop: ["ALL"]: Despoja al proceso de cualquier capacidad administrativa de Linux (como CAPNETRAW para escanear redes o CAPSYSPTRACE para inspeccionar memoria de otros procesos).
  • seccompProfile: RuntimeDefault: Activa el filtrado de llamadas al sistema (syscalls) nativo de Linux para bloquear syscalls peligrosas que no sean requeridas por runtimes modernos.

2. Microsegmentación de Red con NetworkPolicies

Por diseño, el modelo de red de Kubernetes es completamente plano: cualquier Pod puede conectarse a cualquier otro Pod. Para implementar una arquitectura Zero Trust, el primer paso en cada namespace debe ser establecer una política de Default-Deny total tanto para tráfico entrante (Ingress) como saliente (Egress).

Política 1: Default-Deny Global en el Namespace

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: production-apps
spec:
  podSelector: {} # Aplica a todos los pods del namespace
  policyTypes:
    - Ingress
    - Egress

Una vez aplicada esta política, ningún Pod del namespace puede comunicarse con el exterior ni recibir peticiones, neutralizando intentos de escaneo lateral.

Política 2: Whitelist Granular para el Microservicio Backend

A continuación, se autoriza explícitamente únicamente el tráfico estrictamente necesario:

  • Ingress: Tráfico TCP en el puerto 8080 procedente exclusivamente de pods con la etiqueta role: ingress-controller ubicados en el namespace ingress-nginx.
  • Egress: Tráfico hacia el servidor DNS interno de Kubernetes (kube-dns en puerto 53 UDP/TCP) y hacia la base de datos PostgreSQL privada en el puerto 5432:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: backend-service-policy
  namespace: production-apps
spec:
  podSelector:
    matchLabels:
      app: backend-service
  policyTypes:
    - Ingress
    - Egress
  ingress:
    - from:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: ingress-nginx
          podSelector:
            matchLabels:
              app.kubernetes.io/name: ingress-nginx
      ports:
        - protocol: TCP
          port: 8080
  egress:
    # Permitir resolución DNS
    - to:
        - namespaceSelector: {}
          podSelector:
            matchLabels:
              k8s-app: kube-dns
      ports:
        - protocol: UDP
          port: 53
        - protocol: TCP
          port: 53
    # Permitir conexión a la base de datos en namespace específico
    - to:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: database-tier
          podSelector:
            matchLabels:
              app: postgres-cluster
      ports:
        - protocol: TCP
          port: 5432

Nota Crítica de Implementación: Las NetworkPolicies solo son efectivas si el clúster utiliza un CNI (Container Network Interface) con soporte de políticas de red, como Cilium, Calico o AWS VPC CNI con Network Policy Engine activado. El plugin de red básico de Kubernetes no implementa filtrado.


3. RBAC y Workload Identity: Eliminación de Credenciales Estáticas

El control de acceso basado en roles (Role-Based Access Control - RBAC) debe estructurarse mediante roles locales en namespaces (Role) en lugar de roles a nivel de clúster (ClusterRole), restringiendo estrictamente los verbos y recursos autorizados:

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: production-apps
  name: deployment-operator
rules:
  - apiGroups: ["apps"]
    resources: ["deployments"]
    verbs: ["get", "list", "watch", "update", "patch"]
  - apiGroups: [""]
    resources: ["pods"]
    verbs: ["get", "list", "watch"]

Integración de Workload Identity (AWS EKS Pod Identity / IRSA)

En entornos cloud, los microservicios desplegados en Kubernetes a menudo necesitan interactuar con servicios externos como Amazon S3, DynamoDB o Secrets Manager.

El antipatrón consiste en crear claves de acceso estáticas de AWS (AWSACCESSKEYID y AWSSECRETACCESSKEY) y almacenarlas como Secrets de Kubernetes. Si el clúster es comprometido, las claves se filtran y persisten de forma indefinida.

La solución de seguridad es Workload Identity (EKS Pod Identity o IAM Roles for Service Accounts - IRSA):

  1. Se asocia un ServiceAccount de Kubernetes con un rol de IAM en AWS mediante un token OIDC emitido por el propio clúster.
  2. El SDK de AWS dentro del contenedor asume el rol de IAM automáticamente mediante sts:AssumeRoleWithWebIdentity.
  3. Las credenciales generadas son temporales, se rotan automáticamente cada hora y carecen de persistencia física en disco.

4. Detección de Amenazas en Tiempo de Ejecución con Falco eBPF

Incluso con controles estáticos de admisión y microsegmentación de red, es imprescindible monitorizar la actividad en tiempo de ejecución. Falco es el estándar de facto de la CNCF para detección de anomalías y amenazas de seguridad en Kubernetes, utilizando sondas eBPF (Extended Berkeley Packet Filter) directamente en el kernel de Linux.

Falco intercepta llamadas al sistema en tiempo real y evalúa reglas de comportamiento sospechoso:

  • Ejecución de un shell interactivo dentro de un contenedor en producción (exec de /bin/bash o /bin/sh).
  • Modificación de archivos en directorios binarios críticos (/bin, /sbin, /usr/bin).
  • Apertura de sockets de red hacia rangos de IPs externas inesperadas.
  • Acceso anómalo al token del ServiceAccount.

Ejemplo de regla de Falco para alertar de la ejecución de una terminal interactiva en un Pod:

- rule: Terminal shell in container
  desc: A shell was spawned inside a container with an attached terminal
  condition: >
    spawned_process and container
    and shell_procs and proc.tty != 0
    and container.name != "debug-sidecar"
  output: >
    Alerta Crítica: Terminal abierta en contenedor
    (user=%user.name pod=%k8s.pod.name ns=%k8s.ns.name
     cmd=%proc.cmdline image=%container.image.repository)
  priority: WARNING
  tags: [container, shell, mitre_execution]

La señal generada por Falco se exporta en formato JSON hacia el SIEM corporativo o canales de respuesta ante incidentes, permitiendo al equipo de operaciones revocar Pods comprometidos en cuestión de segundos.


Conclusión y Checklist de Hardening para Clústeres Kubernetes

La seguridad en Kubernetes no es un estado binario, sino un proceso continuo de reducción de privilegios y observabilidad activa:

  • [x] Pod Security Admission configurado en modo enforce: restricted en todos los namespaces de producción.
  • [x] Contenedores configurados con readOnlyRootFilesystem: true, runAsNonRoot: true y capabilities.drop: ["ALL"].
  • [x] Montaje automático de tokens desactivado (automountServiceAccountToken: false) en Pods que no gestionan la API.
  • [x] Microsegmentación de red implementada con NetworkPolicies aplicando default-deny-all por defecto.
  • [x] Eliminación de credenciales estáticas mediante Workload Identity / EKS Pod Identity federado con IAM.
  • [x] CNI con soporte de filtrado stateful (Cilium o Calico) verificado en el clúster.
  • [x] Detección en tiempo de ejecución desplegada con Falco eBPF para alertar de intrusiones y shells no autorizadas.

Si necesitas auditar la arquitectura cloud de tu organización, implementar bastiones Zero Trust o securizar clústeres de contenedores y cargas de trabajo en Kubernetes, puedes consultar mis Servicios de Ciberseguridad y Consultoría o ponerte en contacto conmigo a través de Sobre mí.