← Volver al blog
Un error de AWS que solo nombra s3:GetObject y, debajo, la nota de KubeBolt que añade el permiso que faltaba: kms:Decrypt sobre la clave del bucket

Kobi en AWS: diez incidentes y dónde fue cada arreglo

El conector de AWS salió en KubeBolt 2.5. Antes lo probamos con diez incidentes en un laboratorio de EKS. Así lee Kobi tu cuenta sin escribir una línea de código, y por eso casi ningún arreglo fue una frase más en el prompt.

El conector de AWS salió el 27 de octubre en KubeBolt 2.5, la versión open source. Antes de soltarlo lo probamos con diez incidentes en un laboratorio de EKS: una regla de red borrada, una clave KMS desactivada, una Lambda que deja de consumir. En cada uno, Kobi recibía la pregunta que haría un operador, sin pistas.

Lo más útil que dejaron esas pruebas es dónde va el arreglo cuando Kobi se equivoca: el dato va donde mira el modelo. Casi nunca fue una frase más en el prompt. Fue un dato puesto junto a lo que Kobi ya estaba leyendo.

Lo que hay debajo, en cinco líneas:

  • Kobi no escribe código contra tu cuenta. Elige una operación de una lista, y KubeBolt hace la llamada con una sesión que no puede más que esa lista.
  • Diez incidentes, todos probados en vivo. Cinco salieron a la primera pregunta, y los otros cinco necesitaron arreglos entre una vuelta y otra.
  • Cuando Kobi se equivocaba, reescribir la skill no lo movía. Poner el dato junto al error, sí.
  • La prueba más barata costó 32 créditos, y la mayoría, entre 50 y 100.
  • El conector está en KubeBolt 2.5 open source. En KubeBolt Cloud todavía no.

El recorrido de una pregunta

El caso del laboratorio: orders-api deja de llegar a su base de datos, un PostgreSQL de RDS que el clúster alcanza por un nombre privado. Así va la pregunta desde el pod hasta la cuenta:

TU CLÚSTER · EKS KUBEBOLT AWS · TU CUENTA orders-api pod · shop-backend cannot reach database shop-db Service ExternalName → db.tienda.internal Nodos spec.providerID → 2 instancias EC2 operador Kobi Haiku 5.5 pasa a Sonnet 5.5 al salir a la nube B C get_external_ connections route53 privada red del clúster 2 instancias EC2 sin listar la cuenta A Gateway MCP de KubeBolt D 16 familias escribe el script asume el rol external ID política de sesión compacta la respuesta auditado por org. AWS MCP Server alojado por AWS · ejecuta el script firmado con la sesión asumida Tu cuenta de AWS CloudTrail quién y cuándo el cambio entero EC2 y red security groups network ACL RDS la instancia y sus eventos Route 53 zonas privadas de la VPC 1 logs 2 host 3 clasifica el método 4 llamada 5 script 6 lectura rol del cliente 7 respuesta TU CLÚSTER · EKS KUBEBOLT AWS · TU CUENTA orders-api pod · shop-backend cannot reach database shop-db Service ExternalName → db.tienda.internal Nodos providerID → EC2 operador Kobi Haiku 5.5 pasa a Sonnet 5.5 al salir a la nube B C get_external_ connections route53 privada red del clúster 2 instancias EC2 sin listar la cuenta A Gateway MCP de KubeBolt D 16 familias escribe el script asume el rol external ID política de sesión compacta la respuesta auditado por org. AWS MCP Server alojado por AWS · ejecuta el script firmado con la sesión asumida Tu cuenta de AWS CloudTrail quién y cuándo el cambio entero EC2 y red security groups network ACL RDS la instancia y sus eventos Route 53 zonas privadas de la VPC 1 2 3 4 5 6 7
  • ALa clasificación de la dirección
  • BLa pista del conector y la guía de dependencias
  • CLa skill aws-investigation
  • DLas notas del gateway
  1. Logs del backend. get_pod_logs da el timeout hacia shop-db…:5432, con su inicio y su fin.
  2. La dirección. El Service ExternalName lleva a db.tienda.internal, un nombre privado.
  3. La clasificación. get_external_connections lo reconoce como una zona privada de Route 53, con el conector disponible, y añade las instancias de los nodos.
  4. La llamada tipada. Kobi pide servicio, operación y parámetros a mcp__aws__call. Nunca código.
  5. El script. El gateway comprueba la operación contra la lista, escribe el script y asume el rol del cliente con su external ID y una política de sesión.
  6. La lectura. El servidor MCP de AWS ejecuta el script en la cuenta, con la sesión asumida.
  7. La respuesta. Compactada por el gateway y sin valores de secretos: causa, quién y cuándo.
