Denylist de tools
WhyStack pide acceso de lectura privilegiado a todo tu stack, y para una parte de él la respuesta legítima es no. Las operaciones que leen security groups, ACLs y reglas de firewall describen la superficie de ataque de tu infraestructura; algunos equipos no van a autorizar eso, y no deberían tener que desconectar AWS por completo (perdiendo de paso costos, compute y RDS) para poder decirlo.
La denylist es cómo dices “puedes leer mis métricas y mis logs, pero no mis security groups”.
Promesa y garantía
Sección titulada «Promesa y garantía»Una denylist que viviera solo en nuestro control-plane sería configuración nuestra, que tendrías que confiar en que respetamos. Aplicada en el borde, la puedes verificar tú: lees tu propio manifiesto de deployment. Es el mismo razonamiento que ya rige tus credenciales (nunca custodiadas fuera de tu infraestructura), la redacción de PII (en el borde, antes de que salga nada) y el gate de escritura.
Eso tiene una consecuencia que preferimos decir sin rodeos: la pantalla del control-plane es un borrador. Hasta que el artefacto se despliega, nada cambia: ni la superficie de tools ni lo que el agente puede leer. Ocultar tools a partir de una política que compusiste pero nunca desplegaste produciría la sensación de protección sin la protección, y un control de seguridad que existe solo en nuestra interfaz no es un control de seguridad.
Lo que puedes negar
Sección titulada «Lo que puedes negar»Cuatro niveles, resueltos como unión. El más fino gana donde se superponen:
| Nivel | Ejemplo | Cuándo usarlo |
|---|---|---|
| Grupo con nombre | network-posture, iam |
El camino principal. Un recorte curado que acompaña al catálogo. |
| Capability | security, audit |
Cuando toda la categoría está fuera de discusión. |
| Connector | amplitude |
Cuando toda la fuente está fuera de discusión. |
| Operación | network.listSecurityGroups |
Ajuste fino, cuando ninguno de los recortes de arriba sirve. |
Por qué un grupo y no una capability
Sección titulada «Por qué un grupo y no una capability»Porque una capability es demasiado gruesa para el caso que motiva esta funcionalidad. La capability
network cubre la lectura de security groups y la de VPCs, subnets, load balancers, target groups y
transit gateways. Negarla por completo también tumba el mapa de recursos, el blast radius y la topología
de DNS, que probablemente es lo opuesto de lo que quieres.
El grupo network-posture aísla exactamente las operaciones que describen la postura de red (regla de
firewall, ACL, security group) y deja el resto en pie.
Por qué no una lista de operaciones
Sección titulada «Por qué no una lista de operaciones»Puedes escribir una, y para el ajuste fino es la herramienta correcta. Pero una lista literal compuesta en marzo no cubre la operación de security group que se agregó al catálogo en mayo, y nadie relee su denylist en cada release. El artefacto guarda la intención, no la lista expandida: el grupo compuesto en marzo cubre la operación agregada en mayo.
El artefacto
Sección titulada «El artefacto»Un archivo JSON que montas en el deployment del runner (ConfigMap, archivo montado, lo que use tu GitOps):
{ "version": 1, "deny": { "groups": ["network-posture", "iam"], "capabilities": ["audit"], "connectors": ["amplitude"], "ops": ["query.runAthenaQuery"] }}Que sea un archivo y no un puñado de variables de entorno es deliberado: un archivo JSON en tu
repositorio de GitOps es diffable, pasa por un pull request y lo revisa tu equipo de seguridad. Una
variable de entorno enterrada en un values.yaml no la revisa nadie.
La ruta del archivo va en RUNNER_DENY_POLICY_FILE. Para el caso simple, cuatro variables hacen lo
mismo sin artefacto, y las dos fuentes se combinan si usas ambas:
| Variable | Contenido |
|---|---|
RUNNER_DENY_POLICY_FILE |
Ruta al artefacto JSON |
RUNNER_DENIED_GROUPS |
Grupos con nombre, separados por comas |
RUNNER_DENIED_CAPABILITIES |
Capabilities |
RUNNER_DENIED_CONNECTORS |
Connectors |
RUNNER_DENIED_OPS |
Operaciones |
Un artefacto ilegible, un JSON inválido o una clave desconocida dentro de él hacen fallar el boot en lugar de degradarse a “nada negado”: caer en una política vacía cuando pediste una negación es el peor valor por defecto posible.
Los grupos que existen hoy
Sección titulada «Los grupos que existen hoy»| Grupo | Cubre |
|---|---|
network-posture |
Security groups (resumen, asociaciones, los abiertos al mundo) y reglas de firewall en AWS y GCP. No cubre VPCs, subnets, load balancers, rutas ni DNS: negar la postura no debe tumbar el mapa de recursos. |
iam |
Higiene de claves y credenciales: access keys (incluida la búsqueda por sufijo), claves antiguas de service accounts de GCP, API/app keys. |
audit-trail |
El rastro de quién hizo qué: CloudTrail (lookup más la cadena de consultas de Lake) y los audit logs de GCP. No cubre el inventario de recursos: negar el rastro no debe tumbar el descubrimiento de lo que existe. |
end-user-activity |
El individuo: búsqueda y actividad de usuarios en Amplitude, más los session replays. No cubre las métricas agregadas de producto (usuarios activos, volumen de eventos, retención, conversión): negar esto protege a la persona, no apaga tu embudo. |
La lista acompaña al catálogo: una operación nueva que describa la postura de red entra en
network-posture sin que toques tu artefacto. Para eso existe el grupo.
Dónde se aplica
Sección titulada «Dónde se aplica»Dos capas, con la misma estructura que el gate de escritura: una en lo que se anuncia, otra en lo que se ejecuta.
- Anuncio. En el boot, el runner expande tu política contra su propio catálogo y resta las operaciones negadas del conjunto que declara servir. El control-plane nunca expone una tool cuya operación no se sirve, así que simplemente no existe para el modelo.
- Ejecución.
enforcement.ts, el mismo punto que ya rechaza cualquier operación fuera del catálogo, se niega a invocar una operación negada aunque la llamada llegue. Un control-plane con bugs, una versión vieja o un catálogo desincronizado no evaden la política.
La segunda capa existe justamente porque la primera depende de nosotros. La negación no se sostiene porque aceptamos ocultarla; se sostiene porque el proceso que corre dentro de tu infraestructura se niega a ejecutarla.
Cómo verificas que está en vigor
Sección titulada «Cómo verificas que está en vigor»El runner anuncia de vuelta lo que la política negó de verdad: qué operaciones, y bajo qué regla (si cayó bajo un grupo, una capability, un connector, o se nombró directamente). Es la expansión aplicada, no la intención que escribiste.
Eso importa por dos razones. Primero, es como descubres en qué se expande una etiqueta: el
network-posture que negaste se convierte en una lista concreta de operaciones, declarada por el
proceso que las rechaza. Segundo, se contrasta con el borrador: la vista Flota muestra ambos lado a
lado, y cualquier divergencia entre “lo que compuse” y “lo que está en vigor” es visible (típicamente un
artefacto que aún no se ha desplegado, o una flota que corre versiones mezcladas).
Suprimido no es ausente
Sección titulada «Suprimido no es ausente»Esta es la parte más importante de esta página, y la razón por la que la denylist no es una simple resta.
Si una tool simplemente desapareciera, tres situaciones se verían idénticas: el connector no está configurado, el connector está roto, y decidiste prohibirlo. Solo en la tercera la infraestructura existe, está sana, y el vacío es una decisión. Si le preguntaran por la exposición de red, el agente respondería “no encontré security groups permisivos”. Eso es falso, y es el peor resultado posible, porque un error hace que alguien investigue y una respuesta tranquilizadora no.
Así que la supresión es legible, en tres lugares:
- La sesión conoce la política antes de necesitarla. La política se inyecta en el contexto de la sesión, así que el agente responde “eso está deshabilitado por la política de tu tenant” en lugar de “no encontré nada”.
- La respuesta lleva el motivo. Una fuente negada se reporta como
denied_by_policy, que cuenta como no pude leer, nunca como no existe. “La política me lo prohibió” no es prueba de ausencia. - La vista Flota distingue las tres situaciones desde afuera: nunca conectado · conectado y roto · conectado y restringido.
Qué pasa con los análisis compuestos
Sección titulada «Qué pasa con los análisis compuestos»Un análisis que dependía de una operación negada corre con lo que queda y dice lo que faltó, en lugar de fallar por completo. Un análisis compuesto multiseñal suele tocar mucho más que la dimensión negada, y apagarlo haría que la capacidad desapareciera sin explicación.
Lo que no sale es el veredicto derivado. Un puntaje de seguridad calculado a partir de penalizaciones saldría más alto precisamente porque no se pudo leer la dimensión negada (una lectura que nunca ocurrió mejorando el número), así que el puntaje no se emite. Recibes lo que se observó, marcado como parcial, y ninguna conclusión que dependa de lo que la política ocultó.
Lo que la política no borra
Sección titulada «Lo que la política no borra»El aprendizaje se detiene automáticamente: el conocimiento derivado (topología, mapeos) se extrae del resultado de las operaciones, y una operación negada no produce resultado.
Lo que se aprendió antes de que la política entrara en vigor se queda donde está. Eso es deliberado: el artefacto vive en un runner desechable, y un error de tipeo en un archivo no debe destruir en silencio meses de topología aprendida. La purga es una acción separada y auditada: la vista Flota muestra que existe estado anterior a la política y ofrece borrarlo.
Cuando el boot falla a propósito
Sección titulada «Cuando el boot falla a propósito»Si el artefacto nombra una operación que el runner no conoce (renombrada en el catálogo, o compuesta contra una versión más nueva), el runner se niega a arrancar y nombra la entrada.
Parece severo, y es la decisión correcta: una directiva de negación que no tiene efecto es invisible hasta que se convierte en incidente. Los runners son cattle, un boot que falla hace ruido, y tu supervisor muestra el error de inmediato, mientras que una política parcial en silencio solo aparece el día en que alguien lee lo que no debía.
Una entrada también puede nombrar algo que el catálogo tuvo y quitó a propósito. El rechazo es el
mismo: la regla ya no protege nada, y mantenerla es creer en una protección que ya no existe. Pero el
mensaje es distinto: dice en qué versión salió el objetivo y qué usar en su lugar, en vez de mandarte a
buscar una operación renombrada, que aquí sería la causa equivocada. Es el caso de la capability
profiling y del connector witness, ambos eliminados en la versión 0.50.0 del catálogo: si tu política
todavía los nombra, consulta el Profiler.