Módulo 7: Detective Vs Preventive Guardrails

8. Proyecto: el mapa de guardrails de Andes Cargo

Descripción

Las lecciones 2 a 7 de este módulo construyeron o nombraron ocho mecanismos distintos de guardrail. Este proyecto los reúne en un único documento —GUARDRAILS-MAP.md, en la raíz de andes-cargo-infra/, junto a THREAT-MODEL.md y RISK-MAP.md— que los clasifica, sin ninguna ambigüedad, en dos ejes independientes: preventivo o detectivo (qué tipo de control es), y implementado, representativo o nombrado (qué tan construido está en este laboratorio $0 específico). Es, con precisión, el entregable que este módulo entero existe para producir.

Conexión con el módulo

Este documento no agrega ningún dato nuevo — organiza, con la misma disciplina que ya aplicaste en THREAT-MODEL.md (Módulo 1, lección 7) y RISK-MAP.md (Módulo 1, lección 8), lo que las seis lecciones anteriores de este módulo ya construyeron y explicaron. La diferencia con esos dos documentos es el eje de clasificación: mientras RISK-MAP.md ordena riesgos por secuencia de resolución, este documento ordena mecanismos de guardrail por tipo y por grado de construcción — la pregunta que un entrevistador técnico haría, con precisión, frente a cualquiera de los ocho.


Paso 1 — Por qué ocho piezas, ni una más ni una menos

Antes de escribir la tabla, vale la pena contar con cuidado qué cuenta como una "pieza" de este módulo, para no inflar el mapa con filas artificiales ni perder ninguna real. Las ocho son:

  1. El permission boundary de AppServerRole (lección 3)
  2. Service Control Policies, como mecanismo (lecciones 2 y 6)
  3. AWS Organizations, como estructura (lección 6)
  4. AWS Control Tower, como capa de orquestación (lección 6)
  5. CloudTrail —el trail andes-cargo-trail— (lección 5)
  6. AWS Config (lección 4)
  7. GuardDuty (lección 4)
  8. La regla de EventBridge + tema de SNS sobre eventos sensibles de IAM (lección 7)

Fíjate en dos decisiones deliberadas: Organizations y Control Tower cuentan como dos piezas separadas, no una — la lección 6 las presentó como conceptos técnicamente distintos (una es la estructura, la otra es la orquestación sobre esa estructura), y colapsarlas en una sola fila escondería esa distinción. Y SCP cuenta como una sola pieza, aunque aparezca en dos lecciones (2 y 6) — es el mismo mecanismo técnico en ambos lugares, solo que la lección 2 lo introduce por contraste con el permission boundary y la lección 6 lo retoma a escala multi-cuenta; una sola fila, con ambas lecciones citadas.


Paso 2 — El documento completo

En la raíz de andes-cargo-infra/, junto a THREAT-MODEL.md y RISK-MAP.md, crea GUARDRAILS-MAP.md:

# GUARDRAILS-MAP.md — Preventive vs. Detective Guardrails for `cloud-security-and-guardrails-guide`

**Status:** Accepted · **Date:** this module's close · **Supersedes:** none
**Governs:** Module 7 of `cloud-security-and-guardrails-guide`
**Source:** `THREAT-MODEL.md` (`TM-03`, `TM-07`), `RISK-MAP.md`, this module's lessons 2-7

## Context

Module 7 introduced a vocabulary that governs every control this guide has built since Module 2:
**preventive** (could not happen) versus **detective** (it happened, and there is a record). This
document classifies the eight distinct guardrail mechanisms this module named or built, along two
independent axes: what **type** each one is, and how **built** each one is in this specific $0 lab.
Three of the eight mechanisms have compound build status on purpose — the resource is real and
validated, but the live enforcement or trigger could not be confirmed against this environment. That
compound status is stated explicitly in every row where it applies, never collapsed into a single
label that would overstate or understate what actually ran.

## The matrix