Las llamadas 1 a 4 las hace Kobi con sus herramientas de KubeBolt. La 5 y la 6 cruzan a AWS, siempre por el gateway. Las letras A a D son las cuatro capas de lo que Kobi sabe de AWS. Nombres del laboratorio, cambiados.

Como en Azure, Kobi no tiene una skill por servicio. Lo que sabe de AWS llega en cuatro capas, y dos de ellas viajan dentro del resultado de una herramienta, en el momento en que Kobi las necesita.

Kobi no escribe código contra tu cuenta

El servidor MCP de AWS no tiene modo de solo lectura: su herramienta ejecuta Python con la identidad de quien llama. Así que las protecciones están en KubeBolt y en el rol, y se repiten a propósito:

líneas de código del modelo que lleguen a la cuenta
0
familias de lectura, que aprueba el administrador
16
llamadas a AWS como mucho por turno; el prompt le pide cinco
12
  • El modelo no escribe códigoKobi pasa servicio, operación y parámetros a una herramienta tipada, y KubeBolt escribe el script que ejecuta el servidor de AWS.
  • Una tabla, tres filtrosLa lista del gateway, la política de sesión y la política del rol salen de la misma tabla de familias, así que no pueden contradecirse.
  • La sesión no puede más que la listaCada llamada asume el rol con una política de sesión. Aunque el rol del cliente tuviera ReadOnlyAccess, la sesión no pasa de la lista.
  • Ni secretos ni datosEl rol niega los valores de Secrets Manager y Parameter Store, descifrar con KMS, los objetos de S3, las filas de DynamoDB y el código de una función.
  • Lo que vuelve, limpioLas variables de entorno de una Lambda llegan con su nombre y el valor tapado, también en CloudTrail, igual que las credenciales de una respuesta.
  • Nada más por el servidor MCPLa política del rol niega cualquier acción fuera de la lista cuando la llamada llega por el servidor MCP de AWS (aws:ViaAWSMCPService).
  • Un external ID que nadie eligeKubeBolt lo genera para cada organización, y el administrador solo lo copia en la confianza del rol.
  • Servicios enteros fueraLa Data API de RDS, los streams de DynamoDB y SSO no admiten ninguna operación, ni siquiera de lectura.

Diez incidentes

Cada incidente se rompió como pasaría de verdad, con el usuario del laboratorio y aws-cli, y se preguntó sin pistas. Una vuelta es una pregunta. Cuando hizo falta más de una, entre vuelta y vuelta cambió algo en KubeBolt.

  1. 01 1 + seguimiento

    La regla de la base

    Se borra la regla del security group de RDS que deja entrar a los pods.

    Causa, autor y hora en una pregunta; la regla exacta, en el seguimiento.

    Lee ec2 · rds · cloudtrail50 + 99 créditos

  2. 02 5 vueltas

    La entrada desde Internet

    Una regla de la network ACL deja fuera al ALB, y dentro del clúster todo sigue en verde.

    Con los arreglos, causa, autor y hora en una pregunta y un seguimiento, también con el laboratorio recién creado.

    Lee elbv2 · ec2 · cloudtrail97 y 115 créditos

  3. 03 3 vueltas

    El rol sin su política

    catalog-api pierde la política para leer su bucket, cifrado con una clave KMS propia.

    La clave nombrada en la respuesta y comprobada en el seguimiento, con los dos permisos.

    Lee iam · s3 · kms34 + 22 créditos

  4. 04 2 vueltas

    La clave desactivada

    S3 responde KMS.DisabledException, con un incidente anterior aún en los logs.

    El segundo de tres cambios sobre la clave, separado del incidente anterior.

    Lee kms · cloudtrail50 créditos

  5. 05 1 pregunta

    La contraseña rotada

    Secrets Manager rota la contraseña y los pods siguen con la vieja.

    El RotateSecret correcto y el rollout restart en una pregunta, sin ver nunca el valor.

    Lee secretsmanager · cloudtrail55 créditos

  6. 06 1 pregunta

    Un reinicio de diez segundos

    Se reinicia la RDS y la tienda falla unos segundos. Se pregunta después.

    El reinicio, quién y cuándo, con la fuente de cada hecho.

    Lee rds · cloudtrail56 créditos

  7. 07 1 pregunta

    La imagen que no se descarga

    La política del repositorio de ECR deja fuera al rol de los nodos.

    La política, su autor y el ARN del rol escrito sin su path.

    Lee ecr · iam · cloudtrail32 créditos

  8. 08 3 vueltas

    El registro DNS

    El CNAME privado de la base apunta a una instancia que no existe.

    El registro en la primera respuesta y la instancia que falta, en el seguimiento.

    Lee route53 · ec2 · rds88 créditos

  9. 09 1 pregunta

    Un nodo que desaparece

    Se termina una instancia del node group, con las dos réplicas de CoreDNS dentro.

    El autor, el reemplazo y el hueco de DNS, con la propuesta de repartir CoreDNS.

    Lee eks · autoscaling · cloudtrail57 créditos

  10. 10 2 vueltas

    Los pedidos que no se confirman

    La Lambda que confirma pedidos queda con concurrencia 0. La cola y su DLQ crecen.

    La DLQ leída y el arreglo en dos pasos; el autor, en el seguimiento.

    Lee sqs · lambda · cloudtrail67 + 39 créditos

