CloudTrail cerró la lección anterior con una limitación concreta y honesta: detecta eventos, no
estados. Sabe que alguien llamó a DeleteBucketEncryption esta mañana. No sabe que
mercadofresco-registros-web lleva cinco meses sin cifrado porque nunca se configuró. No sabe que hay
un grupo de seguridad con 0.0.0.0/0 en el puerto 22 desde una prueba de marzo que nadie deshizo. No
puede decir cuántos recursos de la cuenta incumplen el etiquetado obligatorio de MercadoFresco. Y
desde luego no puede arreglar nada.
Esta es la quinta y última pregunta que dejó abierta el módulo 4: nada avisa si alguien desactiva el cifrado de un bucket o abre un grupo de seguridad al mundo.
AWS Config es la respuesta. Su idea es distinta a todo lo que hemos visto en este módulo: en lugar de registrar lo que pasa, fotografía continuamente cómo está configurado cada recurso, guarda el histórico de esas fotografías, y evalúa cada una contra un conjunto de reglas. Cuando una configuración se desvía de lo que debería ser, Config lo marca como no conforme, avisa, y —si se lo permites— lo corrige solo.
Es la diferencia entre una cámara de seguridad que graba a quien entra y un inventario que, cada noche, comprueba que todas las puertas están cerradas.
Aviso de coste. AWS Config es, junto con la ingesta de registros de CloudWatch, el servicio de este módulo que más fácilmente se dispara. Cobra por cada elemento de configuración registrado y por cada evaluación de regla. Una cuenta con un grupo de autoescalado que rota instancias todo el día puede generar decenas de miles de elementos al mes. Hay una sección entera con el cálculo y las cinco formas de controlarlo.
Aviso de cumplimiento. Este material es didáctico. Los paquetes de conformidad que se mencionan (CIS, PCI DSS) son ayudas, no certificaciones: activar un conformance pack no te hace conforme con PCI DSS. Cualquier uso de Config con finalidad de cumplimiento normativo real debe validarlo un profesional de cumplimiento.
Contenido
- Qué es la configuración de un recurso
- El elemento de configuración y la línea temporal
- Config frente a CloudTrail
- El flujo completo: grabador, reglas y remediación
- Activar el grabador de configuración
- El canal de entrega: S3 y SNS
- Elegir qué tipos de recurso se graban
- Reglas: el corazón del servicio
- Las reglas gestionadas que activa MercadoFresco
- Disparo por cambio frente a disparo periódico
- El etiquetado obligatorio con
required-tags - Reglas personalizadas con Lambda
- Reglas personalizadas con Guard
- Remediación automática con Systems Manager
- Los dos casos reales: cifrado de bucket y SG abierto
- La advertencia de probar primero en modo manual
- Paquetes de conformidad
- El cuadro de cumplimiento de MercadoFresco
- Consultas avanzadas para inventariar
- Agregadores multicuenta
- Security Hub y GuardDuty: dónde encaja cada uno
- Coste real y cómo no dispararlo
- Limpieza
Qué es la configuración de un recurso
La configuración de un recurso es el conjunto de propiedades que lo definen en un momento dado. No sus datos, no su tráfico: sus ajustes.
| Recurso | Su configuración incluye… |
|---|---|
mercadofresco-catalogo-fotos (S3) |
Cifrado, versionado, política, bloqueo público, ciclo de vida, etiquetas |
sg-mercadofresco-basedatos |
Reglas de entrada y salida, VPC, descripción, etiquetas |
mercadofresco-pedidos (RDS) |
Clase, Multi-AZ, cifrado, retención de copias, versión del motor, ventana |
rol-mercadofresco-tienda (IAM) |
Políticas adjuntas, política de confianza, límite de permisos |
mercadofresco-tienda-01 (EC2) |
Tipo, AMI, subred, SG, rol de instancia, volúmenes, etiquetas |
alb-mercadofresco-tienda |
Escuchadores, certificados, esquema, grupos de destino |
Config es capaz de grabar la configuración de más de 250 tipos de recurso de decenas de servicios, y de mantener el histórico completo de sus cambios.
El elemento de configuración y la línea temporal
Un elemento de configuración (CI, configuration item) es una fotografía completa de un recurso en un instante concreto. Config crea uno cada vez que detecta un cambio.
{
"configurationItemVersion": "1.3",
"configurationItemCaptureTime": "2026-07-28T09:14:33.412Z",
"configurationItemStatus": "OK",
"configurationStateId": "1785142473412",
"awsAccountId": "111122223333",
"resourceType": "AWS::S3::Bucket",
"resourceId": "mercadofresco-registros-web",
"resourceName": "mercadofresco-registros-web",
"ARN": "arn:aws:s3:::mercadofresco-registros-web",
"awsRegion": "eu-west-1",
"resourceCreationTime": "2026-03-02T11:20:01.000Z",
"tags": {
"Proyecto": "mercadofresco",
"Entorno": "produccion",
"Componente": "registros",
"Propietario": "marta",
"CentroCoste": "plataforma"
},
"relationships": [
{ "resourceType": "AWS::KMS::Key",
"resourceId": "8f2c1a9b-4d3e-4f6a-9c1b-2e5d7a8f3c04",
"relationshipName": "Is encrypted with" }
],
"configuration": {
"name": "mercadofresco-registros-web",
"bucketVersioningConfiguration": { "status": "Enabled" },
"publicAccessBlockConfiguration": {
"blockPublicAcls": true, "ignorePublicAcls": true,
"blockPublicPolicy": true, "restrictPublicBuckets": true
},
"serverSideEncryptionConfiguration": {
"rules": [{
"applyServerSideEncryptionByDefault": {
"sseAlgorithm": "aws:kms",
"kmsMasterKeyID": "arn:aws:kms:eu-west-1:111122223333:alias/mercadofresco-datos"
}
}]
}
},
"supplementaryConfiguration": {
"BucketPolicy": { "policyText": "{...}" },
"IsRequesterPaysEnabled": false
}
}Tres campos merecen atención especial:
relationships: Config no guarda recursos aislados, guarda el grafo. Sabe que este bucket está cifrado con esa clave KMS, que esa instancia está en esa subred, que ese SG está asociado a ese ALB. Cuando quieras saber qué se rompe si borras una clave, esto es lo que responde.supplementaryConfiguration: propiedades que no vienen en la llamadaDescribeprincipal y que Config recoge aparte. La política del bucket, por ejemplo.configurationItemStatus:OK,ResourceDiscovered,ResourceDeleted. Sí: Config graba también el borrado, así que puedes ver cómo estaba configurado un recurso que ya no existe.
La línea temporal (configuration timeline) es la secuencia de todos los CI de un recurso. Es la vista más potente de la consola de Config y responde a la pregunta que en operaciones se hace cada semana: «esto funcionaba el viernes; ¿qué ha cambiado?»
timeline
title Linea temporal de sg-mercadofresco-basedatos
2026-03-02 : Creado con regla 5432 desde sg-mercadofresco-tienda : CONFORME
2026-05-14 : Anadida regla 22 desde 0.0.0.0-0 : NO CONFORME
2026-05-14 : Remediacion automatica revoca la regla : CONFORME
2026-07-09 : Anadida regla 5432 desde sg-mercadofresco-admin : CONFORME
Y la consulta:
aws configservice get-resource-config-history \
--resource-type AWS::EC2::SecurityGroup \
--resource-id sg-0a1b2c3d4e5f6a7b8 \
--limit 10 \
--query 'configurationItems[].[configurationItemCaptureTime,configurationItemStatus]' \
--output table \
--profile mercadofresco-dev --region eu-west-1Config frente a CloudTrail
Son complementarios y se confunden constantemente. La tabla:
| CloudTrail (05-03) | AWS Config | |
|---|---|---|
| Graba | La llamada a la API | El estado del recurso |
| Pregunta | ¿Quién hizo qué, cuándo, desde dónde? | ¿Cómo está configurado ahora? ¿Cumple? |
| Unidad | Evento | Elemento de configuración |
| Ve el estado previo | No | Sí: antes y después |
| Detecta desviaciones existentes | No | Sí |
| Evalúa contra reglas | No | Sí |
| Corrige | No | Sí (remediación) |
| Ve cambios hechos fuera de la API | No | Sí, en la evaluación periódica |
| Coste | Casi cero | Por CI y por evaluación |
| Primera copia gratis | Sí | No |
El ejemplo que lo fija. Alguien añade una regla SSH abierta a sg-mercadofresco-basedatos:
- CloudTrail: un evento
AuthorizeSecurityGroupIngress, con la identidad, la hora y la IP de quien lo hizo. No dice cómo quedó el grupo de seguridad, solo qué se pidió. - Config: un CI nuevo con la configuración completa del grupo después del cambio, la
comparación con el CI anterior (el diff), la evaluación de la regla
restricted-sshmarcándolo comoNON_COMPLIANT, y —si está configurada— la remediación que revoca la regla.
Se usan juntos, y de hecho Config depende de CloudTrail: usa sus eventos para saber que algo ha cambiado y disparar la grabación de un CI nuevo.
El flujo completo: grabador, reglas y remediación
flowchart TD
R["Recurso de AWS<br/>bucket, SG, RDS, rol..."] -->|"cambio"| CT["CloudTrail<br/>detecta la llamada"]
CT --> G["GRABADOR DE CONFIGURACION<br/>crea un elemento de configuracion"]
G --> H["Historial en S3<br/>mercadofresco-config-historial"]
G --> E["Motor de REGLAS"]
E -->|"COMPLIANT"| OK["Nada que hacer"]
E -->|"NON_COMPLIANT"| N["Marcado como no conforme"]
N --> SNS["Notificacion SNS<br/>alertas-mercadofresco"]
N --> EB["Evento de EventBridge<br/>07-03"]
N --> REM["REMEDIACION<br/>documento de SSM Automation"]
REM -->|"corrige"| R
G --> SH["Security Hub<br/>agregacion de hallazgos"]
Cinco piezas y su orden:
- Grabador de configuración (configuration recorder): el proceso que crea los CI. Solo puede haber uno por cuenta y región.
- Canal de entrega (delivery channel): dónde se depositan los CI (un bucket de S3) y a dónde se notifican los cambios (un tema de SNS). También uno por cuenta y región.
- Reglas: las condiciones que se evalúan.
- Remediación: la acción correctiva, ejecutada por Systems Manager Automation.
- Agregador: la vista consolidada de varias cuentas y regiones (09-04).
Activar el grabador de configuración
Paso 1: el bucket del historial.
aws s3api create-bucket \
--bucket mercadofresco-config-historial \
--region eu-west-1 \
--create-bucket-configuration LocationConstraint=eu-west-1 \
--profile mercadofresco-dev
aws s3api put-public-access-block \
--bucket mercadofresco-config-historial \
--public-access-block-configuration \
"BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true" \
--profile mercadofresco-dev
aws s3api put-bucket-encryption \
--bucket mercadofresco-config-historial \
--server-side-encryption-configuration '{
"Rules":[{"ApplyServerSideEncryptionByDefault":{"SSEAlgorithm":"AES256"},
"BucketKeyEnabled":true}]}' \
--profile mercadofresco-devCon su política:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ConfigComprobarAcl",
"Effect": "Allow",
"Principal": { "Service": "config.amazonaws.com" },
"Action": ["s3:GetBucketAcl", "s3:ListBucket"],
"Resource": "arn:aws:s3:::mercadofresco-config-historial",
"Condition": {
"StringEquals": { "AWS:SourceAccount": "111122223333" }
}
},
{
"Sid": "ConfigEscribir",
"Effect": "Allow",
"Principal": { "Service": "config.amazonaws.com" },
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::mercadofresco-config-historial/AWSLogs/111122223333/Config/*",
"Condition": {
"StringEquals": {
"s3:x-amz-acl": "bucket-owner-full-control",
"AWS:SourceAccount": "111122223333"
}
}
}
]
}Paso 2: el rol de servicio. Config necesita permiso para leer la configuración de todo:
aws iam create-role \
--role-name rol-aws-config-mercadofresco \
--assume-role-policy-document '{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": { "Service": "config.amazonaws.com" },
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": { "AWS:SourceAccount": "111122223333" }
}
}]
}' \
--profile mercadofresco-dev
aws iam attach-role-policy \
--role-name rol-aws-config-mercadofresco \
--policy-arn arn:aws:iam::aws:policy/service-role/AWS_ConfigRole \
--profile mercadofresco-devLa política gestionada AWS_ConfigRole concede solo lectura sobre decenas de servicios. Config
no necesita escribir nada: la remediación la ejecuta Systems Manager con otro rol distinto, que es
donde estarán los permisos peligrosos. Esa separación es deliberada y buena.
Paso 3: el grabador.
aws configservice put-configuration-recorder \
--configuration-recorder '{
"name": "grabador-mercadofresco",
"roleARN": "arn:aws:iam::111122223333:role/rol-aws-config-mercadofresco",
"recordingGroup": {
"allSupported": false,
"includeGlobalResourceTypes": false,
"resourceTypes": [
"AWS::S3::Bucket",
"AWS::EC2::SecurityGroup",
"AWS::EC2::Instance",
"AWS::EC2::Volume",
"AWS::EC2::NetworkAcl",
"AWS::EC2::VPC",
"AWS::EC2::Subnet",
"AWS::RDS::DBInstance",
"AWS::RDS::DBSnapshot",
"AWS::Lambda::Function",
"AWS::IAM::Role",
"AWS::IAM::Policy",
"AWS::IAM::User",
"AWS::KMS::Key",
"AWS::SecretsManager::Secret",
"AWS::ElasticLoadBalancingV2::LoadBalancer",
"AWS::CloudFront::Distribution",
"AWS::CloudTrail::Trail",
"AWS::Logs::LogGroup",
"AWS::WAFv2::WebACL"
]
},
"recordingMode": {
"recordingFrequency": "CONTINUOUS",
"recordingModeOverrides": [
{
"description": "Las instancias del ASG rotan mucho: diario basta",
"resourceTypes": ["AWS::EC2::Instance", "AWS::EC2::Volume"],
"recordingFrequency": "DAILY"
}
]
}
}' \
--profile mercadofresco-dev --region eu-west-1Dos decisiones que aquí valen dinero:
allSupported: falsecon lista explícita. Grabar los 250+ tipos soportados en una cuenta viva genera un volumen de CI enorme, y muchos no aportan nada.recordingModeOverridesconDAILYpara instancias y volúmenes. Esta es la optimización más importante de toda la lección. El ASGasg-mercadofresco-tiendalanza y termina instancias en cada pico de los viernes; cada lanzamiento genera CI de la instancia, de sus volúmenes, de sus interfaces de red. En modo continuo son miles de CI al mes por un recurso efímero cuya configuración es siempre la misma —viene delt-mercadofresco-tienda—. En modo diario, uno.
Paso 4: el canal de entrega.
aws configservice put-delivery-channel \
--delivery-channel '{
"name": "canal-mercadofresco",
"s3BucketName": "mercadofresco-config-historial",
"s3KeyPrefix": "config",
"snsTopicARN": "arn:aws:sns:eu-west-1:111122223333:alertas-mercadofresco",
"configSnapshotDeliveryProperties": {
"deliveryFrequency": "TwentyFour_Hours"
}
}' \
--profile mercadofresco-dev --region eu-west-1Paso 5: arrancarlo. Igual que CloudTrail, crear no es arrancar:
aws configservice start-configuration-recorder \
--configuration-recorder-name grabador-mercadofresco \
--profile mercadofresco-dev --region eu-west-1
# Comprobar
aws configservice describe-configuration-recorder-status \
--query 'ConfigurationRecordersStatus[].[name,recording,lastStatus]' \
--output table \
--profile mercadofresco-dev --region eu-west-1Y una advertencia sobre el tema de SNS: si conectas todas las notificaciones de Config a
alertas-mercadofresco, recibirás un mensaje por cada cambio de configuración de la cuenta. Con el
ASG rotando, eso es ruido puro que hará que la gente deje de leer los avisos. La forma correcta es
enviar por SNS solo las notificaciones de no conformidad, y eso se hace con EventBridge (07-03)
filtrando el tipo de evento, o con un tema separado para el ruido. MercadoFresco usa el tema aquí
únicamente porque el canal lo exige, y filtra en EventBridge lo que llega a las personas.
Elegir qué tipos de recurso se graban
La tabla de decisión de MercadoFresco, que es el patrón que hay que copiar:
| Tipo de recurso | ¿Grabar? | Frecuencia | Motivo |
|---|---|---|---|
AWS::S3::Bucket |
Sí | Continua | Cifrado, acceso público: crítico |
AWS::EC2::SecurityGroup |
Sí | Continua | La puerta de entrada |
AWS::RDS::DBInstance |
Sí | Continua | Cifrado, Multi-AZ, copias |
AWS::IAM::Role / Policy / User |
Sí | Continua | Es global: solo en una región |
AWS::KMS::Key |
Sí | Continua | Rotación, política |
AWS::CloudTrail::Trail |
Sí | Continua | Que nadie lo desactive |
AWS::Logs::LogGroup |
Sí | Continua | Retención (el problema de 05-01) |
AWS::EC2::Instance |
Sí | Diaria | Rotan constantemente |
AWS::EC2::Volume |
Sí | Diaria | Idem |
AWS::EC2::NetworkInterface |
No | — | Puro ruido del ASG |
AWS::SSM::ManagedInstanceInventory |
No | — | Volumen enorme, valor bajo |
AWS::Config::ResourceCompliance |
No | — | Config grabando a Config |
Las tres últimas filas son las que más dinero ahorran en cuentas reales. NetworkInterface y
ManagedInstanceInventory son, con diferencia, los dos tipos que más CI generan por accidente.
Recursos globales: IAM y CloudFront son globales. Si activas includeGlobalResourceTypes en varias
regiones, pagarás el mismo CI varias veces. Se activa en una sola región —MercadoFresco usa
eu-west-1— y se graban explícitamente los tipos de IAM como hemos hecho arriba.
Reglas: el corazón del servicio
Una regla de Config evalúa recursos y les asigna un estado:
| Estado | Significa |
|---|---|
COMPLIANT |
Cumple |
NON_COMPLIANT |
No cumple |
NOT_APPLICABLE |
La regla no aplica a ese recurso |
INSUFFICIENT_DATA |
No se ha podido evaluar |
Hay tres clases de regla:
| Clase | Quién la escribe | Coste de evaluación |
|---|---|---|
| Gestionada por AWS | AWS. Hay más de 300 | 0,001 USD |
| Personalizada con Lambda | Tú, en código | 0,001 USD + coste de Lambda |
| Personalizada con Guard | Tú, en lenguaje declarativo | 0,001 USD |
Empezar siempre por las gestionadas: cubren el 90 % de lo que necesitas y no hay que mantenerlas.
Las reglas gestionadas que activa MercadoFresco
| Regla | Qué comprueba | Por qué la necesita MercadoFresco | Disparo |
|---|---|---|---|
s3-bucket-server-side-encryption-enabled |
Cifrado por defecto en buckets | La pregunta del módulo 4 | Cambio |
s3-bucket-public-read-prohibited |
Que no se pueda leer públicamente | mercadofresco-copias-basedatos público sería catastrófico |
Cambio |
s3-bucket-public-write-prohibited |
Que no se pueda escribir | Idem | Cambio |
s3-bucket-versioning-enabled |
Versionado activo | Recuperación ante borrados | Cambio |
s3-bucket-ssl-requests-only |
Política que exige TLS | Datos en tránsito | Cambio |
rds-storage-encrypted |
Cifrado de RDS | mercadofresco-pedidos con alias/mercadofresco-datos (04-02) |
Cambio |
rds-instance-public-access-check |
RDS no accesible desde internet | Debe estar en subredes de datos (03-01) | Cambio |
rds-multi-az-support |
Multi-AZ activo | Lo montamos en 02-04; que nadie lo quite | Cambio |
db-instance-backup-enabled |
Copias automáticas activas | PITR de 02-04 | Cambio |
restricted-ssh |
Puerto 22 no abierto a 0.0.0.0/0 |
La otra pregunta del módulo 4 | Cambio |
restricted-common-ports |
3389, 5432, 3306, 6379… cerrados | Que nadie exponga PostgreSQL | Cambio |
vpc-default-security-group-closed |
El SG por defecto sin reglas | El olvido clásico de 03-02 | Cambio |
vpc-flow-logs-enabled |
Flow logs activos en la VPC | flowlogs-mercadofresco (03-01) |
Periódico |
iam-user-mfa-enabled |
MFA en todos los usuarios IAM | 04-01 | Periódico (24 h) |
iam-root-access-key-check |
La raíz sin claves de acceso | 04-01 | Periódico (24 h) |
iam-password-policy |
Política de contraseñas robusta | 04-01 | Periódico |
access-keys-rotated |
Claves rotadas cada 90 días | Ejercicio 2 de 05-03 | Periódico |
iam-policy-no-statements-with-admin-access |
Sin "Action": "*" |
Mínimo privilegio (04-01) | Cambio |
cloudtrail-enabled |
Existe un trail activo | trail-mercadofresco (05-03) |
Periódico |
cloud-trail-log-file-validation-enabled |
Validación de integridad | 05-03 | Periódico |
cloud-trail-encryption-enabled |
Trail cifrado con KMS | 05-03 | Periódico |
cw-loggroup-retention-period-check |
Retención configurada | El error de 610 USD de 05-01 | Cambio |
encrypted-volumes |
Volúmenes EBS cifrados | 04-02 | Cambio |
ec2-imdsv2-check |
IMDSv2 obligatorio en EC2 | Protege el rol de instancia | Cambio |
elb-tls-https-listeners-only |
ALB solo con HTTPS | 03-03 | Cambio |
kms-cmk-not-scheduled-for-deletion |
Ninguna clave marcada para borrar | El evento más destructivo (05-03) | Periódico |
required-tags |
Etiquetado obligatorio | La política de MercadoFresco | Cambio |
lambda-function-public-access-prohibited |
Lambdas no invocables por cualquiera | 02-05 | Cambio |
secretsmanager-rotation-enabled-check |
Rotación activa en secretos | 04-03 | Periódico |
Son 29 reglas. Activarlas:
# La que responde a la pregunta del modulo 4
aws configservice put-config-rule --config-rule '{
"ConfigRuleName": "mercadofresco-s3-cifrado",
"Description": "Todos los buckets deben tener cifrado por defecto",
"Source": {
"Owner": "AWS",
"SourceIdentifier": "S3_BUCKET_SERVER_SIDE_ENCRYPTION_ENABLED"
},
"Scope": { "ComplianceResourceTypes": ["AWS::S3::Bucket"] }
}' --profile mercadofresco-dev --region eu-west-1
# La otra: SSH abierto al mundo
aws configservice put-config-rule --config-rule '{
"ConfigRuleName": "mercadofresco-ssh-restringido",
"Description": "Ningun SG con el puerto 22 abierto a 0.0.0.0/0",
"Source": { "Owner": "AWS", "SourceIdentifier": "INCOMING_SSH_DISABLED" },
"Scope": { "ComplianceResourceTypes": ["AWS::EC2::SecurityGroup"] }
}' --profile mercadofresco-dev --region eu-west-1
# Puertos comunes, con parametros
aws configservice put-config-rule --config-rule '{
"ConfigRuleName": "mercadofresco-puertos-restringidos",
"Source": { "Owner": "AWS", "SourceIdentifier": "RESTRICTED_INCOMING_TRAFFIC" },
"Scope": { "ComplianceResourceTypes": ["AWS::EC2::SecurityGroup"] },
"InputParameters": "{\"blockedPort1\":\"22\",\"blockedPort2\":\"3389\",\"blockedPort3\":\"5432\",\"blockedPort4\":\"3306\",\"blockedPort5\":\"6379\"}"
}' --profile mercadofresco-dev --region eu-west-1
# Retencion de los grupos de registros: el error de 05-01
aws configservice put-config-rule --config-rule '{
"ConfigRuleName": "mercadofresco-retencion-registros",
"Description": "Todo grupo de registros debe tener retencion configurada",
"Source": { "Owner": "AWS", "SourceIdentifier": "CW_LOGGROUP_RETENTION_PERIOD_CHECK" },
"InputParameters": "{\"MinRetentionTime\":\"7\"}"
}' --profile mercadofresco-dev --region eu-west-1Y la consulta del estado general:
aws configservice describe-compliance-by-config-rule \
--query 'ComplianceByConfigRules[?Compliance.ComplianceType==`NON_COMPLIANT`].[ConfigRuleName,Compliance.ComplianceContributorCount.CappedCount]' \
--output table \
--profile mercadofresco-dev --region eu-west-1
# Y el detalle de una regla concreta
aws configservice get-compliance-details-by-config-rule \
--config-rule-name mercadofresco-s3-cifrado \
--compliance-types NON_COMPLIANT \
--query 'EvaluationResults[].EvaluationResultIdentifier.EvaluationResultQualifier.ResourceId' \
--output table \
--profile mercadofresco-dev --region eu-west-1Disparo por cambio frente a disparo periódico
| Por cambio de configuración | Periódico | |
|---|---|---|
| Cuándo evalúa | Al detectar un CI nuevo | Cada 1, 3, 6, 12 o 24 horas |
| Latencia de detección | Minutos | Hasta 24 horas |
| Coste | Una evaluación por cambio | Una evaluación por periodo |
| Para qué | Recursos concretos | Comprobaciones de cuenta |
| Ejemplo | restricted-ssh |
iam-user-mfa-enabled, cloudtrail-enabled |
La distinción no es arbitraria. Una regla por cambio necesita un recurso al que asociarse. Reglas
como cloudtrail-enabled o iam-password-policy evalúan una propiedad de la cuenta, no de un
recurso concreto, así que solo pueden ser periódicas.
Y hay un matiz de coste importante: una regla por cambio sobre un tipo de recurso que cambia mucho —instancias EC2 con el ASG rotando— se evalúa muchísimas veces. Una regla periódica cada 24 horas se evalúa 30 veces al mes y punto. Cuando una comprobación no necesite detección inmediata, ponla periódica.
Se puede combinar:
{
"ConfigRuleName": "mercadofresco-volumenes-cifrados",
"Source": {
"Owner": "AWS",
"SourceIdentifier": "ENCRYPTED_VOLUMES",
"SourceDetails": [
{ "EventSource": "aws.config",
"MessageType": "ConfigurationItemChangeNotification" },
{ "EventSource": "aws.config",
"MessageType": "ScheduledNotification",
"MaximumExecutionFrequency": "TwentyFour_Hours" }
]
}
}Evalúa al cambiar y una vez al día, que es la red de seguridad por si se perdió algún CI.
El etiquetado obligatorio con required-tags
El etiquetado de MercadoFresco es Proyecto, Entorno, Componente, Propietario, CentroCoste.
Hasta ahora era una norma escrita que nadie comprobaba. Config la convierte en una regla:
aws configservice put-config-rule --config-rule '{
"ConfigRuleName": "mercadofresco-etiquetado-obligatorio",
"Description": "Los recursos deben llevar las cinco etiquetas de MercadoFresco",
"Source": { "Owner": "AWS", "SourceIdentifier": "REQUIRED_TAGS" },
"InputParameters": "{\"tag1Key\":\"Proyecto\",\"tag1Value\":\"mercadofresco\",\"tag2Key\":\"Entorno\",\"tag2Value\":\"produccion,preproduccion,desarrollo\",\"tag3Key\":\"Componente\",\"tag4Key\":\"Propietario\",\"tag5Key\":\"CentroCoste\"}",
"Scope": {
"ComplianceResourceTypes": [
"AWS::EC2::Instance",
"AWS::EC2::Volume",
"AWS::S3::Bucket",
"AWS::RDS::DBInstance",
"AWS::Lambda::Function",
"AWS::ElasticLoadBalancingV2::LoadBalancer"
]
},
"MaximumExecutionFrequency": "TwentyFour_Hours"
}' --profile mercadofresco-dev --region eu-west-1Detalles de la sintaxis que no son evidentes:
tagNKeysintagNValueexige que la etiqueta exista, con cualquier valor. Así estánComponente,PropietarioyCentroCoste: sus valores son libres.tagNValuecon una lista separada por comas exige que el valor sea uno de esos.Entornosolo puede serproduccion,preproduccionodesarrollo.- Máximo 6 etiquetas por regla. Si necesitas más, dos reglas.
Y el resultado la primera vez que se ejecuta, que es el momento más instructivo:
| Tipo | Total | Conformes | No conformes | Qué falta |
|---|---|---|---|---|
| Buckets S3 | 6 | 4 | 2 | mercadofresco-registros-web sin CentroCoste |
| Instancias EC2 | 2 | 2 | 0 | — |
| Volúmenes EBS | 4 | 1 | 3 | Los creados por el ASG no heredan etiquetas |
| Lambdas | 2 | 2 | 0 | — |
| RDS | 2 | 2 | 0 | — |
| ALB | 1 | 1 | 0 | — |
La fila de los volúmenes es el hallazgo real, y es un problema que casi todo el mundo tiene sin saberlo: los volúmenes EBS creados automáticamente por un ASG no heredan las etiquetas de la instancia salvo que se configure explícitamente en la plantilla de lanzamiento:
{
"TagSpecifications": [
{
"ResourceType": "instance",
"Tags": [
{ "Key": "Proyecto", "Value": "mercadofresco" },
{ "Key": "Entorno", "Value": "produccion" },
{ "Key": "Componente", "Value": "tienda" },
{ "Key": "Propietario", "Value": "marta" },
{ "Key": "CentroCoste", "Value": "tienda-online" }
]
},
{
"ResourceType": "volume",
"Tags": [
{ "Key": "Proyecto", "Value": "mercadofresco" },
{ "Key": "Entorno", "Value": "produccion" },
{ "Key": "Componente", "Value": "tienda" },
{ "Key": "Propietario", "Value": "marta" },
{ "Key": "CentroCoste", "Value": "tienda-online" }
]
}
]
}Consecuencia directa y medible: en el módulo 11, cuando Sara reparta el coste por CentroCoste, los
volúmenes de la tienda aparecerían como «sin asignar». Una regla de Config ha encontrado un agujero
en la contabilidad de costes que nadie había visto en meses. Es el mejor argumento para activar
required-tags desde el primer día.
Reglas personalizadas con Lambda
Cuando ninguna regla gestionada sirve. El caso de MercadoFresco: ningún bucket de datos de negocio puede tener una regla de ciclo de vida que borre objetos antes de 30 días, porque Sara necesita poder recalcular informes del mes.
"""Regla de Config personalizada: ciclo de vida seguro en buckets de negocio.
El evento que recibe la funcion contiene el elemento de configuracion
completo del recurso evaluado.
"""
import json
import boto3
config = boto3.client("config")
s3 = boto3.client("s3")
BUCKETS_DE_NEGOCIO = (
"mercadofresco-informes-analitica",
"mercadofresco-copias-basedatos",
)
DIAS_MINIMOS = 30
def evaluar(elemento):
"""Devuelve (estado, motivo) para un elemento de configuracion."""
nombre = elemento["resourceName"]
if not nombre.startswith(BUCKETS_DE_NEGOCIO):
return "NOT_APPLICABLE", "No es un bucket de datos de negocio"
try:
ciclo = s3.get_bucket_lifecycle_configuration(Bucket=nombre)
except s3.exceptions.ClientError as e:
if "NoSuchLifecycleConfiguration" in str(e):
return "COMPLIANT", "Sin reglas de ciclo de vida: nada se borra"
raise
for regla in ciclo.get("Rules", []):
if regla.get("Status") != "Enabled":
continue
expiracion = regla.get("Expiration", {})
dias = expiracion.get("Days")
if dias is not None and dias < DIAS_MINIMOS:
return ("NON_COMPLIANT",
f"La regla '{regla.get('ID')}' expira objetos a los {dias} dias "
f"(minimo exigido: {DIAS_MINIMOS})")
return "COMPLIANT", f"Ninguna regla expira antes de {DIAS_MINIMOS} dias"
def handler(evento, contexto):
invocacion = json.loads(evento["invokingEvent"])
elemento = invocacion["configurationItem"]
token = evento["resultToken"]
# Un recurso borrado no se evalua.
if elemento["configurationItemStatus"] in ("ResourceDeleted",
"ResourceDeletedNotRecorded"):
estado, motivo = "NOT_APPLICABLE", "Recurso borrado"
else:
estado, motivo = evaluar(elemento)
config.put_evaluations(
Evaluations=[{
"ComplianceResourceType": elemento["resourceType"],
"ComplianceResourceId": elemento["resourceId"],
"ComplianceType": estado,
"Annotation": motivo[:256], # limite de 256 caracteres
"OrderingTimestamp": elemento["configurationItemCaptureTime"],
}],
ResultToken=token,
)
return {"estado": estado, "motivo": motivo}Cuatro cosas que hay que hacer bien y que se olvidan:
- Devolver
NOT_APPLICABLEpara lo que no aplica, noCOMPLIANT. Si devuelvesCOMPLIANTpara todos los buckets del mundo, el cuadro de cumplimiento miente: parecerá que 40 buckets cumplen una regla que solo aplica a dos. - Tratar
ResourceDeleted. Evaluar un recurso que ya no existe deja registros huérfanos marcados como no conformes para siempre. Annotationes el texto que se ve en la consola junto al recurso no conforme. Un buen mensaje ahí ahorra media hora de investigación. Máximo 256 caracteres.ResultTokenhay que devolverlo tal cual. Es lo que asocia la respuesta con la evaluación.
Y el registro de la regla:
aws configservice put-config-rule --config-rule '{
"ConfigRuleName": "mercadofresco-ciclo-vida-seguro",
"Description": "Los buckets de negocio no pueden expirar objetos antes de 30 dias",
"Source": {
"Owner": "CUSTOM_LAMBDA",
"SourceIdentifier": "arn:aws:lambda:eu-west-1:111122223333:function:mercadofresco-regla-ciclo-vida",
"SourceDetails": [{
"EventSource": "aws.config",
"MessageType": "ConfigurationItemChangeNotification"
}]
},
"Scope": { "ComplianceResourceTypes": ["AWS::S3::Bucket"] }
}' --profile mercadofresco-dev --region eu-west-1Coste: 0,001 USD por evaluación más el coste de la invocación de Lambda. Con pocos buckets es despreciable; con una regla personalizada sobre instancias EC2 en una cuenta con mucho movimiento, puede sorprender.
Reglas personalizadas con Guard
AWS CloudFormation Guard es un lenguaje declarativo de reglas, mucho más corto que una Lambda para comprobaciones sobre la configuración. Las reglas de Config escritas en Guard se llaman Custom Policy rules y no requieren ninguna función: no hay código que mantener, ni rol, ni coste de Lambda.
La misma idea de antes, en Guard, comprobando que los buckets de negocio están cifrados con la clave de MercadoFresco y no con la de AWS:
# Regla: los buckets de negocio deben usar alias/mercadofresco-datos
rule buckets_negocio_con_clave_propia when
resourceType == "AWS::S3::Bucket"
{
let nombre = resourceName
when %nombre == /^mercadofresco-(informes-analitica|copias-basedatos)/ {
configuration.serverSideEncryptionConfiguration.rules[*]
.applyServerSideEncryptionByDefault.sseAlgorithm == "aws:kms"
<<
Los buckets de datos de negocio deben cifrarse con KMS,
no con AES256 gestionado por S3.
>>
configuration.serverSideEncryptionConfiguration.rules[*]
.applyServerSideEncryptionByDefault.kmsMasterKeyID
== /mercadofresco-datos/
<<
Debe usarse la clave alias/mercadofresco-datos (04-02).
>>
}
}
# Regla: ningun grupo de registros sin retencion
rule grupos_registros_con_retencion when
resourceType == "AWS::Logs::LogGroup"
{
configuration.retentionInDays EXISTS
<< Todo grupo de registros debe tener retencion (05-01). >>
configuration.retentionInDays <= 400
<< Retenciones mayores de 400 dias deben justificarse. >>
}El bloque entre << y >> es el mensaje de error, que aparece como anotación en la consola.
aws configservice put-config-rule --config-rule '{
"ConfigRuleName": "mercadofresco-cifrado-buckets-negocio",
"Source": {
"Owner": "CUSTOM_POLICY",
"SourceDetails": [{
"EventSource": "aws.config",
"MessageType": "ConfigurationItemChangeNotification"
}],
"CustomPolicyDetails": {
"PolicyRuntime": "guard-2.x.x",
"PolicyText": "<contenido del fichero .guard>",
"EnableDebugLogDelivery": true
}
},
"Scope": { "ComplianceResourceTypes": ["AWS::S3::Bucket"] }
}' --profile mercadofresco-dev --region eu-west-1| Lambda | Guard | |
|---|---|---|
| Escribes | Código Python/Node | Reglas declarativas |
| Puedes llamar a otras API | Sí | No |
| Coste extra | Invocaciones de Lambda | Ninguno |
| Mantenimiento | Runtime, dependencias, rol | Ninguno |
| Complejidad admitida | Cualquiera | Lo que esté en el CI |
| Recomendación | Solo si necesitas lógica externa | Por defecto |
La diferencia práctica: Guard solo puede mirar lo que hay en el elemento de configuración. Si tu
comprobación necesita llamar a otra API —como el ejemplo de Lambda, que consultaba
get_bucket_lifecycle_configuration—, necesitas Lambda. Para todo lo demás, Guard.
Remediación automática con Systems Manager
Detectar es bueno. Corregir solo es mejor, y es lo que convierte a Config en una herramienta operativa y no en un informe más que nadie lee.
La remediación se ejecuta con un documento de Systems Manager Automation: un procedimiento
predefinido con pasos y parámetros. AWS proporciona docenas de documentos AWS-* listos para usar.
El rol de remediación. Aquí es donde están los permisos peligrosos, y por eso es un rol aparte del de Config, con el mínimo estricto:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "CifrarBuckets",
"Effect": "Allow",
"Action": [
"s3:PutEncryptionConfiguration",
"s3:GetEncryptionConfiguration"
],
"Resource": "arn:aws:s3:::mercadofresco-*"
},
{
"Sid": "CerrarGruposDeSeguridad",
"Effect": "Allow",
"Action": [
"ec2:RevokeSecurityGroupIngress",
"ec2:DescribeSecurityGroups"
],
"Resource": "*",
"Condition": {
"StringEquals": { "aws:ResourceTag/Proyecto": "mercadofresco" }
}
},
{
"Sid": "PonerRetencionEnRegistros",
"Effect": "Allow",
"Action": ["logs:PutRetentionPolicy", "logs:DescribeLogGroups"],
"Resource": "arn:aws:logs:eu-west-1:111122223333:log-group:*"
}
]
}Fíjate en tres decisiones de mínimo privilegio, todas del manual de 04-01:
s3:PutEncryptionConfigurationsolo sobremercadofresco-*. El rol no puede tocar buckets ajenos.- Condición de etiqueta en las acciones sobre SG: solo actúa sobre recursos del proyecto.
- No hay
ec2:AuthorizeSecurityGroupIngress. El rol puede quitar reglas, nunca añadirlas. Si alguien comprometiera este rol, lo peor que podría hacer es cerrar cosas. Esa asimetría es deliberada y es el tipo de detalle que separa una configuración segura de una peligrosa.
Los dos casos reales: cifrado de bucket y SG abierto
Caso 1: reactivar el cifrado de un bucket.
aws configservice put-remediation-configurations \
--remediation-configurations '[{
"ConfigRuleName": "mercadofresco-s3-cifrado",
"TargetType": "SSM_DOCUMENT",
"TargetId": "AWS-EnableS3BucketEncryption",
"TargetVersion": "1",
"Automatic": true,
"MaximumAutomaticAttempts": 3,
"RetryAttemptSeconds": 300,
"Parameters": {
"AutomationAssumeRole": {
"StaticValue": {
"Values": ["arn:aws:iam::111122223333:role/rol-remediacion-mercadofresco"]
}
},
"BucketName": {
"ResourceValue": { "Value": "RESOURCE_ID" }
},
"SSEAlgorithm": {
"StaticValue": { "Values": ["AES256"] }
}
}
}]' \
--profile mercadofresco-dev --region eu-west-1El ResourceValue: RESOURCE_ID es la pieza clave: le dice a la remediación que el nombre del bucket
sea el del recurso que ha fallado la evaluación. Sin eso, la remediación no sabría sobre qué actuar.
Cronología completa de lo que pasa ahora si alguien desactiva el cifrado:
| Momento | Qué ocurre |
|---|---|
T+0 s |
Alguien ejecuta DeleteBucketEncryption |
T+2 s |
CloudTrail registra el evento (05-03) |
T+3 min |
Config crea un CI nuevo del bucket |
T+3 min |
La regla mercadofresco-s3-cifrado evalúa: NON_COMPLIANT |
T+3 min |
Se emite un evento de EventBridge y una notificación a SNS |
T+4 min |
La remediación ejecuta AWS-EnableS3BucketEncryption |
T+5 min |
El cifrado está restaurado |
T+8 min |
Nueva evaluación: COMPLIANT |
T+8 min |
Marta recibe el aviso con todo el ciclo en el registro |
Ocho minutos, sin intervención humana. Y con el rastro completo: CloudTrail sabe quién lo desactivó, Config sabe cómo quedó, la remediación lo arregló, y la línea temporal lo cuenta entero. Esa es la respuesta a la última pregunta del módulo 4.
Caso 2: revocar una regla de grupo de seguridad abierta al mundo.
aws configservice put-remediation-configurations \
--remediation-configurations '[{
"ConfigRuleName": "mercadofresco-ssh-restringido",
"TargetType": "SSM_DOCUMENT",
"TargetId": "AWSConfigRemediation-RemoveUnrestrictedSourceIngressRules",
"TargetVersion": "1",
"Automatic": true,
"MaximumAutomaticAttempts": 5,
"RetryAttemptSeconds": 60,
"Parameters": {
"AutomationAssumeRole": {
"StaticValue": {
"Values": ["arn:aws:iam::111122223333:role/rol-remediacion-mercadofresco"]
}
},
"GroupId": { "ResourceValue": { "Value": "RESOURCE_ID" } }
}
}]' \
--profile mercadofresco-dev --region eu-west-1Este documento revoca todas las reglas de entrada con origen 0.0.0.0/0 o ::/0 del grupo, no
solo la del 22. Es agresivo, y hay que saberlo: si tu ALB público está en un grupo de seguridad que
legítimamente permite 0.0.0.0/0 en el 443, esta remediación lo tumbaría.
Por eso MercadoFresco acota el alcance de la regla, no de la remediación:
{
"ConfigRuleName": "mercadofresco-ssh-restringido",
"Source": { "Owner": "AWS", "SourceIdentifier": "INCOMING_SSH_DISABLED" },
"Scope": {
"ComplianceResourceTypes": ["AWS::EC2::SecurityGroup"],
"TagKey": "Componente",
"TagValue": "basedatos"
}
}Con TagKey/TagValue, la regla —y por tanto la remediación— solo se aplica a los grupos de
seguridad etiquetados como Componente=basedatos. sg-mercadofresco-alb, que debe estar abierto al
mundo en el 443, queda fuera. Es el mismo razonamiento del alcance mínimo que aplicábamos a las
exclusiones de WAF en 04-05.
Caso 3, el más rentable: poner retención a los grupos de registros.
aws configservice put-remediation-configurations \
--remediation-configurations '[{
"ConfigRuleName": "mercadofresco-retencion-registros",
"TargetType": "SSM_DOCUMENT",
"TargetId": "AWSConfigRemediation-SetCloudWatchLogGroupRetention",
"Automatic": true,
"MaximumAutomaticAttempts": 3,
"RetryAttemptSeconds": 300,
"Parameters": {
"AutomationAssumeRole": { "StaticValue": { "Values": [
"arn:aws:iam::111122223333:role/rol-remediacion-mercadofresco"] } },
"LogGroupName": { "ResourceValue": { "Value": "RESOURCE_ID" } },
"RetentionInDays": { "StaticValue": { "Values": ["30"] } }
}
}]' \
--profile mercadofresco-dev --region eu-west-1Esta es la que habría evitado el incidente de los 610 USD del ejercicio 3 de 05-01. Cualquier grupo de registros nuevo —creado por una Lambda, por el agente, por un servicio— recibe 30 días de retención automáticamente en cuestión de minutos. Es la remediación con mejor relación coste-beneficio de todo el módulo.
La advertencia de probar primero en modo manual
Nunca actives Automatic: true en una remediación que no hayas probado. Una remediación es código
con permisos que modifica tu producción sin que nadie lo apruebe. Si está mal, lo hace muy rápido y en
todas partes.
El procedimiento de MercadoFresco, y es innegociable:
Fase 1 — Solo detección, 1 semana. La regla, sin remediación. Se observa qué recursos marca. Aquí salen los falsos positivos: recursos que incumplen la letra de la regla por buenas razones.
Fase 2 — Remediación manual, 2 semanas. Automatic: false. La remediación existe pero hay que
lanzarla a mano desde la consola, recurso a recurso:
aws configservice start-remediation-execution \
--config-rule-name mercadofresco-s3-cifrado \
--resource-keys resourceType=AWS::S3::Bucket,resourceId=mercadofresco-registros-web \
--profile mercadofresco-dev --region eu-west-1Se ejecuta sobre un recurso poco crítico, se comprueba que hace exactamente lo esperado y nada más, y se revisa el registro de la ejecución en Systems Manager.
Fase 3 — Automática, con alcance acotado. Automatic: true, pero primero solo sobre los recursos
etiquetados Entorno=desarrollo. Una semana. Después, producción.
Fase 4 — Con límites. MaximumAutomaticAttempts y RetryAttemptSeconds son un freno importante:
si la remediación falla 3 veces, Config deja de intentarlo en lugar de entrar en bucle. Un bucle de
remediación puede generar miles de llamadas a la API y una factura considerable.
Y tres tipos de remediación que MercadoFresco NO automatiza nunca:
| Remediación | Por qué no |
|---|---|
| Borrar recursos no conformes | Irreversible. Una regla mal escrita borra producción |
| Detener instancias EC2 | Corta el servicio. La regla puede estar mal |
| Modificar políticas de IAM | Puede dejar a todo el mundo sin acceso, incluida la propia remediación |
La regla mental: automatiza lo que añade seguridad y es reversible (activar cifrado, poner retención, quitar acceso público). Nunca automatices lo que quita disponibilidad o es irreversible.
Paquetes de conformidad
Un conformance pack es una plantilla que despliega un conjunto de reglas y remediaciones de golpe, como una unidad, y que se puede aplicar a toda una organización.
AWS publica plantillas alineadas con marcos conocidos:
| Paquete | Reglas aprox. | Para qué |
|---|---|---|
| Operational Best Practices for CIS AWS Foundations Benchmark v1.4 | ~50 | El estándar de partida más habitual |
| Operational Best Practices for PCI DSS 3.2.1 | ~90 | Comercios que procesan tarjetas |
| Operational Best Practices for NIST 800-53 | ~200 | Sector público estadounidense |
| Operational Best Practices for GDPR | ~60 | Ayuda para el RGPD |
| Operational Best Practices for Amazon S3 | ~15 | Solo S3 |
| Security Best Practices | ~120 | Generalista de AWS |
# paquete-mercadofresco.yaml
Parameters:
RetencionMinimaRegistros:
Type: String
Default: '7'
Resources:
S3CifradoActivado:
Type: AWS::Config::ConfigRule
Properties:
ConfigRuleName: paq-mercadofresco-s3-cifrado
Source:
Owner: AWS
SourceIdentifier: S3_BUCKET_SERVER_SIDE_ENCRYPTION_ENABLED
Scope:
ComplianceResourceTypes: [ 'AWS::S3::Bucket' ]
S3SinLecturaPublica:
Type: AWS::Config::ConfigRule
Properties:
ConfigRuleName: paq-mercadofresco-s3-sin-lectura-publica
Source:
Owner: AWS
SourceIdentifier: S3_BUCKET_PUBLIC_READ_PROHIBITED
Scope:
ComplianceResourceTypes: [ 'AWS::S3::Bucket' ]
RdsCifrado:
Type: AWS::Config::ConfigRule
Properties:
ConfigRuleName: paq-mercadofresco-rds-cifrado
Source:
Owner: AWS
SourceIdentifier: RDS_STORAGE_ENCRYPTED
RetencionRegistros:
Type: AWS::Config::ConfigRule
Properties:
ConfigRuleName: paq-mercadofresco-retencion-registros
InputParameters:
MinRetentionTime: !Ref RetencionMinimaRegistros
Source:
Owner: AWS
SourceIdentifier: CW_LOGGROUP_RETENTION_PERIOD_CHECKaws configservice put-conformance-pack \
--conformance-pack-name paquete-mercadofresco-base \
--template-body file://paquete-mercadofresco.yaml \
--delivery-s3-bucket mercadofresco-config-historial \
--delivery-s3-key-prefix conformance \
--profile mercadofresco-dev --region eu-west-1
aws configservice describe-conformance-pack-compliance \
--conformance-pack-name paquete-mercadofresco-base \
--profile mercadofresco-dev --region eu-west-1La advertencia que hay que repetir. Desplegar el paquete de PCI DSS no te hace conforme con PCI DSS. Es una ayuda: comprueba automáticamente un subconjunto de controles técnicos. La conformidad real incluye procesos, formación, contratos, evaluaciones y una auditoría formal por un evaluador cualificado. Presentar un cuadro verde de Config como prueba de conformidad es un error serio. Estas decisiones las valida un profesional de cumplimiento, no un curso.
Coste: un paquete no cuesta nada por sí mismo; cuestan las reglas que contiene. Un paquete de PCI DSS con 90 reglas evaluándose sobre cientos de recursos es la forma más rápida de multiplicar la factura de Config por diez. Léelo antes de desplegarlo.
El cuadro de cumplimiento de MercadoFresco
El primer resultado, tres días después de activar todo. Este es el momento realmente valioso, porque saca a la luz lo que llevaba meses ahí:
| Regla | Estado | Recursos no conformes | Comentario |
|---|---|---|---|
mercadofresco-s3-cifrado |
NO CONFORME | 1 | mercadofresco-registros-web sin cifrado desde marzo |
mercadofresco-ssh-restringido |
NO CONFORME | 1 | Un SG de pruebas con 22 abierto desde mayo |
mercadofresco-etiquetado-obligatorio |
NO CONFORME | 5 | 3 volúmenes EBS + 2 buckets |
mercadofresco-retencion-registros |
NO CONFORME | 4 | 4 grupos «nunca expira» |
mercadofresco-puertos-restringidos |
CONFORME | 0 | El trabajo de 03-02 aguanta |
mercadofresco-rds-cifrado |
CONFORME | 0 | 04-02 |
mercadofresco-rds-multi-az |
CONFORME | 0 | 02-04 |
mercadofresco-iam-mfa |
NO CONFORME | 1 | Un usuario de servicio sin MFA |
mercadofresco-cloudtrail-activo |
CONFORME | 0 | 05-03 |
mercadofresco-claves-rotadas |
NO CONFORME | 1 | Clave de 412 días |
mercadofresco-vpc-flow-logs |
CONFORME | 0 | 03-01 |
mercadofresco-sg-defecto-cerrado |
NO CONFORME | 1 | El SG por defecto de la VPC, con reglas |
Seis reglas no conformes, 14 recursos. Y ninguno de esos catorce había disparado nunca una alarma de CloudWatch ni aparecido en una investigación de CloudTrail, porque ninguno era un evento: eran estados. Llevaban ahí meses.
Los tres hallazgos que Marta no esperaba:
mercadofresco-registros-websin cifrado desde marzo. Se creó a mano en 03-03 para los registros del ALB, antes de que existiera la política de cifrado de 04-02, y nadie volvió.- El SG con el 22 abierto desde una prueba de mayo. La regla se añadió «un momento» para depurar y se quedó tres meses.
- El grupo de seguridad por defecto de
vpc-mercadofrescocon reglas. Es el clásico de 03-02: nadie lo usa, pero está ahí, permisivo, esperando a que alguien lance una instancia sin especificar grupo.
El plan de corrección, ordenado por riesgo:
| Prioridad | Recurso | Acción | Cómo |
|---|---|---|---|
| 1 | SG con 22 abierto | Revocar la regla | Remediación automática |
| 2 | SG por defecto | Vaciar sus reglas | Manual, con revisión |
| 3 | mercadofresco-registros-web |
Activar cifrado | Remediación automática |
| 4 | 4 grupos de registros | Poner retención de 30 días | Remediación automática |
| 5 | Clave de 412 días | Rotar y desactivar la antigua | Manual, coordinado |
| 6 | Usuario sin MFA | Sustituir por rol (04-01) | Manual, cambio de diseño |
| 7 | Etiquetas de volúmenes | Corregir lt-mercadofresco-tienda |
Manual, cambio de plantilla |
Fíjate en el patrón: las cuatro primeras se automatizan porque añaden seguridad y son reversibles. Las tres últimas requieren criterio humano, y por eso no se automatizan.
Consultas avanzadas para inventariar
Config incluye un motor de consultas SQL sobre el inventario actual de todos los recursos grabados. Es una de sus funciones más infravaloradas.
# Todos los buckets sin cifrado
aws configservice select-resource-config \
--expression "
SELECT resourceId, resourceName, awsRegion
WHERE resourceType = 'AWS::S3::Bucket'
AND supplementaryConfiguration.ServerSideEncryptionConfiguration NOT LIKE '%aws:kms%'
" \
--profile mercadofresco-dev --region eu-west-1
# Instancias EC2 por tipo, para el analisis de coste del modulo 11
aws configservice select-resource-config \
--expression "
SELECT configuration.instanceType, COUNT(*)
WHERE resourceType = 'AWS::EC2::Instance'
GROUP BY configuration.instanceType
" \
--profile mercadofresco-dev --region eu-west-1
# Recursos SIN la etiqueta CentroCoste: el agujero de la contabilidad
aws configservice select-resource-config \
--expression "
SELECT resourceType, resourceId, resourceName, tags
WHERE tags.key != 'CentroCoste'
" \
--profile mercadofresco-dev --region eu-west-1
# Volumenes EBS sin cifrar
aws configservice select-resource-config \
--expression "
SELECT resourceId, configuration.size, configuration.volumeType
WHERE resourceType = 'AWS::EC2::Volume'
AND configuration.encrypted = false
" \
--profile mercadofresco-dev --region eu-west-1
# Grupos de seguridad con reglas abiertas al mundo
aws configservice select-resource-config \
--expression "
SELECT resourceId, resourceName, configuration.ipPermissions
WHERE resourceType = 'AWS::EC2::SecurityGroup'
AND configuration.ipPermissions.ipRanges = '0.0.0.0/0'
" \
--profile mercadofresco-dev --region eu-west-1Este motor responde en segundos a preguntas que de otro modo requerirían recorrer decenas de llamadas
Describe paginadas. Y a diferencia de Athena sobre CloudTrail (05-03), no cuesta nada por
consulta: es parte del servicio.
Una consulta que Marta ejecuta cada mes y que ya ha justificado el coste de Config varias veces:
# Volumenes EBS sin asociar a ninguna instancia: dinero tirado
aws configservice select-resource-config \
--expression "
SELECT resourceId, configuration.size, configuration.createTime, tags
WHERE resourceType = 'AWS::EC2::Volume'
AND configuration.state.value = 'available'
" \
--profile mercadofresco-dev --region eu-west-1Un volumen available es un volumen que existe, que se paga cada mes, y que no está conectado a
nada. Suelen quedar de instancias terminadas. Es exactamente uno de los hallazgos que también reporta
Trusted Advisor, que es la lección siguiente.
Agregadores multicuenta
Un agregador consolida los datos de Config de varias cuentas y regiones en una sola vista.
aws configservice put-configuration-aggregator \
--configuration-aggregator-name agregador-mercadofresco \
--account-aggregation-sources '[{
"AccountIds": ["111122223333"],
"AllAwsRegions": true
}]' \
--profile mercadofresco-dev --region eu-west-1Con AWS Organizations se usa --organization-aggregation-source y el agregador recoge
automáticamente todas las cuentas de la organización, incluidas las que se creen en el futuro. Esa
es la configuración correcta para cualquier empresa con varias cuentas, y es la lección 09-04.
MercadoFresco tiene una cuenta hoy, así que el agregador solo aporta la vista multirregión —útil para detectar recursos olvidados en regiones donde no se debería tener nada, el caso del ejercicio 3 de 05-03—.
Security Hub y GuardDuty: dónde encaja cada uno
Es fácil confundir estas tres herramientas. La tabla que las separa:
| AWS Config | Amazon GuardDuty | AWS Security Hub | |
|---|---|---|---|
| Qué hace | Evalúa configuraciones | Detecta amenazas | Agrega hallazgos |
| Fuente de datos | Elementos de configuración | CloudTrail, DNS, flow logs, S3, EKS | Config, GuardDuty, Inspector, Macie… |
| Detecta | Un bucket sin cifrar | Una IP maliciosa hablando con tu instancia | Ambas cosas, en un panel |
| Basado en | Tus reglas | Aprendizaje e inteligencia de amenazas | Estándares (CIS, PCI, AWS FSBP) |
| Corrige | Sí (remediación) | No | Vía acciones personalizadas |
| Coste | Por CI y evaluación | Por GB analizado y eventos | Por comprobación y hallazgo |
| Pregunta | ¿Está bien configurado? | ¿Hay alguien dentro? | ¿Cuál es mi postura global? |
Ejemplos concretos que aclaran la división del trabajo:
- Config: «
mercadofresco-registros-webno tiene cifrado.» Es un problema de configuración. - GuardDuty: «Una instancia de
asg-mercadofresco-tiendaestá resolviendo dominios asociados a minado de criptomonedas» o «credenciales del rol de instancia usadas desde una IP externa a AWS». Es un problema de intrusión, y ninguna regla de configuración lo detectaría. - Security Hub: «Tu cuenta cumple el 82 % del CIS Benchmark; estos 14 hallazgos son críticos», con los hallazgos de Config y de GuardDuty en la misma lista priorizada.
Recomendación práctica: si vas a activar los tres, actívalos en este orden —Config primero, porque es la base y porque Security Hub consume sus reglas; GuardDuty después, porque es el que detecta lo que Config no puede ver; Security Hub al final, como capa de agregación—. Y ten presente que los tres cuestan dinero y los tres generan hallazgos que alguien tiene que leer. Activar los tres y no mirarlos es peor que activar solo Config y revisarlo mensualmente.
Coste real y cómo no dispararlo
| Concepto | Precio en eu-west-1 |
|---|---|
| Elemento de configuración registrado | 0,003 USD cada uno |
| Evaluación de regla (primeras 100.000/mes) | 0,001 USD cada una |
| Evaluaciones 100.001-500.000 | 0,0008 USD |
| Consultas avanzadas | Gratis |
| Paquetes de conformidad | Solo las reglas que contienen |
| Ejecución de remediación (SSM Automation) | Gratis en el nivel estándar |
| Almacenamiento en S3 | 0,023 USD/GB/mes |
| Agregador | Gratis |
Cálculo para MercadoFresco:
| Concepto | Cantidad/mes | Coste |
|---|---|---|
| CI de recursos estables (S3, RDS, IAM, KMS, SG, ALB…) | ~450 | 1,35 USD |
| CI de instancias y volúmenes (modo diario) | ~180 | 0,54 USD |
| Evaluaciones de reglas por cambio (29 reglas) | ~4.200 | 4,20 USD |
| Evaluaciones periódicas (10 reglas × 30 días) | 300 | 0,30 USD |
| Almacenamiento en S3 | ~0,3 GB | 0,01 USD |
| Total | ~6,40 USD/mes |
Y ahora el mismo cálculo sin las optimizaciones, que es lo que le pasa a la mayoría de las cuentas:
| Concepto | Cantidad/mes | Coste |
|---|---|---|
allSupported: true con instancias en modo continuo |
~28.000 CI | 84,00 USD |
| Evaluaciones sobre todos esos recursos | ~140.000 | 132,00 USD |
| Total | ~216,00 USD/mes |
Treinta y cuatro veces más caro, y con exactamente la misma capacidad de detección de los problemas que importan. La diferencia está entera en tres decisiones de configuración.
Las cinco formas de disparar la factura de Config, por frecuencia real:
allSupported: trueen una cuenta con autoescalado. Cada lanzamiento de instancia genera CI de la instancia, sus volúmenes, sus interfaces de red y sus asociaciones. Miles al mes.- No usar
recordingFrequency: DAILYpara recursos efímeros. La optimización más rentable y la menos conocida. - Grabar recursos globales (IAM) en varias regiones. Se paga el mismo CI tantas veces como
regiones tengan el grabador con
includeGlobalResourceTypes. - Desplegar un conformance pack de 200 reglas sin leerlo. Cada regla se evalúa contra cada recurso aplicable, en cada cambio.
- Reglas personalizadas con Lambda sobre tipos de recurso muy cambiantes. Se paga la evaluación y la invocación.
Las cinco defensas:
- Lista explícita de
resourceTypes, nuncaallSupported. recordingFrequency: DAILYparaAWS::EC2::Instance,AWS::EC2::Volumey todo lo efímero.- Recursos globales en una sola región.
- Reglas periódicas cada 24 h en vez de por cambio siempre que no necesites detección inmediata.
- Un presupuesto de AWS Budgets filtrado por servicio = Config (módulo 11), con aviso al 80 %.
Limpieza
# 1. Quitar las remediaciones ANTES que las reglas
aws configservice delete-remediation-configuration \
--config-rule-name mercadofresco-s3-cifrado \
--profile mercadofresco-dev --region eu-west-1
# 2. Borrar las reglas
for R in mercadofresco-s3-cifrado mercadofresco-ssh-restringido \
mercadofresco-puertos-restringidos mercadofresco-retencion-registros \
mercadofresco-etiquetado-obligatorio mercadofresco-ciclo-vida-seguro \
mercadofresco-cifrado-buckets-negocio; do
aws configservice delete-config-rule --config-rule-name "$R" \
--profile mercadofresco-dev --region eu-west-1
done
# 3. El paquete de conformidad
aws configservice delete-conformance-pack \
--conformance-pack-name paquete-mercadofresco-base \
--profile mercadofresco-dev --region eu-west-1
# 4. DETENER EL GRABADOR: esto es lo que corta el gasto
aws configservice stop-configuration-recorder \
--configuration-recorder-name grabador-mercadofresco \
--profile mercadofresco-dev --region eu-west-1
# 5. Canal y grabador
aws configservice delete-delivery-channel --delivery-channel-name canal-mercadofresco \
--profile mercadofresco-dev --region eu-west-1
aws configservice delete-configuration-recorder \
--configuration-recorder-name grabador-mercadofresco \
--profile mercadofresco-dev --region eu-west-1
# 6. El bucket del historial (revisa antes si hay obligacion de conservarlo)
aws s3 rm s3://mercadofresco-config-historial --recursive --profile mercadofresco-dev
aws s3api delete-bucket --bucket mercadofresco-config-historial --profile mercadofresco-devEl paso 4 es el importante. Borrar reglas no detiene el gasto: los CI se siguen registrando
mientras el grabador esté activo. Detener el grabador es lo que corta la facturación. Y si en
algún momento ves un cargo de Config que no esperas, empieza siempre por
describe-configuration-recorder-status en todas las regiones: un grabador olvidado en una región
que no usas es un clásico.
Errores Comunes y Consejos
1. Activar allSupported: true sin pensarlo. Es el error de coste número uno del servicio. Lista
explícita, siempre.
2. No usar recordingFrequency: DAILY para instancias y volúmenes. Con un ASG activo, es la
diferencia entre 6 y 216 USD al mes.
3. Grabar recursos globales en varias regiones. Se paga el mismo CI varias veces. IAM y CloudFront en una sola región.
4. Activar Automatic: true en una remediación sin probarla. Detección una semana, remediación
manual dos semanas, automática en desarrollo una semana, y luego producción. Sin atajos.
5. Automatizar remediaciones destructivas. Borrar recursos, detener instancias o modificar IAM automáticamente puede convertir una regla mal escrita en una caída. Automatiza solo lo que añade seguridad y es reversible.
6. Confundir Config con CloudTrail. «¿Quién lo cambió?» → CloudTrail. «¿Cómo está y cumple?» → Config. Se usan juntos.
7. Devolver COMPLIANT en vez de NOT_APPLICABLE en una regla personalizada. El cuadro de
cumplimiento queda inflado y deja de ser útil.
8. Conectar todas las notificaciones de Config a SNS. Un mensaje por cada cambio de configuración es ruido garantizado, y el ruido hace que la gente deje de leer los avisos importantes. Filtra con EventBridge (07-03) y envía a personas solo las no conformidades.
9. Creer que un conformance pack te certifica. No. Comprueba controles técnicos; la conformidad real incluye procesos, contratos y una auditoría formal. Que lo valide un profesional de cumplimiento.
10. Aplicar una remediación de SG sin acotar el alcance de la regla.
RemoveUnrestrictedSourceIngressRules sobre sg-mercadofresco-alb tumbaría la tienda. Acota con
TagKey/TagValue.
11. Borrar reglas creyendo que se detiene el gasto. Lo que factura son los CI del grabador.
stop-configuration-recorder.
12. Olvidar start-configuration-recorder. Como con CloudTrail, crear no es arrancar.
13. Escribir una regla con Lambda cuando bastaba Guard. Si la comprobación solo mira el elemento de configuración, Guard es más corto, más barato y no hay que mantener un runtime.
Consejo final: activa Config con pocas reglas y bien elegidas antes que con un paquete de conformidad completo. Diez reglas que alguien revisa cada mes valen infinitamente más que doscientas que generan un cuadro rojo permanente al que todo el mundo se acostumbra. El objetivo no es tener muchas reglas: es llegar a cero no conformidades y que cualquier nueva sea una señal real.
Ejercicios
Ejercicio 1: diseñar la configuración de Config con presupuesto
MercadoFresco quiere activar Config con un presupuesto máximo de 12 USD al mes. Inventario real de la cuenta:
| Tipo de recurso | Cantidad | Cambios/mes estimados |
|---|---|---|
AWS::S3::Bucket |
7 | 4 |
AWS::EC2::SecurityGroup |
6 | 8 |
AWS::EC2::Instance |
2-4 (ASG) | 620 (rotación) |
AWS::EC2::Volume |
4-8 | 640 |
AWS::EC2::NetworkInterface |
4-8 | 1.280 |
AWS::RDS::DBInstance |
2 | 3 |
AWS::IAM::Role |
9 | 6 |
AWS::IAM::Policy |
12 | 5 |
AWS::Lambda::Function |
2 | 20 (despliegues) |
AWS::Logs::LogGroup |
9 | 12 |
AWS::KMS::Key |
1 | 1 |
Requisitos: hay que detectar en menos de 15 minutos que un bucket pierde el cifrado o que un SG se abre al mundo; hay que comprobar el etiquetado obligatorio; hay que vigilar la retención de los grupos de registros.
Diseña: la lista de resourceTypes con su recordingFrequency, el conjunto de reglas con su tipo de
disparo, y calcula el coste total. Justifica cada exclusión. Si te pasas del presupuesto, explica qué
recortarías y qué capacidad de detección perderías.
Ejercicio 2: la regla personalizada y su remediación
MercadoFresco tiene una política interna: toda instantánea de RDS de más de 90 días debe estar
cifrada y no debe ser pública, y las de más de 365 días deben borrarse salvo que lleven la etiqueta
Retencion=legal.
- a) Explica por qué esta comprobación no se puede hacer con una sola regla gestionada, y qué reglas gestionadas cubren parte del problema.
- b) Escribe la regla personalizada (Lambda o Guard, justificando la elección) que evalúa las instantáneas.
- c) Diseña la remediación. Decide qué parte se automatiza y qué parte no, y justifícalo con el criterio de la lección.
- d) Calcula el coste añadido si MercadoFresco tiene 120 instantáneas y la regla es periódica cada 24 horas, frente a por cambio de configuración.
Ejercicio 3: la investigación combinada
Un lunes, la regla mercadofresco-s3-cifrado marca mercadofresco-informes-analitica como
NO CONFORME. La remediación automática está en modo manual todavía. Datos:
- La línea temporal muestra que el CI no conforme se creó el domingo a las 04:17.
- El CI anterior, del jueves, era conforme.
- La regla
mercadofresco-etiquetado-obligatoriomarca el mismo bucket como no conforme desde el domingo a las 04:17: le falta la etiquetaPropietario. - Hay tres buckets nuevos en la cuenta, creados el domingo entre las 04:10 y las 04:25, ninguno con cifrado ni etiquetas.
- CloudTrail muestra 46 llamadas de escritura ese domingo entre las 04:05 y las 04:30, todas con
userIdentity.type = "AssumedRole"ysessionIssuer.userName = "rol-despliegue-infra".
Explica: qué ha pasado con toda probabilidad; cómo lo confirmarías combinando Config y CloudTrail (con comandos y consultas concretas); por qué Config detectó algo que CloudTrail por sí solo no habría hecho saltar; qué remediación aplicarías y en qué orden; y qué arreglo de fondo propondrías para que no vuelva a ocurrir, indicando en qué módulo del curso se estudia.
Soluciones
Solución 1
Análisis del inventario. El problema está concentrado en tres filas: instancias (620),
volúmenes (640) e interfaces de red (1.280). Son 2.540 de los 2.679 cambios mensuales: el 95 %.
Todos generados por la rotación del ASG y todos de configuración idéntica, porque vienen de
lt-mercadofresco-tienda.
Configuración propuesta del grabador:
| Tipo | ¿Grabar? | Frecuencia | Justificación |
|---|---|---|---|
AWS::S3::Bucket |
Sí | Continua | Requisito: detección en 15 min |
AWS::EC2::SecurityGroup |
Sí | Continua | Requisito: detección en 15 min |
AWS::RDS::DBInstance |
Sí | Continua | Pocos cambios, alto valor |
AWS::IAM::Role / Policy |
Sí | Continua | Pocos cambios, alto valor |
AWS::KMS::Key |
Sí | Continua | 1 cambio/mes |
AWS::Logs::LogGroup |
Sí | Continua | Requisito de retención |
AWS::Lambda::Function |
Sí | Continua | 20 cambios/mes, asumible |
AWS::EC2::Instance |
Sí | DIARIA | 620 → ~30 CI/mes |
AWS::EC2::Volume |
Sí | DIARIA | 640 → ~30 CI/mes |
AWS::EC2::NetworkInterface |
NO | — | 1.280 CI de valor casi nulo |
Cálculo de elementos de configuración:
| Tipo | CI/mes | Coste |
|---|---|---|
| Buckets | 4 | 0,012 USD |
| SG | 8 | 0,024 USD |
| Instancias (diaria) | 30 | 0,090 USD |
| Volúmenes (diaria) | 30 | 0,090 USD |
| RDS | 3 | 0,009 USD |
| Roles + políticas | 11 | 0,033 USD |
| Lambdas | 20 | 0,060 USD |
| Grupos de registros | 12 | 0,036 USD |
| KMS | 1 | 0,003 USD |
| Descubrimiento inicial (una vez) | ~55 | 0,165 USD |
| Total CI | ~174 | ~0,52 USD |
Reglas propuestas:
| Regla | Disparo | Evaluaciones/mes | Coste |
|---|---|---|---|
s3-bucket-server-side-encryption-enabled |
Cambio | 4 + 7 iniciales = 11 | 0,011 USD |
s3-bucket-public-read-prohibited |
Cambio | 11 | 0,011 USD |
restricted-ssh |
Cambio | 8 + 6 = 14 | 0,014 USD |
restricted-common-ports |
Cambio | 14 | 0,014 USD |
vpc-default-security-group-closed |
Cambio | 14 | 0,014 USD |
rds-storage-encrypted |
Cambio | 3 + 2 = 5 | 0,005 USD |
rds-multi-az-support |
Cambio | 5 | 0,005 USD |
cw-loggroup-retention-period-check |
Cambio | 12 + 9 = 21 | 0,021 USD |
encrypted-volumes |
Cambio (diaria) | 30 | 0,030 USD |
required-tags |
Periódico 24 h | 30 × 25 recursos = 750 | 0,750 USD |
iam-user-mfa-enabled |
Periódico 24 h | 30 | 0,030 USD |
iam-root-access-key-check |
Periódico 24 h | 30 | 0,030 USD |
cloudtrail-enabled |
Periódico 24 h | 30 | 0,030 USD |
access-keys-rotated |
Periódico 24 h | 30 | 0,030 USD |
kms-cmk-not-scheduled-for-deletion |
Periódico 24 h | 30 | 0,030 USD |
| Total evaluaciones | ~1.062 | ~1,03 USD |
Coste total: 0,52 + 1,03 + ~0,02 de S3 = ~1,57 USD/mes. Muy por debajo de los 12 USD.
Con el margen sobrante se puede: añadir el paquete de conformidad CIS completo (~50 reglas, unos
3-4 USD más), pasar required-tags a disparo por cambio para detección más rápida, o incluir
AWS::EC2::NetworkInterface si alguna vez hiciera falta para una investigación de red.
Justificación de las exclusiones:
NetworkInterface: 1.280 CI (3,84 USD) por interfaces efímeras que el ASG crea y destruye. Su configuración es siempre la misma y viene de la plantilla de lanzamiento. Si hiciera falta investigar red, están los VPC Flow Logs (03-01) y CloudTrail (05-03).- Instancias y volúmenes en modo diario: se pierde la detección inmediata de un cambio en una instancia concreta. ¿Importa? Las instancias son ganado, no mascotas: si una se degrada, el ASG la reemplaza. Los cambios que importan están en la plantilla de lanzamiento, no en la instancia. El compromiso es correcto.
Lo que se perdería recortando más: si hubiera que bajar de 1,57 USD, el siguiente recorte sería
required-tags (0,75 USD, casi la mitad del total). Se perdería la detección del agujero de
CentroCoste en los volúmenes, que en el módulo 11 vale mucho más de 0,75 USD. No se recorta.
Solución 2
a) Por qué no basta una regla gestionada.
Hay reglas gestionadas que cubren partes:
| Regla gestionada | Qué cubre | Qué le falta |
|---|---|---|
rds-snapshot-encrypted |
Instantáneas cifradas | No mira la antigüedad |
rds-snapshots-public-prohibited |
Que no sean públicas | No mira la antigüedad |
required-tags |
La etiqueta Retencion |
No la relaciona con la edad |
Lo que ninguna cubre es la lógica combinada: «más de 90 días y sin cifrar», «más de 365
días y sin la etiqueta Retencion=legal». Config no permite componer reglas gestionadas con
condiciones entre ellas. Eso exige una regla propia.
b) La regla. Elección: Lambda, no Guard.
Guard solo puede mirar lo que está en el elemento de configuración. Aquí necesitamos calcular la antigüedad respecto a la fecha actual, y Guard no tiene aritmética de fechas contra «ahora». Además, conviene un disparo periódico que evalúe todas las instantáneas, no solo las que cambian —una instantánea no cambia: simplemente envejece—. Ese es el argumento decisivo: el incumplimiento aparece con el paso del tiempo, no con un cambio de configuración.
"""Regla de Config: instantaneas de RDS segun antiguedad.
Disparo PERIODICO cada 24 h: una instantanea no cambia,
pero envejece, y el incumplimiento aparece por el paso del tiempo.
"""
import json
from datetime import datetime, timezone
import boto3
config = boto3.client("config")
rds = boto3.client("rds")
DIAS_CIFRADO = 90
DIAS_BORRADO = 365
def edad_en_dias(fecha):
return (datetime.now(timezone.utc) - fecha).days
def evaluar_instantanea(snap, etiquetas):
identificador = snap["DBSnapshotIdentifier"]
edad = edad_en_dias(snap["SnapshotCreateTime"])
if edad > DIAS_BORRADO and etiquetas.get("Retencion") != "legal":
return ("NON_COMPLIANT",
f"{edad} dias de antiguedad y sin Retencion=legal: debe borrarse")
if edad > DIAS_CIFRADO and not snap.get("Encrypted", False):
return ("NON_COMPLIANT",
f"{edad} dias de antiguedad y SIN CIFRAR")
# La comprobacion de publicidad requiere otra llamada.
atributos = rds.describe_db_snapshot_attributes(
DBSnapshotIdentifier=identificador
)["DBSnapshotAttributesResult"]["DBSnapshotAttributes"]
for atributo in atributos:
if atributo["AttributeName"] == "restore" and "all" in atributo["AttributeValues"]:
return "NON_COMPLIANT", f"La instantanea es PUBLICA"
return "COMPLIANT", f"{edad} dias, cifrada y privada"
def handler(evento, contexto):
token = evento["resultToken"]
evaluaciones = []
ahora = datetime.now(timezone.utc)
paginador = rds.get_paginator("describe_db_snapshots")
for pagina in paginador.paginate(SnapshotType="manual"):
for snap in pagina["DBSnapshots"]:
etiquetas = {t["Key"]: t["Value"] for t in snap.get("TagList", [])}
estado, motivo = evaluar_instantanea(snap, etiquetas)
evaluaciones.append({
"ComplianceResourceType": "AWS::RDS::DBSnapshot",
"ComplianceResourceId": snap["DBSnapshotIdentifier"],
"ComplianceType": estado,
"Annotation": motivo[:256],
"OrderingTimestamp": ahora,
})
# put_evaluations acepta como maximo 100 por llamada.
for i in range(0, len(evaluaciones), 100):
config.put_evaluations(
Evaluations=evaluaciones[i:i + 100],
ResultToken=token,
)
return {"evaluadas": len(evaluaciones)}Cuatro detalles que hacen que esta función funcione en producción y no solo en el ejemplo:
SnapshotType="manual": las automáticas las gestiona RDS con su propia retención (02-04) y se borran solas. Evaluarlas generaría no conformidades imposibles de remediar.put_evaluationsen lotes de 100: es el límite de la API. Con 120 instantáneas, una sola llamada fallaría.Annotationcon la edad concreta: quien mire la consola sabrá inmediatamente de qué se trata.- La comprobación de publicidad hace una llamada extra por instantánea. Con 120 instantáneas son 120 llamadas cada 24 horas: aceptable. Con 10.000, habría que paralelizar o separar en otra regla.
c) La remediación, y aquí está el criterio.
| Incumplimiento | ¿Automatizar? | Por qué |
|---|---|---|
| Instantánea pública | SÍ, inmediato | Añade seguridad, es reversible, y una copia de base de datos pública es una brecha de datos en curso. AWSConfigRemediation-RevokeRDSDBSnapshotPublicAccess |
| Instantánea sin cifrar de más de 90 días | NO automático | «Cifrar» una instantánea existente no es posible: hay que copiarla cifrada y borrar la original. Es una operación con coste, duración y riesgo. Se genera un ticket |
| Instantánea de más de 365 días sin etiqueta | NUNCA automático | Es un borrado. Irreversible. Si la regla o la etiqueta están mal, se destruye la única copia de algo importante |
Esa tabla es exactamente la regla de la lección: automatiza lo que añade seguridad y es reversible; nunca lo destructivo. La instantánea pública se cierra sola en minutos; el borrado lo aprueba una persona.
Para las dos que no se automatizan, la respuesta correcta es notificar bien: una regla de
EventBridge (07-03) que capture el cambio a no conformidad y envíe a alertas-mercadofresco un
mensaje que incluya la Annotation de la evaluación, que es donde está la explicación concreta
(«412 días de antigüedad y sin cifrar»). Un aviso que solo dice «hay un recurso no conforme» obliga a
entrar en la consola; uno que dice qué instantánea y por qué se resuelve desde el móvil.
Y una revisión trimestral en el calendario de Marta para decidir qué se borra, con la lista delante.
d) Coste.
Opción periódica cada 24 horas:
- 120 instantáneas × 30 días = 3.600 evaluaciones/mes = 3,60 USD
- Invocaciones de Lambda: 30/mes (una por evaluación de la regla, no por instantánea) ≈ 0,00 USD
- Llamadas a la API de RDS: gratuitas
- Total: ~3,60 USD/mes
Opción por cambio de configuración:
- Las instantáneas manuales cambian poquísimo: quizá 10 CI/mes.
- 10 evaluaciones = 0,01 USD
- Total: ~0,01 USD/mes
Pero la opción por cambio NO SIRVE, y esa es la conclusión del apartado. El incumplimiento aquí aparece con el paso del tiempo, no con un cambio: una instantánea creada hoy sin cifrar es conforme, y dentro de 91 días deja de serlo sin que nada haya cambiado en ella. Una regla por cambio nunca la volvería a evaluar y la no conformidad jamás se detectaría.
3,60 USD al mes es el precio de detectar incumplimientos que dependen del tiempo, y
TwentyFour_Hours ya es la frecuencia periódica más espaciada que admite Config: no hay margen por
ahí. La única palanca real de ahorro sería emitir una sola evaluación agregada por regla en lugar
de una por instantánea, pero entonces la consola diría «la regla no se cumple» sin decir cuál de las
120 instantáneas es la culpable, que es justo el dato que hace falta para actuar. Se paga el detalle,
y merece la pena.
Solución 3
Qué ha pasado, con toda probabilidad.
Los datos apuntan de forma inequívoca a un despliegue automatizado de infraestructura el domingo de madrugada que creó recursos sin las propiedades obligatorias, y que además modificó un bucket existente.
Las pistas, en orden:
- Domingo a las 04:17, hora sin actividad humana: es un proceso automático.
rol-despliegue-infracon 46 llamadas de escritura en 25 minutos: es un pipeline o una ejecución de infraestructura como código.- Tres buckets nuevos sin cifrado ni etiquetas, creados en la misma ventana.
- Un bucket existente que pasó de conforme a no conforme exactamente a la misma hora.
El punto 4 es el más grave y el más revelador: la plantilla no solo creó recursos nuevos mal configurados, sino que redefinió un bucket que ya existía, sobrescribiendo su cifrado y sus etiquetas con una definición incompleta. Es el patrón clásico de una plantilla de CloudFormation o Terraform que declara un recurso que ya existía y lo «alinea» con una especificación pobre.
Confirmación, paso a paso.
1. Ver exactamente qué cambió en el bucket (esto es lo que solo Config puede dar):
aws configservice get-resource-config-history \
--resource-type AWS::S3::Bucket \
--resource-id mercadofresco-informes-analitica \
--later-time 2026-08-03T00:00:00Z \
--earlier-time 2026-07-30T00:00:00Z \
--profile mercadofresco-dev --region eu-west-1Comparando el CI del jueves con el del domingo se ve el diff: serverSideEncryptionConfiguration
desapareció y las etiquetas se redujeron. Esto es información que CloudTrail no tiene: CloudTrail
registra la llamada PutBucketEncryption o su ausencia, pero no el estado resultante completo del
bucket.
2. Ver quién hizo la llamada concreta (esto es lo que solo CloudTrail puede dar):
SELECT eventtime, eventname,
split_part(userIdentity.arn, '/', 3) AS sesion,
sourceipaddress, useragent,
json_extract_scalar(requestParameters, '$.bucketName') AS bucket
FROM auditoria_mercadofresco.cloudtrail_mercadofresco
WHERE anio='2026' AND mes='08' AND dia='02'
AND userIdentity.sessionContext.sessionIssuer.userName = 'rol-despliegue-infra'
ORDER BY eventtime;Se busca: el userAgent (¿cloudformation.amazonaws.com? ¿Terraform/1.7?), el nombre de sesión, y
si hay un DeleteBucketEncryption explícito o simplemente un CreateBucket sobre un bucket existente.
3. Ver todos los recursos afectados a la vez, con la consulta avanzada de Config:
aws configservice select-resource-config \
--expression "
SELECT resourceId, resourceName, resourceCreationTime, tags
WHERE resourceType = 'AWS::S3::Bucket'
AND resourceCreationTime > '2026-08-02T00:00:00Z'
" \
--profile mercadofresco-dev --region eu-west-14. Cuadro de cumplimiento completo, para ver si hay más daños de los que se sabía:
aws configservice get-compliance-details-by-config-rule \
--config-rule-name mercadofresco-etiquetado-obligatorio \
--compliance-types NON_COMPLIANT \
--profile mercadofresco-dev --region eu-west-1Por qué CloudTrail solo no habría hecho saltar nada.
Esta es la pregunta central del ejercicio, y hay tres razones:
- CloudTrail no evalúa. Registra 46 llamadas de un rol de despliegue un domingo. Eso es completamente normal para un pipeline programado. No hay nada anómalo en el evento en sí.
- La alarma de 05-03 no habría saltado. Los filtros de métricas que montamos vigilaban
DeleteBucketEncryptionyPutBucketPolicy, pero desde un rol de despliegue legítimo, y probablemente sinDeleteBucketEncryptionexplícito: si la plantilla simplemente no declara cifrado al crear el bucket, no hay ninguna llamada que registrar. La ausencia de una llamada no genera un evento. - CloudTrail no conoce la política de MercadoFresco. No sabe que todos los buckets deben estar cifrados ni que hacen falta cinco etiquetas. Config sí, porque se lo hemos dicho con reglas.
Config detecta el estado resultante; CloudTrail detecta el acto. Aquí el acto era legítimo y el estado resultante no lo era. Ese es exactamente el hueco que esta lección viene a cubrir.
Remediación, en orden de riesgo:
| # | Acción | Modo |
|---|---|---|
| 1 | Detener el pipeline o desactivar su ejecución programada | Manual, inmediato |
| 2 | Restaurar el cifrado de mercadofresco-informes-analitica |
Remediación manual, verificando |
| 3 | Comprobar si se subieron objetos sin cifrar entre el domingo y el lunes | Manual |
| 4 | Cifrar los tres buckets nuevos | Remediación manual |
| 5 | Restaurar las etiquetas del bucket afectado | Manual |
| 6 | Decidir qué hacer con los tres buckets nuevos: ¿debían existir? | Manual, con Luis |
| 7 | Solo entonces, reactivar el pipeline con la plantilla corregida | Manual |
El paso 3 merece comentario: los objetos escritos mientras el cifrado por defecto estaba desactivado se guardaron sin cifrar, y activarlo ahora no los cifra retroactivamente. Hay que reescribirlos:
aws s3 cp s3://mercadofresco-informes-analitica/ s3://mercadofresco-informes-analitica/ \
--recursive --sse aws:kms \
--sse-kms-key-id arn:aws:kms:eu-west-1:111122223333:alias/mercadofresco-datos \
--metadata-directive REPLACE \
--profile mercadofresco-devEste detalle se pasa por alto constantemente y es lo que convierte un incidente de configuración en un incidente de datos.
Los arreglos de fondo, que son la parte importante:
| Arreglo | Módulo | Por qué |
|---|---|---|
| Corregir la plantilla para que declare cifrado y etiquetas obligatorias | 09-01 (CloudFormation) / 09-02 (CDK) | La causa raíz. En CDK se pueden imponer valores por defecto para toda la organización |
Validar la plantilla antes de desplegar con cfn-guard en el pipeline |
08-02 (CodeBuild) | Detectar el problema antes de que llegue a producción es siempre mejor que remediarlo después |
Activar la remediación automática de mercadofresco-s3-cifrado |
Esta lección | Habría corregido el cifrado en 8 minutos, un domingo de madrugada, sin que nadie se enterase hasta el lunes |
Política de control de servicios que deniegue s3:PutObject sin cifrado |
09-04 (Organizations) | Prevención dura: ni siquiera se puede escribir sin cifrar |
| Revisión obligatoria de cambios de infraestructura | 08-04 (CodePipeline) | Un paso de aprobación manual para cambios sobre recursos existentes |
La lección de fondo, y merece subrayarse porque es el argumento más fuerte a favor de este
servicio: la misma regla de Guard que escribes para Config puede ejecutarse en el pipeline con
cfn-guard antes del despliegue. Config es la red de seguridad que atrapa lo que se escapó; la
validación en el pipeline es la que evita que se escape. Las dos, con la misma regla escrita una vez.
Ese es el patrón que MercadoFresco adopta, y que se cierra en el módulo 8.
Conclusión
MercadoFresco ya tiene la última pieza que le faltaba. Sabes qué es la configuración de un recurso
—sus ajustes, no sus datos— y qué es un elemento de configuración: una fotografía completa con sus
relationships, su supplementaryConfiguration y su estado, que Config crea en cada cambio y que
incluso registra cuando el recurso se borra. Conoces la línea temporal, que responde a la pregunta
que se hace cada semana en operaciones: «esto funcionaba el viernes; ¿qué ha cambiado?».
Tienes clara la diferencia esencial con la lección anterior: CloudTrail graba la llamada, Config
graba el estado. CloudTrail dice quién llamó a AuthorizeSecurityGroupIngress; Config dice cómo
quedó el grupo de seguridad, si cumple, y lo arregla. Y sabes que Config depende de CloudTrail para
enterarse de los cambios: no compiten, se apoyan.
Has activado el grabador grabador-mercadofresco con su rol de solo lectura, su canal hacia
mercadofresco-config-historial y su tema de SNS, con las dos decisiones que valen dinero:
allSupported: false con lista explícita, y recordingFrequency: DAILY para instancias y
volúmenes, que es la optimización más rentable y menos conocida del servicio. Y sabes que crear no es
arrancar: start-configuration-recorder.
Conoces las 29 reglas gestionadas que MercadoFresco activa, cada una atada a algo que construimos
en un módulo anterior —s3-bucket-server-side-encryption-enabled por 04-02, restricted-ssh por
03-02, cloudtrail-enabled por 05-03, cw-loggroup-retention-period-check por el incidente de 610
USD de 05-01— y la diferencia entre disparo por cambio (minutos, pero se evalúa en cada cambio) y
periódico (hasta 24 horas, pero coste fijo y única opción para comprobaciones de cuenta).
required-tags te ha encontrado el agujero que nadie veía: los volúmenes EBS creados por el ASG no
heredan etiquetas, y en el módulo 11 habrían aparecido como coste sin asignar.
Sabes escribir reglas propias: con Lambda cuando necesitas llamar a otras API o calcular con
fechas —devolviendo NOT_APPLICABLE en lugar de COMPLIANT, tratando ResourceDeleted y escribiendo
una Annotation útil— y con Guard, declarativo, sin runtime, sin coste extra y sin mantenimiento,
que es lo que hay que usar por defecto cuando basta con mirar el elemento de configuración.
Y has montado la remediación automática con documentos de Systems Manager, que es lo que convierte
Config en una herramienta operativa: AWS-EnableS3BucketEncryption restaura el cifrado de un bucket
en ocho minutos sin intervención humana, RemoveUnrestrictedSourceIngressRules cierra un grupo de
seguridad abierto, y SetCloudWatchLogGroupRetention pone 30 días a cualquier grupo de registros
nuevo. Con el rol de remediación separado del de Config, con permisos acotados por prefijo y por
etiqueta, y sin AuthorizeSecurityGroupIngress: el rol puede cerrar, nunca abrir. Y con el
procedimiento innegociable de cuatro fases —detección, remediación manual, automática en desarrollo,
automática en producción— y la regla que resume todo: automatiza lo que añade seguridad y es
reversible; nunca lo destructivo ni lo que quita disponibilidad.
Conoces los paquetes de conformidad alineados con CIS, PCI DSS, NIST y RGPD, con la advertencia que hay que repetir siempre: desplegar el paquete de PCI DSS no te hace conforme con PCI DSS. Y el cuadro de cumplimiento te ha dado el resultado que justifica el servicio entero: seis reglas no conformes y catorce recursos, entre ellos un bucket sin cifrar desde marzo, un puerto 22 abierto desde mayo y el grupo de seguridad por defecto con reglas. Catorce problemas que llevaban meses ahí y que nunca habrían disparado una alarma de CloudWatch ni aparecido en una investigación de CloudTrail, porque no eran eventos: eran estados.
Y tienes las consultas avanzadas, gratuitas, que inventarían la cuenta en segundos: buckets sin
cifrar, volúmenes sin asociar que se pagan cada mes sin servir para nada, recursos sin CentroCoste.
Con el agregador para la vista multicuenta que llegará en 09-04, y el reparto claro de trabajo con
GuardDuty —que detecta intrusiones, no configuraciones— y Security Hub, que agrega los
hallazgos de todos.
Todo por unos 6,40 USD al mes, frente a los 216 USD que costaría exactamente la misma capacidad de detección sin las tres decisiones de configuración correctas. Ese factor de treinta y cuatro es el resumen de por qué esta lección dedica una sección entera al coste.
Con esto quedan respondidas las cinco preguntas del módulo 4. El aviso llega de verdad, los registros están correlacionados, el pedido de ocho segundos tiene culpable y arreglo, sabemos quién descifró la copia, y ahora nada puede desactivar el cifrado de un bucket sin que salte un aviso —y se corrija solo en ocho minutos—.
Queda una última pieza, y es distinta de todo lo anterior. Config comprueba las reglas que tú le has
dicho. Es potente y es exacto, pero tiene un límite evidente: no puede avisarte de lo que no se te
ha ocurrido. No sabe que tienes tres volúmenes EBS huérfanos costando dinero, ni que hay una IP
elástica sin asociar que se factura por no usarse, ni que el asg-mercadofresco-tienda está a punto de
chocar con una cuota de servicio que nadie ha mirado nunca, ni que una de tus instancias lleva dos
meses al 4 % de CPU. Nadie ha escrito reglas para eso, y nadie las escribirá, porque para escribirlas
habría que sospechar primero que el problema existe.
Existe un servicio que hace precisamente ese repaso: comparar tu cuenta con las buenas prácticas que AWS ha destilado de millones de clientes, y decirte lo que no habías pensado en preguntar. En la lección 05-05, «AWS Trusted Advisor», veremos sus cinco categorías —optimización de costes, rendimiento, seguridad, tolerancia a fallos y límites de servicio—, qué comprobaciones ves de verdad con el plan de soporte Basic y cuáles no, cómo suplir lo que falta con lo que ya sabes, un recorrido comentado por un informe real de la arquitectura de MercadoFresco, las cuotas de servicio y el caso concreto del ASG que no puede pasar de cuatro instancias por un límite de vCPU que nadie había mirado, y la rutina de revisión mensual que adopta Marta. Y con ella cerraremos el módulo entero.
Curso de AWS
Módulo 1: Introducción a AWS
- ¿Qué es AWS?
- Configuración de tu cuenta de AWS
- Infraestructura global de AWS
- Consola de administración de AWS
- AWS CLI y SDKs
Módulo 2: Servicios principales de AWS
Módulo 3: Redes y entrega de contenido
- Amazon VPC
- Grupos de seguridad y listas de control de acceso
- Elastic Load Balancing
- Amazon CloudFront
- Route 53
Módulo 4: Seguridad e identidad
- AWS Identity and Access Management (IAM)
- AWS Key Management Service (KMS)
- Secrets Manager y Parameter Store
- AWS Shield
- AWS WAF
Módulo 5: Monitorización y gestión
- Amazon CloudWatch
- AWS X-Ray y trazabilidad distribuida
- AWS CloudTrail
- AWS Config
- AWS Trusted Advisor
Módulo 6: Bases de datos
- Cómo elegir la base de datos adecuada
- Amazon DynamoDB
- Amazon Aurora
- Amazon Redshift
- Amazon ElastiCache
Módulo 7: Integración de aplicaciones
- Amazon SQS
- Amazon SNS
- Amazon EventBridge
- AWS Step Functions
- Patrones de integración: idempotencia, reintentos y colas de mensajes fallidos
