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.
- Endpoint.
endpoint_public_accessen la raízcluster, por defectofalse: 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 verifyfalla. - Respaldo. Si el túnel falla el día del demo:
endpoint_public_access = trueypublic_access_cidrs = ["<IP>/32"]encluster/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/0no se acepta. Todo lo demás detecta el modo solo:make infra-kubeconfigyplataformaconsultanendpointPublicAccessdel clúster y van directo. - Bastión.
modules/bastion:t4g.nanocon 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 conAmazonSSMManagedInstanceCore[2] y nada más (no tiene permisos sobre EKS: solo reenvía bytes; quien se autentica ante Kubernetes eskubectlen 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 concluster(cluster_enabled,make infra-off), ymake infra-auditfalla si queda. - Salida del agente. El agente necesita HTTPS hacia
ssm,ssmmessagesyec2messagesde la región [2]. Sale por el NAT Gateway que ya existe para los nodos. Por eso la salida del bastión es 443 hacia0.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. - Túnel.
make infra-tunnelabre el reenvío enlocalhost:39443(puerto fijo del rango 39000 a 39999 del carril;TUNNEL_PORTlo cambia), en segundo plano, y vuelve;make infra-tunnel-stoplo cierra y termina la sesión en AWS. Busca el bastión por su etiquetaNamey 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/bindesdescripts/tools.sh(zip versionado, sha256 fijado enversions.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. - TLS. El certificado de la API es para el nombre del endpoint de EKS, no para
localhost. El kubeconfig apunta ahttps://localhost:39443contls-server-nameigual al nombre del endpoint [4]; los proveedores deplataformausantls_server_name. Así la verificación del certificado se mantiene; nunca se usainsecure-skip-tls-verify. - Contextos.
make infra-kubeconfigescribebuild/infra/kubeconfigcon dos contextos:andesretail-demo(perfil de AWSandesretail-demo, administrador) yandesretail-demo-ro(perfilandesretail-demo-ro, el deandes-readonlyconAmazonEKSViewPolicy: ve todo menos Secrets).make infra-kubeconfig-mergelos agrega a~/.kube/configcon respaldo previo, sin cambiar el contexto actual ni tocar otras entradas;make infra-kubeconfig-unmergelos 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
- Un recurso más que cobra por hora (0,005 dólares) y que puede fallar: si el agente no se registra en SSM, no hay túnel. Para eso está el respaldo público.
- Todo lo que habla con la API necesita el túnel abierto:
make infra-plan STACK=plataforma, el despliegue deplatform/k8symake infra-off(que lo abre solo). Un túnel caído a mitad de unapplydeplataformadeja el estado a medias, como una sesión de SSO vencida. - El bastión depende del NAT: con
nat_gateway_enabled = falseno hay túnel. Hoy el NAT solo se apaga con el clúster apagado, así que no cambia nada. - La salida del bastión a
0.0.0.0/0en el 443 (ver la decisión 4). kubectla través de un túnel agrega latencia (del orden de decenas de milisegundos). Para un demo en vivo no se nota; para pruebas de carga no se usa: k6 entra por Cloudflare, no por la API.- Las sesiones de Session Manager no se registran en S3 ni en CloudWatch (costo y una pieza más); CloudTrail sí registra quién abrió cada sesión.
Fuentes (consultadas el 14/11/2026)
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: documentoAWS-StartPortForwardingSessionToRemoteHost, parámetroshost,portNumberylocalPortNumber; exige el agente de SSM 3.1.1374.0 o posterior.https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-prerequisites.html(salida HTTPS aec2messages,ssmyssmmessagesde la región) yhttps://docs.aws.amazon.com/systems-manager/latest/userguide/setup-instance-permissions.html(política administradaAmazonSSMManagedInstanceCore).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.https://kubernetes.io/docs/reference/config-api/kubeconfig.v1/:tls-server-namedel cluster, "TLSServerName is used to check server certificate".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.nano0,0042 por hora;t3.nano0,0052. Disco: gp3 a 0,08 por GB al mes (fuente 6 dedocs/plataforma/05-infraestructura-aws.md).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).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.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 deus-east-1.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).