El laboratorio tiene un cerco que una cuenta real no tendría: niega CloudTrail en us-east-1, donde AWS registra los cambios de IAM y de Route 53. Por eso en dos pruebas Kobi no pudo nombrar al autor, y lo dijo.

De cerca: la contraseña rotada

Es el mejor ejemplo de por qué Kobi tiene que salir del clúster. La causa está en AWS, el fallo se ve en el clúster, y el arreglo no es el que parece: el Secret ya tiene la contraseña nueva, pero los pods la leyeron al arrancar.

AWS · SECRETS MANAGER, LAMBDA Y RDS CLÚSTER · TIENDA-EKS Secrets Manager RotateSecret 18:23:45 lo registra CloudTrail Lambda de rotación createSecret → finishSecret AWSCURRENT nueva 18:23:46 RDS · orders_app contraseña nueva aplicada corta 2 sesiones viejas El arreglo rollout restart de orders-api nunca volver a AWSPREVIOUS ExternalSecret sincroniza cada minuto secret updated 18:24:12 Secret ya trae la contraseña nueva se lee al arrancar orders-api · 2/2 Ready arrancaron a las 18:15 28P01 desde 18:23:46 la aplica en la base lee AWSCURRENT 26 s después 28P01: la base ya no acepta la vieja AWS CLÚSTER Secrets Manager RotateSecret 18:23:45 CloudTrail Lambda de rotación createSecret → finishSecret AWSCURRENT nueva 18:23:46 RDS · orders_app contraseña nueva corta 2 sesiones ExternalSecret secret updated 18:24:12 26 s después Secret ya trae la nueva orders-api arrancaron a las 18:15 28P01 desde 18:23:46 El arreglo: rollout restart de orders-api nunca volver a AWSPREVIOUS
La prueba de la contraseña rotada, 22 de octubre. AWS cambia la contraseña y el Secret del clúster se pone al día en 26 segundos, pero los pods la leyeron al arrancar y siguen con la vieja. Kobi encontró el RotateSecret correcto en CloudTrail y propuso el rollout restart, sin ver nunca el valor.

El dato va donde mira el modelo

La prueba del rol lo enseña mejor que ninguna. catalog-api lee de S3 con su propio rol, y el bucket cifra con una clave KMS propia. Alguien desadjunta la política del rol. El error de la app nombra la acción que falló primero, s3:GetObject, y no dice que leer ese objeto exige también kms:Decrypt sobre la clave.

Lo primero fue lo de siempre: reescribir la skill de AWS para que lo dijera. Kobi la cargó y dio la misma respuesta. Así que el dato fue a donde está el error:

Lo que Kobi tenía delante, en los logs de catalog-api

… is not authorized to perform: s3:GetObject on resource: …/featured.json

Lo que get_pod_logs pone ahora junto a esa línea

"awsDenials": [{
  "action": "s3:GetObject",
  "resource": "arn:aws:s3:::…/featured.json",
  "alsoGuardedBy": "An S3 object is also guarded by its bucket's policy
    (s3 GetBucketPolicy) and by the bucket's default encryption
    (s3 GetBucketEncryption). With SSE-KMS under a customer-managed key,
    reading an object also needs kms:Decrypt on that key, …"
}]
  1. 1.ª vuelta Propone devolver solo s3:GetObject. La lectura habría seguido fallando, ahora por la clave.
  2. 2.ª vuelta, con la skill reescrita Carga la skill y da la misma respuesta. La frase nueva no cambió nada.
  3. 3.ª vuelta, con awsDenials Nombra kms:Decrypt en la respuesta. En el seguimiento lee la clave y da los dos permisos.
La prueba del rol sin su política, 22 de octubre. Una frase dentro de una skill de 9 KB pierde contra el error que el modelo tiene delante. El mismo dato junto al error, no.

No fue un caso aislado. Casi todos los arreglos del laboratorio siguieron la misma regla:

