Audita IAM Users, Access Keys y cuentas root en toda una AWS Organization multicuenta — en minutos, con mínimo privilegio. Dashboard HTML interactivo incluido. Modo programado vía ECS Fargate con notificación a Slack.
iam-audit recorre automáticamente todas las cuentas activas de tu AWS Organization, asume un rol de auditoría en cada una, y produce un reporte consolidado con:
- ✅ Todas las Access Keys por usuario — estado, fecha de creación, último uso y servicio
- ✅ Estado de MFA por usuario (Virtual, Hardware, o ausente)
- ✅ Acceso a consola — login profile, último uso, rotación de password
- ✅ Root account por cuenta — MFA habilitado, access keys activas, último login vía CloudTrail
- ✅ Risk scoring por usuario — priorización automática de hallazgos
- ✅ Tendencia de remediación vía CloudTrail — tracking de acciones correctivas en el tiempo
- ✅ Dashboard HTML interactivo con filtros globales, gráficos y tabla de hallazgos
Todo sin crear credenciales de largo plazo adicionales. El script usa sts:AssumeRole — credenciales temporales que expiran solas.
En entornos multicuenta, nadie tiene una vista consolidada de credenciales. Las Access Keys viejas no aparecen en ningún dashboard por defecto. No generan alertas. No molestan a nadie. Simplemente esperan.
Este script las encuentra.
Probado en una AWS Organization real con más de 20 cuentas activas. Encontré una Access Key creada en 2018 — activa en producción. Lee el post completo → roadtocloudsec.hashnode.dev
| Local | Fargate | |
|---|---|---|
| Cómo corre | Docker en tu máquina | ECS Fargate — automático |
| Trigger | Manual | EventBridge — lunes 9am |
| Output | ./output/ local + localhost:8000 |
S3 + Slack con presigned URL |
| Infraestructura | Ninguna | Terraform en cuenta Security |
| Ideal para | Auditorías puntuales | Monitoreo continuo semanal |
- Docker instalado
- AWS credentials configuradas localmente (
~/.aws) - Un rol de auditoría desplegado en cada cuenta miembro con permisos de lectura sobre IAM y CloudTrail
- Un rol en la cuenta Management con
organizations:ListAccountsysts:AssumeRolehacia las cuentas hijas
Todo lo anterior, más:
- Terraform >= 1.14.0
- AWS CLI con acceso admin a tu cuenta Security
- Una Slack webhook URL — cómo crearla
- Un bucket S3 para el Terraform state (se crea manualmente una sola vez — ver instrucciones abajo)
- El rol
iam-audit-org-readeren la cuenta Management (se crea manualmente una sola vez — ver instrucciones abajo)
Sin clonar el repo, sin instalar Python, sin instalar dependencias.
Supply chain hardening: Para máxima reproducibilidad, usá el digest en vez del tag
latest:docker pull gerardokaztro/iam-audit@sha256:2876b73349c6a8c4847d4478a8e1f65971a6e1a3978320a2817e0ee3426e4ea4
docker run --rm \
-v ~/.aws:/root/.aws \
-v $(pwd)/output:/app/output \
-p 8000:8000 \
gerardokaztro/iam-audit \
--profile YOUR-AWS-PROFILE \
--role YOUR-AUDIT-ROLEImagen disponible en: hub.docker.com/r/gerardokaztro/iam-audit
git clone https://github.com/gerardokaztro/iam-audit
cd iam-audit
docker buildx build \
--platform linux/amd64,linux/arm64 \
-t iam-audit .
docker run --rm \
-v ~/.aws:/root/.aws \
-v $(pwd)/output:/app/output \
-p 8000:8000 \
iam-audit \
--profile YOUR-AWS-PROFILE \
--role YOUR-AUDIT-ROLENota: Si buildeas en Mac con Apple Silicon (arm64) y querés correr en Fargate, usá
--platform linux/amd64,linux/arm64para generar una imagen multi-platform.
pip install -r requirements.txt
python src/iam_audit.py --profile YOUR-AWS-PROFILE --role YOUR-AUDIT-ROLECuando el scan termina, abrí el browser en http://localhost:8000 para ver el dashboard interactivo.
Presioná Ctrl+C para detener el servidor.
# Reemplazá ACCOUNT-ID con el ID de tu cuenta Security
aws s3api create-bucket \
--bucket iam-audit-tfstate-ACCOUNT-ID \
--profile YOUR-SECURITY-PROFILE
aws s3api put-bucket-versioning \
--bucket iam-audit-tfstate-ACCOUNT-ID \
--versioning-configuration Status=Enabled \
--profile YOUR-SECURITY-PROFILE
aws s3api put-public-access-block \
--bucket iam-audit-tfstate-ACCOUNT-ID \
--public-access-block-configuration "BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true" \
--profile YOUR-SECURITY-PROFILENota: Si tu cuenta Security está en
us-east-1, omitir--regiones correcto — la API de S3 tiene un comportamiento particular con esa región.
Este rol permite que el Task Role en la cuenta Security liste las cuentas de la organización y luego asuma el rol de auditoría en cada cuenta hija.
# Reemplazá SECURITY-ACCOUNT-ID con el ID de tu cuenta Security
aws iam create-role \
--role-name iam-audit-org-reader \
--assume-role-policy-document '{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::SECURITY-ACCOUNT-ID:role/iam-audit-task-role"
},
"Action": "sts:AssumeRole"
}]
}' \
--profile YOUR-MANAGEMENT-PROFILE
aws iam attach-role-policy \
--role-name iam-audit-org-reader \
--policy-arn arn:aws:iam::aws:policy/AWSOrganizationsReadOnlyAccess \
--profile YOUR-MANAGEMENT-PROFILE
aws iam put-role-policy \
--role-name iam-audit-org-reader \
--policy-name assume-member-accounts \
--policy-document '{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": "sts:AssumeRole",
"Resource": "arn:aws:iam::*:role/YOUR-AUDIT-ROLE-NAME"
}]
}' \
--profile YOUR-MANAGEMENT-PROFILEcd infra
cp backend.hcl.example backend.hcl
cp terraform.tfvars.example terraform.tfvarsEditá backend.hcl con los datos de tu bucket de state:
bucket = "iam-audit-tfstate-ACCOUNT-ID"
key = "iam-audit/terraform.tfstate"
region = "us-east-1"
profile = "YOUR-SECURITY-PROFILE"Editá terraform.tfvars con tus valores:
aws_region = "us-east-1"
aws_profile = "YOUR-SECURITY-PROFILE"
environment = "production"
tfstate_bucket = "iam-audit-tfstate-ACCOUNT-ID"
reports_bucket_name = "iam-audit-reports-ACCOUNT-ID"
management_account_id = "YOUR-MANAGEMENT-ACCOUNT-ID"
audit_role_name = "YOUR-AUDIT-ROLE-NAME"
slack_webhook_url = "https://hooks.slack.com/services/XXX/YYY/ZZZ"Desplegá:
terraform init -backend-config=backend.hcl
terraform plan
terraform applyLa auditoría correrá automáticamente cada lunes a las 9am (Lima, UTC-5). Para disparar manualmente:
aws ecs run-task \
--cluster iam-audit-cluster \
--task-definition iam-audit \
--launch-type FARGATE \
--network-configuration "awsvpcConfiguration={subnets=[SUBNET-ID],securityGroups=[SG-ID],assignPublicIp=ENABLED}" \
--profile YOUR-SECURITY-PROFILEEl rol en cada cuenta hija necesita permisos de lectura sobre IAM y CloudTrail:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"iam:ListUsers",
"iam:ListAccessKeys",
"iam:GetAccessKeyLastUsed",
"iam:ListMFADevices",
"iam:GetLoginProfile",
"iam:GetAccountSummary",
"cloudtrail:LookupEvents"
],
"Resource": "*"
}
]
}Si usás AWS Control Tower, el rol
AWSControlTowerExecutionya existe en todas las cuentas y podés usarlo como punto de partida. Verificá que su trust policy permita ser asumido desde tu cuenta Management — que es desde dondeiam-audit-org-readerasume roles en las cuentas hijas.
Modo Local — archivos guardados en ./output/:
| Archivo | Contenido |
|---|---|
iam_audit_report_TIMESTAMP.html |
Dashboard interactivo con filtros, gráficos y tabla de hallazgos |
iam_audit_report_TIMESTAMP.csv |
Hallazgos de IAM por usuario y cuenta |
root_audit_report_TIMESTAMP.csv |
Estado de root account por cuenta |
cloudtrail_events_TIMESTAMP.csv |
Eventos IAM de CloudTrail para tracking de remediación |
Modo Fargate — archivos subidos a S3:
s3://iam-audit-reports-ACCOUNT-ID/
└── reports/
└── YYYY-MM-DD/
├── iam_audit_report_TIMESTAMP.html
├── iam_audit_report_TIMESTAMP.csv
├── root_audit_report_TIMESTAMP.csv
└── cloudtrail_events_TIMESTAMP.csv
Los reportes se eliminan automáticamente a los 90 días (lifecycle policy). La presigned URL del dashboard HTML es válida por 48 horas y se entrega vía Slack.
Toda la infraestructura vive en la cuenta Security y se gestiona con Terraform:
| Recurso | Propósito |
|---|---|
| ECS Fargate Task | Corre el script de auditoría |
| EventBridge Scheduler | Dispara la task cada lunes 9am Lima |
| S3 Bucket | Almacena los reportes (lifecycle 90 días) |
| Secrets Manager | Guarda el Slack webhook URL cifrado |
| IAM Task Role | Permisos para asumir roles y escribir en S3 |
| IAM Execution Role | Permisos para arrancar el contenedor y leer secrets |
| CloudWatch Log Group | Logs de la task — retención 30 días |
| Security Group | Egress only — el contenedor no expone puertos |
Este script ayuda a avanzar en controles específicos del AWS Security Maturity Model v2:
| Fase | Control | Cómo ayuda este script |
|---|---|---|
| Phase 1 — Quick Wins | Multi-Factor Authentication | Identifica usuarios y cuentas root sin MFA |
| Phase 2 — Foundational | Use Temporary Credentials | Expone usuarios con Access Keys de largo plazo activas |
| Phase 2 — Foundational | Protect Root Credentials | Detecta root sin MFA, con AK activas y con login reciente |
- CloudTrail se consulta en
us-east-1por defecto — IAM es global pero los eventos son regionales - Cuentas donde el rol de auditoría no esté desplegado serán omitidas con un error en consola — eso en sí mismo es un hallazgo
lookup_eventsretorna eventos de los últimos 90 días por limitación de la API de CloudTrail- La cuenta Management es omitida del audit de cuentas hijas si no tiene el rol de auditoría desplegado — comportamiento esperado
- Auditoría de IAM Users y Access Keys multicuenta
- Risk scoring por usuario
- Dashboard HTML interactivo con filtros globales
- Tendencia de remediación vía CloudTrail
- Detección de root account — MFA, Access Keys, último login
- Dockerización — imagen disponible en Docker Hub
- Notificaciones Slack con card resumen y presigned URL
- Ejecución programada vía ECS Fargate + EventBridge
- Infraestructura como código con Terraform
- Security pipeline con GitHub Actions — secrets, SAST, image scanning, IaC scanning
- Supply chain hardening — digest pinning + egress blocking con Harden-Runner
iam-audit/
├── src/
│ ├── iam_audit.py # Script principal
│ └── template.html # Template del dashboard HTML
├── infra/
│ ├── main.tf # Orquestador — llama a todos los módulos
│ ├── variables.tf # Variables globales
│ ├── outputs.tf # Outputs globales
│ ├── backend.hcl.example # Template de configuración del backend
│ ├── terraform.tfvars.example # Template de variables
│ └── modules/
│ ├── s3/ # Bucket de reportes
│ ├── iam/ # Task Role + Execution Role
│ ├── ecs/ # Cluster + Task Definition
│ ├── secrets/ # Secrets Manager
│ └── eventbridge/ # Scheduler semanal
├── examples/
│ └── iam_audit_report_example.csv
├── output/ # Generado en tiempo de ejecución — en .gitignore
├── Dockerfile
├── requirements.txt
└── README.md
Gerardo Castro — AWS Security Hero · Cloud Security Engineer
Construyo herramientas de seguridad para entornos AWS reales en LATAM. Este script nació de una necesidad concreta en campo — como la mayoría de las herramientas que vale la pena usar.
🔗 LinkedIn 🔗 Blog 📝 Post completo con contexto y hallazgos
MIT — usalo, modificalo, compartilo. Si encontrás algo interesante con él, contame.