| # | Guardrail | Type | Build status | Lesson | Why |
|--:|---|---|---|---|---|
| 1 | Permission boundary (`AppServerRole`) | Preventive | Implemented (resource) + Representative (enforcement) | M7.3 | `aws_iam_policy` + `permissions_boundary` declared, `terraform validate`/`plan` real; the actual blocked call requires `IAM Policy Enforcement` (LocalStack Base/Ultimate only) |
| 2 | Service Control Policy (SCP) | Preventive | Named | M7.2, M7.6 | Same `IAM Policy Enforcement` dependency as row 1, plus a structural precondition (AWS Organizations) Andes Cargo does not have |
| 3 | AWS Organizations | Preventive | Named | M7.6 | Ecosystem-level gap (`VALIDACION.md`, `ALTA`, unassigned); Andes Cargo is a single account |
| 4 | AWS Control Tower | Preventive | Named | M7.6 | Orchestration layer over Organizations + SCPs; same precondition as row 3 |
| 5 | CloudTrail (`andes-cargo-trail`) | Detective | Implemented (resource) + Representative (event read) | M7.5 | `aws_cloudtrail` + dedicated bucket + policy declared, `terraform validate`/`plan` real; `lookup-events` attempted for real, failed on connection (no running LocalStack container in this environment) |
| 6 | AWS Config | Detective | Named | M7.4 | Not confirmed in LocalStack's free Hobby/Community tier |
| 7 | GuardDuty | Detective | Named | M7.4 | Not confirmed in LocalStack's free Hobby/Community tier |
| 8 | EventBridge rule + SNS (`andes-cargo-sensitive-iam-events`) | Detective | Implemented (resource) + Representative (trigger) | M7.7 | `aws_cloudwatch_event_rule` + `aws_cloudwatch_event_target` + `aws_sns_topic` declared, `terraform validate`/`plan` real; the live trigger against a real `AttachRolePolicy`/`CreateAccessKey` call attempted for real, failed on connection (same cause as row 5) |

## Reading the matrix

**Type (Preventive/Detective), 4 and 4.** Rows 1-4 could stop a bad action before it happens, at
different scopes: a single role (row 1), a single account (row 2), or a whole account hierarchy
(rows 3-4). Rows 5-8 never stop anything — they exist to leave or surface evidence after an action
already happened, at different levels of automation: a queryable history (row 5), continuous
config-state evaluation (row 6), ML-driven threat correlation (row 7), or a near-real-time push
notification (row 8).

**Build status, three distinct causes, never merged into one label.** Rows 1 and 2 share a cause:
`IAM Policy Enforcement` is a LocalStack Base/Ultimate feature, confirmed absent from the Hobby plan
this entire ecosystem uses. Rows 5 and 8 share a *different* cause: the resource itself is real HCL,
validated with the real Terraform engine, and the live confirmation was **attempted for real** in
this writing environment — it failed with `Could not connect to the endpoint URL`, the same
no-token limitation present since Module 1, unrelated to any paid LocalStack feature. Rows 3, 4, 6,
and 7 share a third cause each with its own reasoning: rows 3-4 are an ecosystem-level scope decision
(Andes Cargo is one account), rows 6-7 are an unconfirmed-coverage decision (no evidence LocalStack
Hobby includes them, unlike CloudTrail's demonstrated coverage).

## Consequences

`RISK-MAP.md` closes its final open row (`TM-03`, via row 5 of this matrix) as part of this module.
No row of this matrix is itself a new `TM-` finding — rows 1 and 8 harden `TM-07` (already `Resolved`
in M2.7) and add net-new detective coverage that `THREAT-MODEL.md` never named as a numbered
finding, the same pattern Module 1's `RISK-MAP.md` already established for Module 5 (community-rule
scanning: coverage, not resolution of a named risk).

## Alternatives considered