Dónde se equivocó KobiLo que KubeBolt pone ahora donde mira
Una negativa de Route 53 le hizo dar el servicio por perdidoTras un AccessDenied, una nota: AWS negó una operación, no el servicio, y estas son las otras habilitadas
La regla, borrada por su id, no salía en CloudTrailCloudTrail guarda también la respuesta de cada cambio, con la regla completa
Buscó la clave KMS por su id y no encontró el cambioUna nota: el DisableKey nombra la clave por su ARN
Dio por vacía una DLQ con unos 404 pedidos dentroGetQueueAttributes lleva una nota cuando la cola tiene DLQ
Leyó 185 copias de la misma línea de logLas líneas iguales salvo la hora salen una vez, con cuántas eran y desde cuándo
Tomó un evento que sumaba dos episodios por el inicio del corteUn evento con más de 30 minutos entre su primera y su última aparición lleva ese tramo y una nota

Ninguna es una regla nueva en el prompt. Cada fallo se podía tapar con una frase más, y así es como engordan los prompts. Un dato no compite con el error que el modelo tiene delante, porque está a su lado.

De cerca: la clave desactivada

Esta prueba puso dos de esos datos a trabajar a la vez. En la misma hora había tres tramos de error en los logs, dos de ellos de la misma clave, y tres cambios sobre ella en CloudTrail. Con las mismas cinco llamadas, la segunda vuelta llegó más lejos porque las respuestas traían las notas: la de la búsqueda por id y la hora de cada denegación.

CloudTrail DisableKey 16:31:43 EnableKey 16:43:39 DisableKey 16:44:09 Logs ◂ desde 15:30:34 · AccessDenied · incidente anterior KMS 1 min sano KMS.DisabledException Preguntas 1.ª · 16:33 2.ª · 17:05 16:00 16:15 16:30 16:45 17:00 16:00 16:15 16:30 16:45 17:00 Logs CloudTrail Preguntas AccessDenied KMS KMS.DisabledException DisableKey 16:31:43 EnableKey 16:43:39 DisableKey 16:44:09 1.ª · 16:33 2.ª · 17:05
1.ª vuelta · 16:3350 créditos
  1. 1 DescribeKey KeyState: Disabled
  2. 2 CloudTrail por el id solo su creación
  3. 3 CloudTrail por origen Decrypt fallidos
  4. 4 SimulatePrincipalPolicy deny falso, sin región
  5. 5 CloudTrail, escrituras DisableKey 16:31:43

Causa, autor y hora en una pregunta, pero tres de las cinco llamadas le contestaron de forma engañosa.

2.ª vuelta · 17:05, con las notas50 créditos
  1. 1 DescribeKey KeyState: Disabled
  2. 2 CloudTrail por el id vacío, con la nota
  3. 3 CloudTrail por el ARN los tres cambios
  4. 4 CloudTrail, escrituras repite lo que tenía
  5. 5 El rol, en us-east-1 AccessDenied del cerco

Los tres cambios y, como causa, el segundo DisableKey. Las dos últimas llamadas sobraron.

La prueba de la clave desactivada, 22 de octubre. Arriba, lo que Kobi tenía que separar: tres tramos de error en los logs, dos de ellos de la misma clave, y tres cambios sobre ella en CloudTrail. Abajo, las cinco llamadas de cada vuelta: la misma cantidad, con respuestas distintas.

Cuando un agente se equivoca, decirle más cosas casi nunca lo arregla. Poner el dato que le faltaba donde ya está mirando, sí.

Lo que todavía falla

La lista de huecos que salió del laboratorio tiene más entradas de las que cuento aquí. Dos de las que menos me preocupan:

  • Comprobar antes de proponer. Cuando la causa le parece clara, Kobi a veces deja sin hacer una lectura que tiene permitida y para la que le sobran llamadas, como confirmar la versión actual de un secreto antes de proponer un reinicio. Lo dice y lo ofrece como opción, pero no lo comprueba.
  • La zona horaria al pedir a CloudTrail. Con horas en dos zonas distintas, Kobi pidió a CloudTrail una ventana desplazada y se le escapó el cambio. Pasa sobre todo en desarrollo, con la API en hora local.

Lo que todavía no es

El conector está en KubeBolt 2.5, la versión open source. Hoy KubeBolt asume el rol del cliente desde la identidad con la que corre, y eso sirve en instalaciones propias. En KubeBolt Cloud todavía no está.

Lo que queda

Diez incidentes dejaron una regla corta: el dato va donde mira el modelo. Con eso, un conector de solo lectura sirve para algo. Kobi lee tu cuenta sin escribir una línea de código, y lo que lee le llega con lo que necesita para no equivocarse.