Las aplicaciones cloud-native — construidas sobre contenedores, microservicios y orquestadores como Kubernetes — introducen un modelo de seguridad radicalmente diferente al de las aplicaciones monoliticas tradicionales. La superficie de ataque se multiplica: cada contenedor, cada comunicación entre servicios, cada secreto almacenado y cada imagen base representan un vector potencial. Asegurar este tipo de arquitectura requiere un enfoque que abarque desde el build de la imagen hasta el runtime en producción, pasando por la red, la identidad y la gestión de secretos.

Seguridad de Contenedores

La seguridad de contenedores comienza con la imagen y se extiende a todo su ciclo de vida.

Construir Imagenes Seguras

Una imagen de contenedor insegura es una bomba de tiempo. Las prácticas esenciales para construir imagenes seguras incluyen:

Usar imagenes base minimales: Preferir imagenes distroless o Alpine en lugar de imagenes completas como Ubuntu. Menos paquetes instalados significa menos vulnerabilidades potenciales.

# Mal: imagen base completa con cientos de paquetes
FROM node:20

# Mejor: imagen slim con lo minimo necesario
FROM node:20-slim

# Optimo: multi-stage con distroless para produccion
FROM node:20-slim AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build

FROM gcr.io/distroless/nodejs20-debian12
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
CMD ["dist/main.js"]

No ejecutar como root: El contenedor debe correr con un usuario sin privilegios. Esto limita el impacto de un escape de contenedor.

FROM node:20-slim
RUN groupadd -r appuser && useradd -r -g appuser appuser
WORKDIR /app
COPY --chown=appuser:appuser . .
USER appuser
CMD ["node", "server.js"]

No incluir secretos en la imagen: Nunca copiar archivos .env, credenciales o tokens en las capas de la imagen. Incluso si los eliminas en una capa posterior, las capas anteriores los retienen.

Escaneo de Imagenes

El escaneo de imagenes detecta vulnerabilidades conocidas (CVEs) en los paquetes del sistema operativo y las dependencias de la aplicación.

# GitHub Actions: escanear imagen con Trivy
- name: Build image
  run: docker build -t myapp:${{ github.sha }} .

- name: Scan image for vulnerabilities
  uses: aquasecurity/trivy-action@master
  with:
    image-ref: 'myapp:${{ github.sha }}'
    format: 'table'
    exit-code: '1'
    severity: 'CRITICAL,HIGH'
    ignore-unfixed: true

Herramientas principales para escaneo de imagenes:

  • Trivy: Open-source, rápido, soporta OS packages y dependencias de aplicación
  • Grype: De Anchore, enfocado en vulnerabilidades de dependencias
  • Snyk Container: Ofrece recomendaciones de imagenes base alternativas con menos CVEs
  • Amazon ECR Image Scanning: Integrado nativamente si usas ECR como registro

Políticas de Admision en Kubernetes

Kyverno y OPA Gatekeeper permiten definir políticas que bloquean el despliegue de contenedores que no cumplen los requisitos de seguridad.

# Kyverno: bloquear contenedores que corren como root
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: disallow-root-user
spec:
  validationFailureAction: Enforce
  rules:
    - name: check-runAsNonRoot
      match:
        any:
          - resources:
              kinds:
                - Pod
      validate:
        message: "Containers must not run as root"
        pattern:
          spec:
            containers:
              - securityContext:
                  runAsNonRoot: true

Protección en Runtime

La seguridad no termina cuando la imagen pasa el escaneo. En runtime, es necesario detectar y prevenir comportamientos anomalos.

Falco para Detección de Amenazas

Falco, proyecto de la CNCF, monitorea las syscalls del kernel para detectar actividad sospechosa en contenedores en tiempo real.

