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

  1. Qué es la configuración de un recurso
  2. El elemento de configuración y la línea temporal
  3. Config frente a CloudTrail
  4. El flujo completo: grabador, reglas y remediación
  5. Activar el grabador de configuración
  6. El canal de entrega: S3 y SNS
  7. Elegir qué tipos de recurso se graban
  8. Reglas: el corazón del servicio
  9. Las reglas gestionadas que activa MercadoFresco
  10. Disparo por cambio frente a disparo periódico
  11. El etiquetado obligatorio con required-tags
  12. Reglas personalizadas con Lambda
  13. Reglas personalizadas con Guard
  14. Remediación automática con Systems Manager
  15. Los dos casos reales: cifrado de bucket y SG abierto
  16. La advertencia de probar primero en modo manual
  17. Paquetes de conformidad
  18. El cuadro de cumplimiento de MercadoFresco
  19. Consultas avanzadas para inventariar
  20. Agregadores multicuenta
  21. Security Hub y GuardDuty: dónde encaja cada uno
  22. Coste real y cómo no dispararlo
  23. 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 llamada Describe principal 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-1

Config 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
Evalúa contra reglas No
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 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-ssh marcándolo como NON_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:

  1. Grabador de configuración (configuration recorder): el proceso que crea los CI. Solo puede haber uno por cuenta y región.
  2. 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.
  3. Reglas: las condiciones que se evalúan.
  4. Remediación: la acción correctiva, ejecutada por Systems Manager Automation.
  5. 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-dev

Con 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-dev

La 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-1

Dos decisiones que aquí valen dinero:

  • allSupported: false con lista explícita. Grabar los 250+ tipos soportados en una cuenta viva genera un volumen de CI enorme, y muchos no aportan nada.
  • recordingModeOverrides con DAILY para instancias y volúmenes. Esta es la optimización más importante de toda la lección. El ASG asg-mercadofresco-tienda lanza 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 de lt-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-1

Paso 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-1

Y 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 Continua Cifrado, acceso público: crítico
AWS::EC2::SecurityGroup Continua La puerta de entrada
AWS::RDS::DBInstance Continua Cifrado, Multi-AZ, copias
AWS::IAM::Role / Policy / User Continua Es global: solo en una región
AWS::KMS::Key Continua Rotación, política
AWS::CloudTrail::Trail Continua Que nadie lo desactive
AWS::Logs::LogGroup Continua Retención (el problema de 05-01)
AWS::EC2::Instance Diaria Rotan constantemente
AWS::EC2::Volume 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-1

Y 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-1

Disparo 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-1

Detalles de la sintaxis que no son evidentes:

  • tagNKey sin tagNValue exige que la etiqueta exista, con cualquier valor. Así están Componente, Propietario y CentroCoste: sus valores son libres.
  • tagNValue con una lista separada por comas exige que el valor sea uno de esos. Entorno solo puede ser produccion, preproduccion o desarrollo.
  • 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_APPLICABLE para lo que no aplica, no COMPLIANT. Si devuelves COMPLIANT para 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.
  • Annotation es 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.
  • ResultToken hay 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-1

Coste: 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 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:PutEncryptionConfiguration solo sobre mercadofresco-*. 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-1

El 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-1

Este 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-1

Esta 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-1

Se 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_CHECK
aws 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-1

La 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:

  1. mercadofresco-registros-web sin 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ó.
  2. 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.
  3. El grupo de seguridad por defecto de vpc-mercadofresco con 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-1

Este 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-1

Un 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-1

Con 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 (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-web no tiene cifrado.» Es un problema de configuración.
  • GuardDuty: «Una instancia de asg-mercadofresco-tienda está 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:

  1. allSupported: true en 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.
  2. No usar recordingFrequency: DAILY para recursos efímeros. La optimización más rentable y la menos conocida.
  3. Grabar recursos globales (IAM) en varias regiones. Se paga el mismo CI tantas veces como regiones tengan el grabador con includeGlobalResourceTypes.
  4. Desplegar un conformance pack de 200 reglas sin leerlo. Cada regla se evalúa contra cada recurso aplicable, en cada cambio.
  5. Reglas personalizadas con Lambda sobre tipos de recurso muy cambiantes. Se paga la evaluación y la invocación.

Las cinco defensas:

  1. Lista explícita de resourceTypes, nunca allSupported.
  2. recordingFrequency: DAILY para AWS::EC2::Instance, AWS::EC2::Volume y todo lo efímero.
  3. Recursos globales en una sola región.
  4. Reglas periódicas cada 24 h en vez de por cambio siempre que no necesites detección inmediata.
  5. 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-dev

El 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-obligatorio marca el mismo bucket como no conforme desde el domingo a las 04:17: le falta la etiqueta Propietario.
  • 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" y sessionIssuer.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 Continua Requisito: detección en 15 min
AWS::EC2::SecurityGroup Continua Requisito: detección en 15 min
AWS::RDS::DBInstance Continua Pocos cambios, alto valor
AWS::IAM::Role / Policy Continua Pocos cambios, alto valor
AWS::KMS::Key Continua 1 cambio/mes
AWS::Logs::LogGroup Continua Requisito de retención
AWS::Lambda::Function Continua 20 cambios/mes, asumible
AWS::EC2::Instance DIARIA 620 → ~30 CI/mes
AWS::EC2::Volume 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_evaluations en lotes de 100: es el límite de la API. Con 120 instantáneas, una sola llamada fallaría.
  • Annotation con 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:

  1. Domingo a las 04:17, hora sin actividad humana: es un proceso automático.
  2. rol-despliegue-infra con 46 llamadas de escritura en 25 minutos: es un pipeline o una ejecución de infraestructura como código.
  3. Tres buckets nuevos sin cifrado ni etiquetas, creados en la misma ventana.
  4. 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-1

Comparando 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-1

4. 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-1

Por qué CloudTrail solo no habría hecho saltar nada.

Esta es la pregunta central del ejercicio, y hay tres razones:

  1. 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í.
  2. La alarma de 05-03 no habría saltado. Los filtros de métricas que montamos vigilaban DeleteBucketEncryption y PutBucketPolicy, pero desde un rol de despliegue legítimo, y probablemente sin DeleteBucketEncryption explí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.
  3. 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-dev

Este 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

Módulo 2: Servicios principales de AWS

Módulo 3: Redes y entrega de contenido

Módulo 4: Seguridad e identidad

Módulo 5: Monitorización y gestión

Módulo 6: Bases de datos

Módulo 7: Integración de aplicaciones

Módulo 8: Herramientas para desarrolladores

Módulo 9: Infraestructura como código y gobierno de cuentas

Módulo 10: Contenedores en AWS

Módulo 11: Mejores prácticas y gestión de costos

© Copyright 2026. Todos los derechos reservados