Problema

Muchas organizaciones tienen un catálogo creciente de aplicaciones web internas que se ejecutan en contenedores Docker o en clústers de Kubernetes. Los usuarios necesitan acceder a esas apps desde sus smartphones, pero el parque de dispositivos es mixto: algunos están gestionados mediante Intune, otros son BYOD y no pueden recibir agentes, certificados ni configuraciones especiales.

El reto consiste en ofrecer acceso clientless (a través del navegador o una app mínima) manteniendo:

  • Autenticación única con Entra ID (OIDC).
  • Tráfico dentro de la infraestructura propia, sin depender de proxies externos.
  • Un modelo que escale con pocos operadores y que no requiera cambios en cada dispositivo.

Causa

Los fallos habituales en este tipo de despliegues provienen de tres áreas:

  1. Exposición directa de los contenedores
    Publicar puertos de los pods o de los contenedores Docker en Internet rompe el principio de “defensa en profundidad”. Además, los firewalls suelen bloquear tráfico inesperado y los navegadores móviles pueden rechazar certificados autofirmados.

  2. Autenticación fragmentada
    Cuando la SSO se implementa solo en la capa de la aplicación, los dispositivos BYOD no pueden completar el flujo OIDC porque la red interna no está accesible. Los intentos de “basic auth” o de tokens estáticos son inseguros y difíciles de rotar.

  3. Falta de segmentación y control de acceso
    Sin un punto de entrada único, cada aplicación necesita reglas de firewall propias. El mantenimiento se vuelve caótico y los errores de configuración aparecen con frecuencia (puertos abiertos, rutas mal definidas, etc.).

Solución

Un enfoque reutilizable combina un reverse proxy en el perímetro, un controlador de ingreso OIDC y políticas de Zero Trust en Entra ID. El esquema básico es:

  1. Punto de entrada único – Un load balancer con WAF en la DMZ recibe todo el tráfico HTTPS (puerto 443).
  2. Reverse proxy con OIDC – Nginx (o Traefik) ejecuta oauth2-proxy como sidecar. El proxy valida la sesión contra Entra ID y, una vez autenticado, genera una cookie de sesión corta.
  3. Ingress en Kubernetes – Cada aplicación se expone mediante un Ingress que solo permite tráfico proveniente del proxy interno (IP del load balancer).
  4. Conditional Access – Entra ID define dos grupos:
    • Managed – dispositivos Intune‑compliant → política que permite acceso sin MFA si la señal de cumplimiento está presente.
    • Unmanaged – cualquier BYOD → política que exige MFA y revisa la ubicación (IP del perímetro).
  5. TLS terminada en el perímetro – El certificado público es gestionado por la autoridad interna o por Let’s Encrypt; el tráfico entre el proxy y los pods sigue cifrado (mutual TLS opcional).

Paso a paso

a) Configurar el load balancer y WAF

  • Asignar un FQDN público (p.ej. apps.corp.example.com).
  • Habilitar reglas WAF para OWASP Top 10 y limitar los métodos HTTP a GET, POST, HEAD.

b) Desplegar oauth2-proxy

  • Registrar una aplicación en Entra ID con redirect URI https://apps.corp.example.com/oauth2/callback.
  • Guardar client_id y client_secret en un secret de Kubernetes.
  • Ejecutar oauth2-proxy con los scopes openid profile email y con cookie-refresh de 5 min.

c) Configurar Nginx como front‑end

  • Definir un bloque server que redirija /oauth2/ a oauth2-proxy.
  • Proteger los demás location con auth_request /oauth2/auth.
  • Añadir encabezados X-Forwarded-User para que las apps internas conozcan al usuario.

d) Ingress por aplicación

  • Cada app tiene su propio Ingress que apunta al servicio interno.
  • Se usa la anotación nginx.ingress.kubernetes.io/whitelist-source-range con la IP del proxy.

e) Políticas de Conditional Access

  • Crear una política que aplique a la aplicación “Internal Apps”.
  • Condición “Device platform = iOS/Android”.
  • Grant: “Require multi‑factor authentication” para usuarios no compliant; “Require device to be marked as compliant” para los gestionados.

