Capa de interoperabilidad · Prueba técnica de arquitectura

ADR-027: API de Kubernetes solo privada, con acceso por túnel de Session Manager

Aceptado el 14/11/2026 por Andrés; escrito por el carril `acceso-eks`. Reemplaza en parte ADR-022 (el endpoint de la API de Kubernetes: la excepción de Trivy del endpoint público en la decisión 14 y la segunda viñeta de "Qué se sacrifica"); el resto de ADR-022 sigue vigente.

Contexto

ADR-022 dejó el endpoint de la API de Kubernetes de EKS público y privado a la vez, con el público limitado a la IP de Andrés (public_access_cidrs) y dos excepciones de Trivy por eso (AVD-AWS-0040 y AVD-AWS-0041). Funciona, pero deja la API de un clúster con secretos de clientes expuesta a internet (aunque filtrada), obliga a cambiar la variable cada vez que cambia la IP de la casa o de la sala de la presentación, y es justo lo que un evaluador de arquitectura señala.

Andrés decidió el 14/11/2026 que el endpoint sea solo privado y que él entre por un túnel, para mostrar Kubernetes en vivo durante el demo (kubectl, k9s). Lo que debe seguir funcionando por ese camino: kubectl y k9s desde su equipo (Windows 11 con Git Bash), los proveedores helm y kubernetes de la raíz plataforma, make infra-off y el despliegue de platform/k8s.

Restricciones: costo mínimo por hora, nada que instalar fuera de .tools/ (regla del proyecto), nada que entre desde internet, y un camino de vuelta si el túnel falla el día de la presentación.

Opciones

Opción Cómo se entra Costo por hora con el clúster encendido Qué se sacrifica
A. Endpoint público limitado por IP (ADR-022) kubectl directo al endpoint público 0 La API queda en internet; depende de la IP del momento (casa, sala, red móvil); dos excepciones de Trivy
B. Bastión con AWS Systems Manager Session Manager y reenvío de puerto aws ssm start-session con el documento AWS-StartPortForwardingSessionToRemoteHost: el agente de SSM del bastión abre la conexión al endpoint privado y la reenvía a un puerto local [1] 0,0042 de un t4g.nano [5] más su disco de 8 GiB (unos 0,0009); en total 0,005 Un recurso más (instancia, rol, grupo de seguridad); el complemento de Session Manager en el equipo; depende del NAT (el agente sale por él)
C. EC2 Instance Connect Endpoint aws ec2-instance-connect open-tunnel con --private-ip-address y --remote-port 443 [6] No encontrado en las páginas de precios de AWS consultadas: por medir Túneles de máximo 1 hora (--max-tunnel-duration, por defecto 3600 segundos) [6]; uno por VPC y 20 conexiones simultáneas [7]; el endpoint privado de EKS cambia de IP y el túnel apunta a una IP, no a un nombre; menos documentado para este uso
D. AWS Client VPN Cliente de VPN en el equipo, la VPC queda alcanzable 0,10 por asociación del endpoint y 0,05 por conexión, según el ejemplo de la página de precios (US East, Ohio) [8] Veinte veces el costo de B; certificados de cliente o un proveedor de identidad que administrar; un cliente de VPN instalado en el sistema

Decisión