**Collapsing compound statuses (rows 1, 5, 8) into a single "Representative" label for simplicity.**
Rejected: it would erase the real, verifiable work this module actually did — three `terraform
validate`/`plan` runs against real HCL, not descriptions of HCL. A reader auditing this portfolio
should be able to tell, from this table alone, that the Terraform layer of these three controls is
real and the live-AWS-behavior layer is not, without reading three separate lessons to recover that
distinction.

**Treating AWS Control Tower as a fifth category alongside preventive/detective (mirroring its own
"preventive/detective/proactive" vocabulary).** Considered, rejected for this matrix specifically:
Control Tower is an orchestration layer over Organizations and SCPs, not a mechanism of its own — this
matrix classifies it by the family it belongs to in this module (multi-account governance, M7.6,
preventive), while lesson 6 documents its own three-way vocabulary in full for readers who need that
precision.

Paso 3 — Verificando el documento

Igual que THREAT-MODEL.md y RISK-MAP.md, este documento se lee, no se corre — pero su forma se puede confirmar con los mismos comandos simples de siempre, deterministas porque el contenido lo escribiste tú:

grep -cE '^\| [0-9] \|' GUARDRAILS-MAP.md
grep -E '^\| [0-9] \|' GUARDRAILS-MAP.md | grep -c 'Preventive'
grep -E '^\| [0-9] \|' GUARDRAILS-MAP.md | grep -c 'Detective'
grep -E '^\| [0-9] \|' GUARDRAILS-MAP.md | grep -c 'Implemented'
grep -E '^\| [0-9] \|' GUARDRAILS-MAP.md | grep -c 'Named'

Qué esperar (literal — el contenido de este documento lo escribiste tú, así que su forma es determinista, ejecutado para escribir esta lección):

8
4
4
3
5

Ocho filas totales — ni una de las ocho piezas del Paso 1 falta, ninguna se cuenta dos veces. Cuatro preventivas, cuatro detectivas — el eje de tipo queda perfectamente balanceado, sin que eso haya sido un objetivo de diseño, solo el resultado honesto de contar las piezas reales de este módulo. Tres con estado compuesto ("Implemented" aparece en su etiqueta, junto con "Representative" en la misma celda) — exactamente las tres lecciones de "manos a la obra" de este módulo (3, 5, 7), cada una con HCL real validado. Cinco puramente nombradas — SCP, Organizations, Control Tower, Config y GuardDuty, ninguna con un solo recurso Terraform declarado en ningún lugar de este módulo.


Por qué esta tabla, y no una lista de prosa

Una lista de ocho párrafos, cada uno describiendo un mecanismo, contendría exactamente la misma información — pero perdería la propiedad que hace a esta tabla valiosa como entregable de portfolio: la capacidad de contar de un vistazo. Un entrevistador técnico, o tú mismo dentro de seis meses, puede confirmar en segundos que el balance es 4 preventivo/4 detectivo, o que 3 de 8 tienen HCL real, sin leer un solo párrafo — la misma ventaja que ya viste en RISK-MAP.md (Módulo 1, lección 8), donde la tabla numerada permitió verificar con grep que ningún riesgo de THREAT-MODEL.md quedaba fuera del mapa.


Errores comunes

Fusionar las cinco filas "Named" en una sola fila genérica de "controles no construidos" (de simplificación excesiva). Qué pasa: alguien, revisando este documento, sugiere colapsar SCP, Organizations, Control Tower, Config y GuardDuty en una sola fila que diga "varios controles nombrados, no construidos". Cómo detectarlo: si tu versión del documento tiene menos de ocho filas numeradas. Cómo corregirlo: cada una de las cinco tiene una razón distinta de por qué queda nombrada —dos comparten la dependencia de IAM Policy Enforcement (fila 2, junto con la fila 1), dos comparten una decisión de alcance de ecosistema (filas 3-4), dos comparten una falta de cobertura confirmada en LocalStack (filas 6-7)— y fusionarlas escondería esas distinciones exactas que las lecciones 2, 4 y 6 de este módulo se tomaron el trabajo de explicar con evidencia separada.