# Regla Falco: detectar shell ejecutado en contenedor
- rule: Terminal shell in container
  desc: A shell was used as the entrypoint/exec in a container
  condition: >
    spawned_process and container and
    shell_procs and proc.tty != 0 and
    container_entrypoint
  output: >
    Shell opened in container
    (user=%user.name container=%container.name
    shell=%proc.name parent=%proc.pname
    cmdline=%proc.cmdline image=%container.image.repository)
  priority: WARNING
  tags: [container, shell, mitre_execution]

Security Contexts en Kubernetes

Los Security Contexts de Kubernetes controlan los privilegios a nivel de pod y contenedor:

apiVersion: v1
kind: Pod
metadata:
  name: secure-app
spec:
  securityContext:
    runAsNonRoot: true
    runAsUser: 1000
    fsGroup: 2000
    seccompProfile:
      type: RuntimeDefault
  containers:
    - name: app
      image: myapp:latest
      securityContext:
        allowPrivilegeEscalation: false
        readOnlyRootFilesystem: true
        capabilities:
          drop:
            - ALL
      resources:
        limits:
          cpu: "500m"
          memory: "256Mi"
        requests:
          cpu: "100m"
          memory: "128Mi"

Puntos clave del ejemplo anterior:

  • runAsNonRoot: Fuerza ejecución como usuario no root
  • readOnlyRootFilesystem: El filesystem del contenedor es de solo lectura
  • capabilities: drop ALL: Elimina todas las Linux capabilities
  • allowPrivilegeEscalation: false: Previene escalación de privilegios
  • seccompProfile: RuntimeDefault: Aplica perfil seccomp por defecto que bloquea syscalls peligrosas

Seguridad de Service Mesh y mTLS

En arquitecturas de microservicios, la comunicación entre servicios es un punto crítico de seguridad. Un service mesh como Istio o Linkerd proporciona mTLS (mutual TLS) automático, cifrando y autenticando toda la comunicación interna.

mTLS con Istio

Con Istio, mTLS se habilita a nivel de namespace o de forma global:

apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
  name: default
  namespace: production
spec:
  mtls:
    mode: STRICT

En modo STRICT, todo el tráfico entre servicios dentro del namespace debe estar cifrado con mTLS. Cualquier comunicación sin TLS es rechazada.

Authorization Policies

Istio permite definir políticas de autorización granulares que controlan que servicio puede comunicarse con cual:

apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: backend-api-policy
  namespace: production
spec:
  selector:
    matchLabels:
      app: backend-api
  action: ALLOW
  rules:
    - from:
        - source:
            principals:
              - "cluster.local/ns/production/sa/frontend"
              - "cluster.local/ns/production/sa/api-gateway"
      to:
        - operation:
            methods: ["GET", "POST"]
            paths: ["/api/v1/*"]

Esta política permite que solo el frontend y el api-gateway accedan al backend-api, y unicamente a las rutas y métodos especificados.

Gestión de Secretos

Los secretos (credenciales de base de datos, API keys, certificados TLS) deben gestionarse con herramientas dedicadas, nunca hardcodeados en código o configuraciones.

External Secrets Operator

External Secrets Operator sincroniza secretos desde proveedores externos (AWS Secrets Manager, HashiCorp Vault, Azure Key Vault) hacia Kubernetes Secrets:

apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: database-credentials
  namespace: production
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: aws-secrets-manager
    kind: ClusterSecretStore
  target:
    name: db-credentials
    creationPolicy: Owner
  data:
    - secretKey: username
      remoteRef:
        key: production/database
        property: username
    - secretKey: password
      remoteRef:
        key: production/database
        property: password

Vault con Sidecar Injector

HashiCorp Vault puede inyectar secretos directamente en los pods mediante un sidecar:

apiVersion: v1
kind: Pod
metadata:
  name: app
  annotations:
    vault.hashicorp.com/agent-inject: "true"
    vault.hashicorp.com/role: "my-app-role"
    vault.hashicorp.com/agent-inject-secret-db-creds: "secret/data/production/db"
    vault.hashicorp.com/agent-inject-template-db-creds: |
      {{- with secret "secret/data/production/db" -}}
      DB_HOST={{ .Data.data.host }}
      DB_USER={{ .Data.data.username }}
      DB_PASS={{ .Data.data.password }}
      {{- end -}}