f) Opcional: Azure AD Application Proxy como fallback

  • Si alguna app necesita acceso externo sin pasar por el perímetro (p.ej. pruebas rápidas), habilitar Application Proxy solo para esa ruta. Mantener la regla de firewall que permita tráfico saliente al agente de Proxy.

Cuándo aplicar esta solución

Vale cuando:

  • Se dispone de un perímetro con load balancer y WAF.
  • Los usuarios usan Entra ID y se cuenta con licencias E5 (para Conditional Access).
  • El equipo de operaciones es pequeño y necesita una única superficie de gestión.
  • Las apps están containerizadas y pueden exponerse mediante Ingress.

No aplica si:

  • No hay control de red en la DMZ (por ejemplo, solo VPN sin firewall de aplicación).
  • La organización prohíbe cualquier tráfico HTTPS fuera de la red interna (ni siquiera al FQDN público).
  • Se requiere acceso sin SSO (p.ej., apps legacy sin OIDC).

Código

# Deploy oauth2-proxy as a Kubernetes Deployment
cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: oauth2-proxy
spec:
  replicas: 2
  selector:
    matchLabels:
      app: oauth2-proxy
  template:
    metadata:
      labels:
        app: oauth2-proxy
    spec:
      containers:
      - name: oauth2-proxy
        image: quay.io/oauth2-proxy/oauth2-proxy:latest
        args:
        - --provider=oidc
        - --oidc-issuer-url=https://login.microsoftonline.com/<tenant-id>/v2.0
        - --client-id=$(CLIENT_ID)
        - --client-secret=$(CLIENT_SECRET)
        - --cookie-secret=$(COOKIE_SECRET)
        - --email-domain=*
        - --upstream=file:///dev/null
        - --http-address=0.0.0.0:4180
        envFrom:
        - secretRef:
            name: oauth2-proxy-secret
        ports:
        - containerPort: 4180
EOF
# Nginx snippet for the reverse proxy (placed in ConfigMap)
cat <<'NGINX' > nginx.conf
server {
    listen 443 ssl;
    server_name apps.corp.example.com;

    ssl_certificate /etc/nginx/certs/tls.crt;
    ssl_certificate_key /etc/nginx/certs/tls.key;

    location /oauth2/ {
        proxy_pass http://oauth2-proxy:4180;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }

    location / {
        auth_request /oauth2/auth;
        error_page 401 = @error401;
        proxy_pass http://internal-apps;
        proxy_set_header X-Forwarded-User $http_x_auth_request_user;
    }

    location @error401 {
        return 302 https://$host/oauth2/start?rd=$request_uri;
    }
}
NGINX

Verificación

  1. Desde un dispositivo gestionado

    • Abre https://apps.corp.example.com en Edge o Chrome.
    • Debería redirigir a la página de login de Entra ID y, tras la autenticación, entrar sin solicitar MFA.
  2. Desde un BYOD

    • Repite el paso anterior. El flujo debe pedir MFA (push, código o biometría).
  3. Comprobar encabezados

    • En la consola del navegador, verifica que la petición a la app interna contiene X-Forwarded-User con el UPN del usuario.
  4. Revisar logs del proxy

    • kubectl logs -l app=oauth2-proxy debe mostrar “authenticated_user” y la duración de la sesión.
  5. Escaneo de puertos

    • Desde fuera de la red, intenta conectar directamente al puerto del contenedor (p.ej., curl -k https://10.0.5.12:8443). Debería fallar, confirmando que solo el proxy está expuesto.

Notas adicionales

  • Rotación de secretos – Usa Azure Key Vault o sealed‑secrets para que client_secret y cookie_secret se renueven automáticamente.
  • CSP y HSTS – Añade encabezados de seguridad en Nginx para mitigar XSS y ataques de downgrade.
  • Auditoría de tokens – Configura Azure AD para registrar los eventos de login y revocar tokens comprometidos.
  • Pruebas de carga – Simula 100 conexiones concurrentes con hey o k6 para validar que el proxy y el WAF no se convierten en cuello de botella.
  • Fallos típicos
    • Redirecciones infinitas cuando el redirect_uri no coincide exactamente