Opción B: endpoint solo privado y bastión de Session Manager, con la A como respaldo explícito.

  1. Endpoint. endpoint_public_access en la raíz cluster, por defecto false: solo privado. Las dos excepciones de Trivy del endpoint público se quitan del código: en modo privado no hacen falta, y si el respaldo quedara encendido por defecto, make verify falla.
  2. Respaldo. Si el túnel falla el día del demo: endpoint_public_access = true y public_access_cidrs = ["<IP>/32"] en cluster/terraform.tfvars, plan y aplicar (unos minutos; EKS actualiza el endpoint sin recrear el clúster). Sin IP, el plan falla; 0.0.0.0/0 no se acepta. Todo lo demás detecta el modo solo: make infra-kubeconfig y plataforma consultan endpointPublicAccess del clúster y van directo.
  3. Bastión. modules/bastion: t4g.nano con Amazon Linux 2023 (trae el agente de SSM [3]) en la primera subred privada; sin llave SSH, sin IP pública, sin reglas de entrada; salida solo al 443; IMDSv2 a un salto; disco cifrado; rol de instancia con AmazonSSMManagedInstanceCore [2] y nada más (no tiene permisos sobre EKS: solo reenvía bytes; quien se autentica ante Kubernetes es kubectl en el equipo, con su propio rol). Una regla en el grupo de seguridad del clúster deja entrar al bastión, y solo al 443, a las interfaces del endpoint privado. Se crea y se destruye con cluster (cluster_enabled, make infra-off), y make infra-audit falla si queda.
  4. Salida del agente. El agente necesita HTTPS hacia ssm, ssmmessages y ec2messages de la región [2]. Sale por el NAT Gateway que ya existe para los nodos. Por eso la salida del bastión es 443 hacia 0.0.0.0/0, con una excepción de Trivy escrita en el código (AVD-AWS-0104). Los endpoints de VPC (PrivateLink) la cerrarían a la VPC, pero son tres interfaces por zona a 0,01 dólares por hora cada una: seis veces el bastión.
  5. Túnel. make infra-tunnel abre el reenvío en localhost:39443 (puerto fijo del rango 39000 a 39999 del carril; TUNNEL_PORT lo cambia), en segundo plano, y vuelve; make infra-tunnel-stop lo cierra y termina la sesión en AWS. Busca el bastión por su etiqueta Name y el endpoint por el nombre del clúster: no lee el estado de OpenTofu. El complemento de Session Manager 1.2.896.0 queda portable en .tools/bin desde scripts/tools.sh (zip versionado, sha256 fijado en versions.env, firma de AWS comprobada); no se usa el instalador oficial, que pide administrador, registra un servicio de Windows y cambia el PATH del usuario.
  6. TLS. El certificado de la API es para el nombre del endpoint de EKS, no para localhost. El kubeconfig apunta a https://localhost:39443 con tls-server-name igual al nombre del endpoint [4]; los proveedores de plataforma usan tls_server_name. Así la verificación del certificado se mantiene; nunca se usa insecure-skip-tls-verify.
  7. Contextos. make infra-kubeconfig escribe build/infra/kubeconfig con dos contextos: andesretail-demo (perfil de AWS andesretail-demo, administrador) y andesretail-demo-ro (perfil andesretail-demo-ro, el de andes-readonly con AmazonEKSViewPolicy: ve todo menos Secrets). make infra-kubeconfig-merge los agrega a ~/.kube/config con respaldo previo, sin cambiar el contexto actual ni tocar otras entradas; make infra-kubeconfig-unmerge los quita. El túnel se abre con el perfil de administrador; por el mismo túnel entra después el contexto de solo lectura, que es el que se muestra en vivo.

Qué se sacrifica

Fuentes (consultadas el 14/11/2026)

  1. https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-working-with-sessions-start.html, sección de reenvío de puerto a un host remoto: documento AWS-StartPortForwardingSessionToRemoteHost, parámetros host, portNumber y localPortNumber; exige el agente de SSM 3.1.1374.0 o posterior.
  2. https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-prerequisites.html (salida HTTPS a ec2messages, ssm y ssmmessages de la región) y https://docs.aws.amazon.com/systems-manager/latest/userguide/setup-instance-permissions.html (política administrada AmazonSSMManagedInstanceCore).
  3. https://docs.aws.amazon.com/systems-manager/latest/userguide/ami-preinstalled-agent.html: Amazon Linux 2023 está en la lista de imágenes que traen el agente de SSM.
  4. https://kubernetes.io/docs/reference/config-api/kubeconfig.v1/: tls-server-name del cluster, "TLSServerName is used to check server certificate".
  5. https://b0.p.awsstatic.com/pricing/2.0/meteredUnitMaps/ec2/USD/current/ec2-ondemand-without-sec-sel/US%20East%20(N.%20Virginia)/Linux/index.json (la lista de la página de precios de EC2), 14/11/2026: t4g.nano 0,0042 por hora; t3.nano 0,0052. Disco: gp3 a 0,08 por GB al mes (fuente 6 de docs/plataforma/05-infraestructura-aws.md).
  6. https://docs.aws.amazon.com/cli/latest/reference/ec2-instance-connect/open-tunnel.html: --private-ip-address, --remote-port (por defecto 22) y --max-tunnel-duration (por defecto 3600 segundos).
  7. https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/eice-quotas.html: un EC2 Instance Connect Endpoint por VPC y 20 conexiones simultáneas por endpoint.
  8. https://aws.amazon.com/vpn/pricing/: ejemplo de precios de Client VPN en US East (Ohio), 0,10 por hora del endpoint y 0,05 por conexión y hora; la página no da la tabla de us-east-1.
  9. https://docs.aws.amazon.com/systems-manager/latest/userguide/plugin-version-history.html: complemento 1.2.896.0 del 09/10/2026 (la 1.2.889.0 fue retirada).