Clasificar el permission boundary o el trail de CloudTrail como "Representative" sin más, ignorando que el recurso Terraform es real (de subestimación). Qué pasa: alguien, apurado, marca las filas 1, 5 y 8 como puramente "Representative", igual que las filas puramente nombradas. Cómo detectarlo: si tu tabla no distingue, en ninguna celda, entre "nunca se declaró ningún HCL" (SCP, Organizations, Config, GuardDuty) y "el HCL es real, lo que falta es la confirmación en vivo" (el boundary, el trail, la alarma). Cómo corregirlo: la columna Build status de este documento usa, a propósito, la etiqueta compuesta "Implemented (resource) + Representative (...)" exactamente para esas tres filas — perder esa distinción subestima el trabajo real de las lecciones 3, 5 y 7, que corrieron terraform validate/plan de verdad, algo que ninguna de las cinco filas nombradas hizo.

Tratar este documento como si resolviera una fila nueva de RISK-MAP.md (de expectativa, ya aclarado en la sección "Consequences" del documento). Qué pasa: alguien, al terminar este proyecto, busca agregar una octava fila a RISK-MAP.md para este mapa. Cómo detectarlo: si tu RISK-MAP.md, después de este módulo, tiene más de siete filas. Cómo corregirlo: RISK-MAP.md ya tenía sus siete filas completas desde el Módulo 1 — este módulo cierra la última que seguía abierta (TM-03, vía la fila 5 de esta matriz), pero no agrega ninguna fila nueva. Las filas 1 y 8 de este mapa son endurecimiento de un riesgo ya resuelto (TM-07) y cobertura detectiva nueva que nunca fue un hallazgo numerado de THREAT-MODEL.md — exactamente el mismo patrón que RISK-MAP.md ya documentó para el Módulo 5, que tampoco cierra ninguna fila propia.


Ejercicios

Ejercicio 1 — Reproduce el conteo de memoria. Sin volver a mirar el Paso 3, escribe de memoria los cinco números que producirían los cinco comandos grep de esta lección, y explica en una frase por qué el segundo y el tercero suman exactamente ocho.

Ver solución

Los cinco números son 8, 4, 4, 3, 5 — total de filas, preventivas, detectivas, con estado compuesto ("Implemented"), y puramente nombradas, en ese orden. El segundo (4, preventivas) y el tercero (4, detectivas) suman exactamente ocho porque Type es un eje exhaustivo y mutuamente excluyente sobre las mismas ocho filas — cada guardrail de este módulo es, sin excepción, uno de los dos tipos, nunca ninguno o ambos a la vez, así que preventivas más detectivas siempre tiene que igualar el total de filas.

Ejercicio 2 — Explica por qué el cuarto y el quinto número (3 y 5) también suman ocho, con un argumento distinto al del Ejercicio 1. El cuarto comando cuenta filas con "Implemented" en su celda, el quinto cuenta filas con "Named". ¿Por qué también suman exactamente ocho, y no menos (si alguna fila no tuviera ninguna de las dos palabras) ni más (si alguna tuviera ambas)?

Ver solución

Porque Build status, igual que Type, es también un eje exhaustivo y mutuamente excluyente sobre las mismas ocho filas: cada una de las ocho piezas de este módulo, según el diseño de esta guía, es o bien puramente "Named" (nunca se declaró HCL) o bien tiene la etiqueta compuesta que incluye "Implemented" (el recurso es real) — no existe, en este módulo, ninguna pieza que no encaje en ninguna de las dos categorías (por ejemplo, algo puramente "Representative" sin ningún HCL real detrás, que sería una tercera categoría distinta), ni ninguna pieza que sea simultáneamente "Named" y "Implemented" a la vez, porque esas dos palabras describen estados que se excluyen mutuamente por diseño de esta tabla.