spec:
  containers:
    - name: app
      image: myapp:latest

RBAC en Kubernetes

Role-Based Access Control (RBAC) en Kubernetes controla quien puede realizar que acciones dentro del cluster.

# Role: permisos dentro de un namespace
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: developer
  namespace: development
rules:
  - apiGroups: [""]
    resources: ["pods", "services", "configmaps"]
    verbs: ["get", "list", "watch", "create", "update"]
  - apiGroups: ["apps"]
    resources: ["deployments"]
    verbs: ["get", "list", "watch", "create", "update", "patch"]
  - apiGroups: [""]
    resources: ["secrets"]
    verbs: ["get", "list"]  # Solo lectura de secrets

---
# RoleBinding: asignar el Role a un grupo
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: developer-binding
  namespace: development
subjects:
  - kind: Group
    name: "developers"
    apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: Role
  name: developer
  apiGroup: rbac.authorization.k8s.io

Principios Zero Trust en Cloud-Native

Zero Trust parte de la premisa de que ninguna entidad — interna o externa — es confiable por defecto. En el contexto cloud-native, esto se traduce en:

  • Verificar siempre: Cada request entre servicios se autentica y autoriza, incluso dentro del cluster
  • Privilegio mínimo: Cada servicio tiene acceso solo a los recursos que necesita
  • Asumir la brecha: Disenar la arquitectura asumiendo que cualquier componente puede estar comprometido
  • Cifrar todo: mTLS para comunicación entre servicios, cifrado en reposo para datos persistidos
  • Microsegmentación: Network Policies que restringen el tráfico a nivel de pod
# NetworkPolicy: el pod solo puede comunicarse con la base de datos
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: app-network-policy
  namespace: production
spec:
  podSelector:
    matchLabels:
      app: backend
  policyTypes:
    - Egress
  egress:
    - to:
        - podSelector:
            matchLabels:
              app: postgresql
      ports:
        - port: 5432
          protocol: TCP
    - to:  # Permitir DNS
        - namespaceSelector: {}
      ports:
        - port: 53
          protocol: UDP

OWASP Top 10 para Cloud-Native

OWASP ha publicado un Top 10 específico para aplicaciones cloud-native. Los riesgos mas relevantes incluyen:

  1. Configuración insegura de la nube: Buckets S3 publicos, security groups abiertos, servicios sin cifrado
  2. Inyección de código y dependencias maliciosas: Supply chain attacks a traves de imagenes base o paquetes npm/pip comprometidos
  3. Autenticación y autorización deficiente: Tokens sin expiración, RBAC demasiado permisivo, ausencia de MFA
  4. Falta de cifrado: Comunicaciones entre microservicios sin TLS, datos en reposo sin cifrar
  5. Gestión inadecuada de secretos: Credenciales en variables de entorno, secrets de Kubernetes sin cifrado at-rest

Para cada riesgo, la mitigación pasa por los controles descritos en las secciones anteriores: escaneo de imagenes, mTLS, RBAC estricto, External Secrets y políticas de admision.

Conclusion

La seguridad en aplicaciones cloud-native no es un producto que se instala ni una fase que se ejecuta una vez. Es una disciplina continua que abarca el ciclo completo: desde la construcción de imagenes seguras con escaneo automatizado, pasando por políticas de admision que previenen despliegues inseguros, hasta la detección de amenazas en runtime con herramientas como Falco. Los pilares de mTLS, RBAC, gestión externa de secretos y Network Policies conforman las capas de defensa que hacen viable operar microservicios en producción con confianza. La clave es automatizar estos controles para que sean parte del flujo de desarrollo, no una barrera que los equipos intentan evitar.

Recursos