Los 10 riesgos de seguridad más críticos en entornos Kubernetes y cómo mitigarlos.
El OWASP Kubernetes Top 10 identifica los riesgos de seguridad más significativos en entornos Kubernetes. Kubernetes se ha convertido en el estándar de facto para la orquestación de contenedores, pero su flexibilidad y complejidad introducen numerosos desafíos de seguridad. Esta guía cubre configuraciones incorrectas de cargas de trabajo, problemas de RBAC, gestión de secretos, segmentación de red y más.
Las configuraciones inseguras de cargas de trabajo son el problema de seguridad más común en Kubernetes. Los contenedores que se ejecutan como root, con privilegios excesivos, sistemas de archivos escribibles o sin límites de recursos crean superficies de ataque significativas. Las configuraciones predeterminadas suelen ser inseguras y deben endurecerse explícitamente.
Un contenedor comprometido con privilegios de root y acceso al host puede escapar del sandbox del contenedor, acceder al sistema de archivos del host y pivotar a otras cargas de trabajo. La escalada de privilegios desde un pod mal configurado puede llevar a un compromiso total del clúster.
apiVersion: v1 kind: Pod metadata: name: insecure-app spec: containers: - name: app image: myapp:latest # Mutable tag! securityContext: privileged: true # Full host access! runAsUser: 0 # Running as root! volumeMounts: - mountPath: /host name: host-fs volumes: - name: host-fs hostPath: path: / # Entire host filesystem!
apiVersion: v1 kind: Pod metadata: name: secure-app spec: securityContext: runAsNonRoot: true seccompProfile: type: RuntimeDefault containers: - name: app image: myapp@sha256:abc123... # Immutable digest securityContext: allowPrivilegeEscalation: false readOnlyRootFilesystem: true runAsUser: 1000 capabilities: drop: ["ALL"] resources: limits: cpu: "500m" memory: "256Mi" requests: cpu: "100m" memory: "128Mi"
El RBAC (Control de Acceso Basado en Roles) de Kubernetes es poderoso pero complejo. Los roles excesivamente permisivos Eespecialmente los enlaces cluster-admin, permisos comodín y privilegios excesivos de cuentas de servicio Epermiten el acceso no autorizado a recursos del clúster, secretos y cargas de trabajo.
Un atacante que obtiene acceso a una cuenta de servicio con privilegios excesivos puede listar secretos, crear pods privilegiados, modificar despliegues y escalar a cluster-admin. Las reglas RBAC con comodines son el equivalente en Kubernetes de otorgar acceso root.
# ClusterRoleBinding granting cluster-admin to a service account apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: app-admin-binding subjects: - kind: ServiceAccount name: my-app # App SA with cluster-admin! namespace: default roleRef: kind: ClusterRole name: cluster-admin # Full cluster control! apiGroup: rbac.authorization.k8s.io
# Scoped Role with minimum required permissions apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: app-role namespace: my-app-ns rules: - apiGroups: [""] resources: ["configmaps"] verbs: ["get", "list"] # Read-only, specific resources resourceNames: ["app-config"] # Named resource only --- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: app-role-binding namespace: my-app-ns subjects: - kind: ServiceAccount name: my-app namespace: my-app-ns roleRef: kind: Role name: app-role apiGroup: rbac.authorization.k8s.io
Los Secrets de Kubernetes solo están codificados en base64 por defecto, no encriptados. Almacenar datos sensibles (claves API, contraseñas de bases de datos, certificados TLS) en ConfigMaps planos, variables de entorno o Secrets no encriptados los expone a cualquier persona con acceso a la API. Los Secrets también son visibles en etcd si no se habilita el cifrado en reposo.
Los secretos expuestos permiten a los atacantes acceder a bases de datos, cuentas en la nube y servicios externos. Los Secrets almacenados en etcd sin cifrado pueden ser leídos por cualquiera con acceso a etcd. Los secretos en variables de entorno son visibles en las especificaciones de pods y listados de procesos.
# Secret in plain ConfigMap Evisible to anyone apiVersion: v1 kind: ConfigMap metadata: name: app-config data: DB_PASSWORD: "SuperSecret123!" # Plaintext password! API_KEY: "sk-live-abc123xyz" # API key in ConfigMap! --- apiVersion: v1 kind: Pod metadata: name: app spec: containers: - name: app image: myapp:latest envFrom: - configMapRef: name: app-config # All values as env vars!
# Use External Secrets Operator with a vault backend apiVersion: external-secrets.io/v1beta1 kind: ExternalSecret metadata: name: app-secrets spec: refreshInterval: 1h secretStoreRef: name: vault-backend kind: ClusterSecretStore target: name: app-secrets data: - secretKey: db-password remoteRef: key: secret/myapp/db property: password --- apiVersion: v1 kind: Pod metadata: name: app spec: containers: - name: app image: myapp@sha256:abc123... volumeMounts: - name: secrets mountPath: /etc/secrets readOnly: true # Mount as read-only files volumes: - name: secrets secret: secretName: app-secrets
Sin políticas a nivel de clúster, no hay barreras que impidan el despliegue de cargas de trabajo inseguras. Los Pod Security Standards (PSS), controladores de admisión y motores de políticas como OPA Gatekeeper o Kyverno son esenciales para hacer cumplir las líneas base de seguridad en todos los namespaces.
Sin aplicación de políticas, cualquier desarrollador puede desplegar contenedores privilegiados, usar volúmenes hostPath o deshabilitar controles de seguridad. Una sola carga de trabajo mal configurada puede comprometer todo el clúster. La revisión manual no puede escalar para detectar todas las violaciones.
#!/bin/bash # No admission control Eany pod spec is accepted # No Pod Security Standards configured # Anyone can deploy a privileged container kubectl run hacker-pod --image=alpine \ --overrides='{"spec":{"containers":[{ "name":"hack", "image":"alpine", "securityContext":{"privileged":true}, "command":["nsenter","--target","1","--mount","--uts","--ipc","--net","--pid","--","bash"] }]}}' # No policy blocks this Enow has host root access!
# Enforce Pod Security Standards with Kyverno apiVersion: kyverno.io/v1 kind: ClusterPolicy metadata: name: disallow-privileged spec: validationFailureAction: Enforce background: true rules: - name: deny-privileged match: any: - resources: kinds: ["Pod"] validate: message: "Privileged containers are not allowed." pattern: spec: containers: - securityContext: privileged: "false" - name: require-run-as-non-root match: any: - resources: kinds: ["Pod"] validate: message: "Containers must run as non-root." pattern: spec: securityContext: runAsNonRoot: true
Por defecto, todos los pods en un clúster de Kubernetes pueden comunicarse entre sí sin restricciones. Esta arquitectura de red plana significa que un pod comprometido puede alcanzar cualquier otro pod, servicio o incluso el servidor API de Kubernetes. Las NetworkPolicies son esenciales para implementar microsegmentación.
Sin segmentación de red, el movimiento lateral es trivial. Un pod de frontend comprometido puede acceder directamente a bases de datos backend, servicios internos y la API de metadatos (169.254.169.254). Esto viola el principio de privilegio mínimo en la capa de red.
# No NetworkPolicy Eall pods can communicate freely apiVersion: v1 kind: Namespace metadata: name: production # No default deny policy # No network policies at all # Any pod can reach: # - Other pods in any namespace # - Kubernetes API server # - Cloud metadata API (169.254.169.254) # - External internet
# Default deny all ingress and egress apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: default-deny-all namespace: production spec: podSelector: {} policyTypes: ["Ingress", "Egress"] --- # Allow only frontend to backend communication apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: allow-frontend-to-backend namespace: production spec: podSelector: matchLabels: app: backend policyTypes: ["Ingress"] ingress: - from: - podSelector: matchLabels: app: frontend ports: - port: 8080 protocol: TCP
Los componentes del plano de control de Kubernetes (servidor API, etcd, kubelet, dashboard) y los servicios de aplicación pueden quedar expuestos inadvertidamente a Internet o redes no confiables. Los dashboards expuestos, kubelets sin autenticación y servidores API públicamente accesibles son vectores de ataque comunes.
Los componentes de Kubernetes expuestos proporcionan acceso directo a la gestión del clúster. Una API de kubelet sin autenticación permite la ejecución de contenedores. Un dashboard expuesto con credenciales predeterminadas otorga acceso de cluster-admin. El acceso público a etcd expone todos los datos del clúster, incluidos los secretos.
# Kubernetes Dashboard exposed via LoadBalancer apiVersion: v1 kind: Service metadata: name: kubernetes-dashboard namespace: kubernetes-dashboard spec: type: LoadBalancer # Public internet access! ports: - port: 443 targetPort: 8443 selector: k8s-app: kubernetes-dashboard # Dashboard bound to cluster-admin service account # No authentication required Efull cluster access!
# Dashboard accessible only via kubectl proxy apiVersion: v1 kind: Service metadata: name: kubernetes-dashboard namespace: kubernetes-dashboard spec: type: ClusterIP # Internal only ports: - port: 443 targetPort: 8443 selector: k8s-app: kubernetes-dashboard --- # Access via: kubectl proxy, then browse to: # http://localhost:8001/api/v1/namespaces/kubernetes-dashboard/services/https:kubernetes-dashboard:/proxy/ # Or use kubectl port-forward for secure access
Los clústeres de Kubernetes constan de muchos componentes: servidor API, etcd, kubelet, kube-proxy, CoreDNS y complementos de terceros (controladores Ingress, service meshes, monitoreo). Los componentes mal configurados o sin parches introducen vulnerabilidades. Las configuraciones predeterminadas a menudo no están endurecidas.
Los componentes vulnerables del clúster pueden ser explotados para ejecución remota de código, escalada de privilegios o denegación de servicio. Los CVEs en kubelet, controladores Ingress o plugins CNI pueden llevar a escapes de contenedores y toma del clúster. Los componentes desactualizados acumulan vulnerabilidades conocidas.
# kubelet with insecure configuration # /var/lib/kubelet/config.yaml apiVersion: kubelet.config.k8s.io/v1beta1 kind: KubeletConfiguration authentication: anonymous: enabled: true # Anyone can access kubelet API! webhook: enabled: false # No authorization! authorization: mode: AlwaysAllow # All requests permitted! readOnlyPort: 10255 # Unauthenticated read port open!
# Hardened kubelet configuration apiVersion: kubelet.config.k8s.io/v1beta1 kind: KubeletConfiguration authentication: anonymous: enabled: false # Deny anonymous access webhook: enabled: true # Use API server for auth x509: clientCAFile: /etc/kubernetes/pki/ca.crt authorization: mode: Webhook # API server authorizes requests readOnlyPort: 0 # Disable read-only port protectKernelDefaults: true eventRecordQPS: 5
Los clústeres de Kubernetes que se ejecutan en entornos de nube (AWS, GCP, Azure) pueden acceder a las APIs de metadatos de la nube y roles IAM adjuntos a los nodos. Los atacantes que comprometen un pod pueden usarlos para escalar privilegios desde el clúster a la cuenta de la nube, accediendo a buckets S3, bases de datos y otros servicios en la nube.
Los roles IAM a nivel de nodo son heredados por todos los pods en ese nodo. Un pod comprometido puede consultar la API de metadatos (169.254.169.254) para obtener credenciales de la nube y luego acceder a cualquier recurso en la nube que el rol del nodo permita. Esto habilita el movimiento lateral desde Kubernetes al entorno de nube más amplio.
# Node IAM role with excessive permissions resource "aws_iam_role_policy_attachment" "node_admin" { role = aws_iam_role.node_role.name policy_arn = "arn:aws:iam::aws:policy/AdministratorAccess" # All pods on this node inherit admin access! } # No IMDS restrictions Eany pod can get node credentials # curl http://169.254.169.254/latest/meta-data/iam/security-credentials/
# Use IAM Roles for Service Accounts (IRSA) resource "aws_iam_role" "app_role" { name = "my-app-role" assume_role_policy = jsonencode({ Version = "2012-10-17" Statement = [{ Effect = "Allow" Principal = { Federated = aws_iam_openid_connect_provider.eks.arn } Action = "sts:AssumeRoleWithWebIdentity" Condition = { StringEquals = { "${aws_iam_openid_connect_provider.eks.url}:sub" = "system:serviceaccount:my-ns:my-app" } } }] }) } # Pod-level scoped IAM via service account annotation # Only this specific pod gets these specific permissions
Kubernetes soporta múltiples mecanismos de autenticación: certificados, tokens, OIDC y autenticación webhook. La autenticación rota incluye el uso de tokens estáticos, compartir archivos kubeconfig, no rotar certificados y no integrar con proveedores de identidad. El montaje automático de tokens de cuenta de servicio crea una superficie de ataque innecesaria.
Los archivos kubeconfig y tokens de cuenta de servicio robados o filtrados proporcionan acceso directo al clúster. Los tokens estáticos que nunca expiran otorgan acceso persistente. Sin gestión centralizada de identidad, revocar el acceso a miembros del equipo que se fueron es difícil y propenso a errores.
# Static token file for API server authentication # /etc/kubernetes/token-auth-file.csv # token,user,uid,"groups" # abc123,admin,1,"system:masters" # Static token, never expires! # Pod with auto-mounted service account token apiVersion: v1 kind: Pod metadata: name: app spec: # automountServiceAccountToken defaults to true! # Token mounted at /var/run/secrets/kubernetes.io/serviceaccount/token containers: - name: app image: myapp:latest # App doesn't need K8s API access but has a token anyway
# Disable auto-mounted token for pods that don't need API access apiVersion: v1 kind: ServiceAccount metadata: name: my-app namespace: my-app-ns automountServiceAccountToken: false # No auto-mount --- # For pods that need API access, use bound tokens with expiry apiVersion: v1 kind: Pod metadata: name: api-consumer spec: serviceAccountName: api-consumer-sa automountServiceAccountToken: false containers: - name: app image: myapp@sha256:abc123... volumeMounts: - name: token mountPath: /var/run/secrets/tokens readOnly: true volumes: - name: token projected: sources: - serviceAccountToken: expirationSeconds: 3600 # 1-hour expiry audience: api-server
Kubernetes genera eventos de auditoría para solicitudes al servidor API, pero el registro de auditoría a menudo está deshabilitado o mal configurado. Sin un registro y monitoreo adecuados, las actividades maliciosas Ecomo el acceso no autorizado a secretos, cambios en RBAC y escapes de contenedores Epasan desapercibidas. El monitoreo de seguridad en tiempo de ejecución a nivel de contenedor y nodo es esencial.
Sin registro de auditoría, los atacantes operan sin ser detectados. Pueden crear cuentas de servicio de puerta trasera, extraer secretos y modificar cargas de trabajo sin ningún registro. La respuesta a incidentes se ve gravemente afectada cuando no hay un rastro de auditoría para determinar qué sucedió, cuándo y por quién.
# API server with no audit logging configured # kube-apiserver flags: # --audit-log-path="" # No audit log! # --audit-policy-file="" # No audit policy! # No runtime security monitoring # No alerting on suspicious activities # Container logs not collected centrally # Default log retention Elogs lost on pod restart
# Comprehensive audit policy apiVersion: audit.k8s.io/v1 kind: Policy rules: # Log all secret access at Metadata level - level: Metadata resources: - group: "" resources: ["secrets"] # Log RBAC changes at RequestResponse level - level: RequestResponse resources: - group: rbac.authorization.k8s.io resources: ["clusterroles", "clusterrolebindings", "roles", "rolebindings"] # Log pod exec/attach (potential attacks) - level: Request resources: - group: "" resources: ["pods/exec", "pods/attach", "pods/portforward"] # Default: log at Metadata level - level: Metadata
| ID | Vulnerabilidad | Severidad | Mitigación Clave |
|---|---|---|---|
| K01 | Configuraciones Inseguras de Cargas de Trabajo | Critical | No-root, FS de solo lectura, eliminar capacidades, límites de recursos |
| K02 | Autorización Excesivamente Permisiva | Critical | RBAC con alcance de namespace, sin comodines, auditar enlaces |
| K03 | Fallas en la Gestión de Secretos | Critical | Secretos externos, cifrado en reposo, montajes de archivos |
| K04 | Falta de Aplicación de Políticas | High | Pod Security Standards, Kyverno/OPA Gatekeeper |
| K05 | Falta de Segmentación de Red | High | NetworkPolicies de denegación por defecto, microsegmentación |
| K06 | Componentes Excesivamente Expuestos | High | Servicios ClusterIP, kubectl proxy, reglas de firewall |
| K07 | Componentes del Clúster Mal Configurados | High | CIS Benchmark, deshabilitar auth anónima, gestión de parches |
| K08 | Movimiento Lateral del Clúster a la Nube | Critical | IRSA/Workload Identity, bloquear API de metadatos, IMDSv2 |
| K09 | Mecanismos de Autenticación Rotos | Critical | Integración OIDC, tokens vinculados, deshabilitar auto-montaje |
| K10 | Registro y Monitoreo Inadecuados | Medium | Registro de auditoría, Falco/Tetragon, integración SIEM |