Ejercicio 3 — Defiende, frente a un entrevistador técnico, por qué solo 3 de 8 controles de este módulo tienen HCL real. Un entrevistador, revisando este documento como pieza de portfolio, pregunta: "¿por qué la mayoría de los controles de tu módulo de seguridad más avanzado están solo 'nombrados', no construidos?". Responde con el criterio de honestidad de esta guía, sin sonar defensivo.

Ver solución

Una respuesta sólida distingue honestidad técnica de falta de esfuerzo: "Este documento es, precisamente, la prueba de que entiendo la diferencia entre lo que un laboratorio $0 puede demostrar y lo que solo puede declararse en conocimiento — cinco de esos ocho controles dependen de funcionalidades de pago de LocalStack (IAM Policy Enforcement), de una estructura que este caso de estudio de una sola cuenta nunca tuvo razón de construir (Organizations, Control Tower), o de servicios cuya cobertura en el plan gratuito nunca estuvo confirmada (Config, GuardDuty) — y cada una de esas tres razones está documentada con su propia fuente citada, no agrupada en un genérico 'no se pudo'. Los tres que sí tienen HCL real —el permission boundary, el trail de CloudTrail, la alarma de EventBridge— corrieron terraform validate y terraform plan de verdad, contra el motor real de Terraform, algo que puedo mostrar en cualquier momento. Prefiero un documento que distinga con precisión estas tres causas a uno que presente los ocho como igualmente construidos, y que alguien descubra la diferencia recién en producción."


Resumen y siguiente paso

En este proyecto final del módulo escribiste GUARDRAILS-MAP.md, la matriz que clasifica, sin ninguna ambigüedad, las ocho piezas de guardrail de este módulo en dos ejes independientes: tipo (4 preventivas, 4 detectivas) y grado de construcción (3 con HCL real validado, 5 puramente nombradas, cada una con su propia razón citada). Verificaste la forma del documento con grep, exactamente como ya hiciste con THREAT-MODEL.md y RISK-MAP.md en el Módulo 1, y confirmaste que RISK-MAP.md cierra, con este módulo, su última fila abierta (TM-03).

Antes de cerrar este módulo deberías poder: recitar las ocho piezas de esta matriz, con su tipo y su estado de construcción, sin mirar el documento; explicar las tres causas distintas de por qué cinco de las ocho quedan nombradas, sin agruparlas en una sola; y defender, frente a una pregunta directa, por qué la mayoría de los controles de este módulo no tienen HCL real, sin que eso suene a una debilidad escondida.

Con esto, el Módulo 7 de cloud-security-and-guardrails-guide queda completo: preventivo y detectivo, distinguidos con precisión y aplicados a AppServerRole, a CloudTrail, y a un evento sensible de IAM; SCPs, Organizations, Control Tower, Config y GuardDuty, nombrados con la misma honestidad que el resto de esta guía. RISK-MAP.md cierra sus siete filas. El Módulo 8 —el capstone— encadena conftest, Trivy y cosign en un security gate real dentro del pipeline heredado, la prueba final de que un cambio malo no puede cruzarlo.

Recursos

  1. Este curso, Módulo 1, lecciones 7 y 8 — THREAT-MODEL.md y RISK-MAP.md, la misma disciplina de documento-verificado-con-grep que este proyecto aplica.
  2. Este módulo, lecciones 2 a 7 — la fuente completa de cada una de las ocho filas de esta matriz, con su evidencia y su fuente citada en el punto exacto donde aparece.
  3. src/paths/aws-cloud-ecosystem/VALIDACION.md — la fuente del faltante ALTA (Organizations/Control Tower/Landing Zone) citada en las filas 3 y 4 de esta matriz.
  4. AWS Prescriptive Guidance — Architecture decision records — el formato ADR (Context, Decision/Matrix, Consequences, Alternatives considered) que este documento sigue, ya citado en RISK-MAP.md.