A 04-02 vam xifrar les còpies de seguretat de mercadofresco-pedidos amb la clau
alias/mercadofresco-datos, i vam dir que KMS registra escrupolosament cada operació criptogràfica.
A 04-03 vam desar la contrasenya de mfadmin al secret
mercadofresco/produccion/rds/mfadmin i vam dir que cada lectura queda anotada. A 04-01 vam crear el
rol rol-mercadofresco-tienda i vam dir que cada AssumeRole deixa rastre.
Cap d'aquestes tres coses no és mentida. Totes estan registrades. I ningú no les ha mirat mai.
Aquest registre es diu AWS CloudTrail, i respon a una pregunta que ni CloudWatch ni X-Ray no poden
contestar. CloudWatch sap el que la teva aplicació diu de si mateixa. X-Ray sap per on va passar una
petició d'un client. CloudTrail sap qui va cridar l'API d'AWS, quan, des d'on, amb quins
paràmetres i amb quin resultat. És la diferència entre saber que la botiga va lenta i saber que
algú, a les 03:42 d'un dimarts, des d'una IP d'un país on no tens oficines, va cridar
kms:Decrypt sobre la còpia de la base de dades.
Aquesta lliçó munta el trail trail-mercadofresco, ensenya a llegir un esdeveniment real, i contesta
per fi la tercera pregunta que va deixar oberta el mòdul 4.
Avís de compliment. Els registres d'auditoria solen tenir requisits legals de conservació. Segons el sector i el país, poden anar de 6 mesos a 10 anys, i en alguns casos han de ser inalterables i estar en custòdia separada. Aquest material és didàctic: les decisions de retenció, immutabilitat i accés als registres d'auditoria d'un sistema real les ha de validar un professional de compliment o l'assessor legal de la teva organització. No les prenguis tu a partir d'un curs.
Contingut
- Què registra CloudTrail i què no
- CloudTrail enfront de CloudWatch Logs
- L'historial d'esdeveniments de 90 dies, gratuït
- Crear el trail
trail-mercadofresco - El bucket que ningú no ha de poder esborrar
- Validació d'integritat dels fitxers
- Anatomia d'un esdeveniment: el JSON comentat
userIdentity: els sis tipus i com es llegeixen- Qui va desxifrar l'última còpia
- Esdeveniments de gestió enfront d'esdeveniments de dades
- Esdeveniments d'Insights
- Trails multiregió i d'organització
- Enviar el trail a CloudWatch Logs
- Filtres de mètriques i alarmes de seguretat
- Consultar amb Athena
- Les consultes que responen preguntes reals
- CloudTrail Lake
- Investigació d'un incident, pas a pas
- Relació amb IAM Access Analyzer
- Retenció, cost i neteja
Què registra CloudTrail i què no
CloudTrail registra crides a l'API d'AWS. Totes. Tant se val que les faci una persona a la consola, la CLI, un SDK, un servei d'AWS actuant en nom teu o una funció Lambda: per sota tot són crides HTTPS signades a les API d'AWS, i CloudTrail les veu totes.
El que no registra:
| No registra | Exemple | On és |
|---|---|---|
| El que fa la teva aplicació per dins | comanda 48213 confirmada |
CloudWatch Logs (05-01) |
| El trànsit HTTP dels teus clients | GET /buscar?q=tomate |
Registres de l'ALB / CloudFront |
| Consultes SQL a la teva base de dades | SELECT * FROM pedidos |
Registres d'RDS |
| Trànsit de xarxa entre instàncies | Paquets TCP | VPC Flow Logs (03-01) |
| Peticions a objectes d'S3 | GET productos/tomate.jpg |
Esdeveniments de dades (cal activar-los) |
| Contingut d'un secret | El valor de la contrasenya | Mai no es registra |
Aquesta última fila importa: CloudTrail registra que algú va cridar GetSecretValue sobre
mercadofresco/produccion/rds/mfadmin, però no registra la contrasenya. Els camps sensibles
s'ometen o s'ofusquen per disseny. El mateix amb kms:Decrypt: registra la crida i l'identificador de
clau, no el text clar.
I una propietat estructural que cal entendre: CloudTrail sempre està encès. No és una cosa que s'«activa»: els últims 90 dies d'esdeveniments de gestió són disponibles gratis al teu compte des del primer dia, encara que no hagis creat cap trail. El que s'activa és la persistència.
CloudTrail enfront de CloudWatch Logs
És la confusió número u d'aquest mòdul, i es mereix una taula:
| CloudTrail | CloudWatch Logs | |
|---|---|---|
| Registra | Crides a l'API d'AWS | El que escriu la teva aplicació |
| Origen de la dada | El pla de control d'AWS | El teu codi, l'agent, els serveis |
| Pregunta que respon | Qui ha fet què? | Què ha passat a dins? |
| Exemple | mercadofresco-admin va esborrar el bucket |
ERROR pagament rebutjat comanda 48213 |
| Àmbit | Compte (i organització) | Regió, grup de registres |
| Actiu per defecte | Sí, 90 dies gratis | Només si publiques |
| Format | JSON estructurat i fix | El que tu escriguis |
| Retenció | 90 dies / el que duri el bucket | El que configuris |
| Es manipula | Molt difícil (amb les proteccions) | Fàcil, si tens permisos |
| Ús principal | Auditoria, forense, compliment | Operació, depuració |
Un cas concret per fixar-ho. Algú esborra el bucket mercadofresco-registros-web:
- CloudWatch Logs: res. La teva aplicació no ha escrit res perquè no ha estat la teva aplicació.
- CloudTrail: un esdeveniment
DeleteBucket, amb la identitat que ho va fer, l'hora exacta, la IP d'origen, l'agent d'usuari (aws-cli/2.15.0oconsole.amazonaws.com), i si es va fer amb MFA.
I el cas invers. La botiga retorna 500 en confirmar una comanda:
- CloudTrail: res. Ningú no va cridar cap API d'AWS de manera anòmala.
- CloudWatch Logs: la traça completa de l'error.
No competeixen: cobreixen universos diferents.
L'historial d'esdeveniments de 90 dies, gratuït
Abans de crear res, hi ha una cosa que ja funciona:
# Els ultims esdeveniments d'escriptura de la regio
aws cloudtrail lookup-events \
--lookup-attributes AttributeKey=ReadOnly,AttributeValue=false \
--max-results 20 \
--query 'Events[].[EventTime,Username,EventName,EventSource]' \
--output table \
--profile mercadofresco-dev --region eu-west-1
# Tot el que ha fet un usuari concret
aws cloudtrail lookup-events \
--lookup-attributes AttributeKey=Username,AttributeValue=mercadofresco-admin \
--start-time 2026-07-01T00:00:00Z \
--profile mercadofresco-dev --region eu-west-1
# Totes les crides a un esdeveniment concret
aws cloudtrail lookup-events \
--lookup-attributes AttributeKey=EventName,AttributeValue=Decrypt \
--profile mercadofresco-dev --region eu-west-1Els atributs de cerca disponibles són limitats i cal conèixer-los, perquè és el que es pot
fer sense infraestructura: EventId, EventName, EventSource, ReadOnly, ResourceName,
ResourceType, Username, AccessKeyId.
Les cinc limitacions de l'historial gratuït, que són exactament les raons per crear un trail:
- Només 90 dies. Una investigació d'un incident detectat tard es queda sense dades.
- Només esdeveniments de gestió. No hi ha esdeveniments de dades (accessos a objectes d'S3, invocacions de Lambda).
- Un sol atribut de cerca per consulta. No pots creuar «aquest usuari» i «aquesta acció».
- No és exportable ni consultable amb SQL. No hi ha anàlisi seriosa possible.
- No és immutable ni verificable. No serveix com a prova en una auditoria formal.
Crear el trail trail-mercadofresco
Un trail persisteix els esdeveniments en un bucket d'S3, indefinidament, i permet tot l'anterior.
Pas 1: el bucket dedicat.
aws s3api create-bucket \
--bucket mercadofresco-auditoria-cloudtrail \
--region eu-west-1 \
--create-bucket-configuration LocationConstraint=eu-west-1 \
--profile mercadofresco-dev
# Bloqueig total d'acces public (02-03)
aws s3api put-public-access-block \
--bucket mercadofresco-auditoria-cloudtrail \
--public-access-block-configuration \
"BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true" \
--profile mercadofresco-dev
# Versionat: imprescindible per a Object Lock i per recuperar esborrats
aws s3api put-bucket-versioning \
--bucket mercadofresco-auditoria-cloudtrail \
--versioning-configuration Status=Enabled \
--profile mercadofresco-dev
# Xifratge amb la clau gestionada per MercadoFresco (04-02)
aws s3api put-bucket-encryption \
--bucket mercadofresco-auditoria-cloudtrail \
--server-side-encryption-configuration '{
"Rules": [{
"ApplyServerSideEncryptionByDefault": {
"SSEAlgorithm": "aws:kms",
"KMSMasterKeyID": "arn:aws:kms:eu-west-1:111122223333:alias/mercadofresco-datos"
},
"BucketKeyEnabled": true
}]
}' \
--profile mercadofresco-devAquest BucketKeyEnabled: true no és cosmètic: redueix fins a un 99 % les crides a KMS —i el seu
cost— quan s'escriuen milers d'objectes petits, que és exactament el que fa CloudTrail. Ho vam veure a
04-02.
Pas 2: la política del bucket. CloudTrail necessita permís per escriure:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "CloudTrailComprobarAcl",
"Effect": "Allow",
"Principal": { "Service": "cloudtrail.amazonaws.com" },
"Action": "s3:GetBucketAcl",
"Resource": "arn:aws:s3:::mercadofresco-auditoria-cloudtrail",
"Condition": {
"StringEquals": {
"aws:SourceArn": "arn:aws:cloudtrail:eu-west-1:111122223333:trail/trail-mercadofresco"
}
}
},
{
"Sid": "CloudTrailEscribir",
"Effect": "Allow",
"Principal": { "Service": "cloudtrail.amazonaws.com" },
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::mercadofresco-auditoria-cloudtrail/AWSLogs/111122223333/*",
"Condition": {
"StringEquals": {
"s3:x-amz-acl": "bucket-owner-full-control",
"aws:SourceArn": "arn:aws:cloudtrail:eu-west-1:111122223333:trail/trail-mercadofresco"
}
}
},
{
"Sid": "DenegarBorradoATodoElMundo",
"Effect": "Deny",
"Principal": "*",
"Action": [
"s3:DeleteObject",
"s3:DeleteObjectVersion",
"s3:PutBucketPolicy",
"s3:DeleteBucketPolicy",
"s3:PutLifecycleConfiguration"
],
"Resource": [
"arn:aws:s3:::mercadofresco-auditoria-cloudtrail",
"arn:aws:s3:::mercadofresco-auditoria-cloudtrail/*"
],
"Condition": {
"ArnNotEquals": {
"aws:PrincipalArn": "arn:aws:iam::111122223333:role/rol-custodia-auditoria"
}
}
},
{
"Sid": "DenegarTrafficoSinTLS",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::mercadofresco-auditoria-cloudtrail",
"arn:aws:s3:::mercadofresco-auditoria-cloudtrail/*"
],
"Condition": { "Bool": { "aws:SecureTransport": "false" } }
}
]
}Les condicions aws:SourceArn són importants i no sempre s'hi posen: sense elles, en teoria un trail
d'un altre compte podria escriure al teu bucket (el confused deputy que vam veure a 04-01).
Pas 3: el trail.
aws cloudtrail create-trail \
--name trail-mercadofresco \
--s3-bucket-name mercadofresco-auditoria-cloudtrail \
--is-multi-region-trail \
--include-global-service-events \
--enable-log-file-validation \
--kms-key-id arn:aws:kms:eu-west-1:111122223333:alias/mercadofresco-datos \
--cloud-watch-logs-log-group-arn arn:aws:logs:eu-west-1:111122223333:log-group:/aws/cloudtrail/mercadofresco:* \
--cloud-watch-logs-role-arn arn:aws:iam::111122223333:role/rol-cloudtrail-a-logs \
--tags-list Key=Proyecto,Value=mercadofresco Key=Entorno,Value=produccion \
Key=Componente,Value=auditoria Key=Propietario,Value=marta \
Key=CentroCoste,Value=plataforma \
--profile mercadofresco-dev --region eu-west-1
# I ARRENCAR-LO: create-trail NO l'inicia.
aws cloudtrail start-logging \
--name trail-mercadofresco \
--profile mercadofresco-dev --region eu-west-1L'error de l'any: create-trail crea el trail però no comença a registrar. Cal cridar
start-logging. Hi ha comptes amb trails creats fa anys que mai no han gravat una línia. Comprova-ho
sempre:
aws cloudtrail get-trail-status --name trail-mercadofresco \
--query '[IsLogging,LatestDeliveryTime,LatestDeliveryError]' \
--profile mercadofresco-dev --region eu-west-1Les quatre banderes de la comanda, explicades:
| Bandera | Què fa | Per què? |
|---|---|---|
--is-multi-region-trail |
Registra les 19+ regions | Un atacant crea recursos a ap-south-1, on ningú no mira |
--include-global-service-events |
Inclou IAM, STS, CloudFront, Route 53 | Són globals i es registren a us-east-1 |
--enable-log-file-validation |
Genera fitxers de resum signats | Detecta manipulació |
--kms-key-id |
Xifra els fitxers amb la teva clau | Separació de funcions (04-02) |
La primera és la més important des del punt de vista de seguretat. Un trail d'una sola regió és una càmera que només enfoca la porta principal.
El bucket que ningú no ha de poder esborrar
Un registre d'auditoria que l'atacant pot esborrar no és un registre d'auditoria. És la primera cosa que fa qualsevol que sap el que fa: entrar, esborrar el rastre, seguir.
Hi ha quatre nivells de protecció, i convé entendre què protegeix cadascun:
| Nivell | Què impedeix | Debilitat |
|---|---|---|
1. Política de bucket amb Deny |
Esborrar objectes | Qui pugui canviar la política, la treu |
| 2. Versionat + MFA Delete | Esborrar versions sense MFA física | Només ho activa el compte arrel |
3. Object Lock en mode COMPLIANCE |
Esborrar, fins i tot el compte arrel | Cal activar-lo en crear el bucket |
| 4. Compte separat | Que l'atacant de producció arribi al bucket | Requereix Organizations (09-04) |
Nivell 3, Object Lock, és el que de debò tanca la porta:
# Nomes es pot activar en un bucket amb versionat.
# En buckets ja existents cal demanar-ho al suport d'AWS;
# l'habitual es crear-lo amb --object-lock-enabled-for-bucket.
aws s3api put-object-lock-configuration \
--bucket mercadofresco-auditoria-cloudtrail \
--object-lock-configuration '{
"ObjectLockEnabled": "Enabled",
"Rule": {
"DefaultRetention": { "Mode": "COMPLIANCE", "Days": 2555 }
}
}' \
--profile mercadofresco-devEls dos modes, i la diferència és enorme:
| Mode | Qui pot escurçar la retenció |
|---|---|
GOVERNANCE |
Qui tingui s3:BypassGovernanceRetention |
COMPLIANCE |
Ningú. Ni el compte arrel. Ni el suport d'AWS. |
Aquests 2.555 dies són 7 anys. I aquí hi ha el perill: amb COMPLIANCE, cada objecte que
CloudTrail escrigui serà inesborrable durant set anys, i el pagaràs durant set anys. Si t'equivoques
de valor, no hi ha marxa enrere.
Aquesta és exactament la decisió que ha de validar un professional de compliment. El període de retenció dels registres d'auditoria depèn del teu sector, el teu país i les teves obligacions contractuals. No el triïs per intuïció, i no copiïs el 2555 d'aquest curs.
Nivell 2, MFA Delete, només el pot activar el compte arrel amb un dispositiu MFA, i per això no encaixa amb el principi de 04-01 de no usar l'arrel. MercadoFresco no el fa servir; usa Object Lock.
Nivell 4, compte separat, és la resposta professional completa: el bucket d'auditoria viu en un
compte de registre diferent, al qual el compte de producció només pot escriure. Encara que algú
comprometi del tot 111122223333, els registres queden fora del seu abast. Requereix AWS
Organizations, que és la lliçó 09-04. MercadoFresco ho té anotat com el pas següent.
I una defensa complementària que es munta avui mateix: una alarma que avisi si algú atura o esborra el trail. És a la secció de filtres de mètriques.
Validació d'integritat dels fitxers
Amb --enable-log-file-validation, CloudTrail fa una cosa elegant: cada hora publica un fitxer de
resum (digest) que conté el hash SHA-256 de cada fitxer de registre d'aquella hora, més el
hash del resum anterior. És una cadena: cada resum signa l'anterior.
flowchart LR
D1["Resum 10:00<br/>hash dels fitxers<br/>+ hash del resum 09:00"] --> D2["Resum 11:00<br/>hash dels fitxers<br/>+ hash del resum 10:00"]
D2 --> D3["Resum 12:00<br/>..."]
F1["registro-10-01.json.gz"] -.-> D1
F2["registro-10-02.json.gz"] -.-> D1
F3["registro-11-01.json.gz"] -.-> D2
Conseqüència pràctica: no es pot alterar un fitxer de registre, ni esborrar-lo, ni substituir un resum, sense trencar la cadena. Els resums estan signats amb la clau privada de CloudTrail, així que tampoc no es poden regenerar.
La verificació:
aws cloudtrail validate-logs \
--trail-arn arn:aws:cloudtrail:eu-west-1:111122223333:trail/trail-mercadofresco \
--start-time 2026-07-01T00:00:00Z \
--end-time 2026-08-01T00:00:00Z \
--profile mercadofresco-dev --region eu-west-1Una sortida sana acaba amb Results requested for ... Results found for N digest files ... All files verified. Qualsevol menció a fitxers modificats o absents és una troballa de seguretat
immediata.
La Marta l'executa el primer dilluns de cada mes i desa la sortida. En una auditoria formal, aquesta sortida és el que demostra que els registres no s'han tocat.
Anatomia d'un esdeveniment: el JSON comentat
Aquest és un esdeveniment real de kms:Decrypt, que és exactament el que buscàvem. Comentat camp a
camp:
{
"eventVersion": "1.09",
"userIdentity": {
"type": "AssumedRole",
"principalId": "AROA1234567890ABCDEFG:sesion-marta-copias",
"arn": "arn:aws:sts::111122223333:assumed-role/rol-restauracion-copias/sesion-marta-copias",
"accountId": "111122223333",
"accessKeyId": "ASIA1234567890ABCDEF",
"sessionContext": {
"sessionIssuer": {
"type": "Role",
"principalId": "AROA1234567890ABCDEFG",
"arn": "arn:aws:iam::111122223333:role/rol-restauracion-copias",
"accountId": "111122223333",
"userName": "rol-restauracion-copias"
},
"attributes": {
"creationDate": "2026-07-28T03:41:52Z",
"mfaAuthenticated": "false"
}
}
},
"eventTime": "2026-07-28T03:42:17Z",
"eventSource": "kms.amazonaws.com",
"eventName": "Decrypt",
"awsRegion": "eu-west-1",
"sourceIPAddress": "198.51.100.77",
"userAgent": "aws-cli/2.15.30 Python/3.11.8 Linux/6.1.0 exe/x86_64",
"requestParameters": {
"encryptionContext": {
"aws:rds:db-id": "arn:aws:rds:eu-west-1:111122223333:db:mercadofresco-pedidos",
"aws:rds:backup-id": "snapshot-2026-07-27-03-00"
},
"keyId": "arn:aws:kms:eu-west-1:111122223333:key/8f2c1a9b-4d3e-4f6a-9c1b-2e5d7a8f3c04",
"encryptionAlgorithm": "SYMMETRIC_DEFAULT"
},
"responseElements": null,
"requestID": "d3a1f9c2-7b4e-4a8d-9f2c-1e5b3a7d9c04",
"eventID": "c7f2b81a-3d9e-4c5f-8a1b-2d6e4f7a9c31",
"readOnly": true,
"resources": [
{
"accountId": "111122223333",
"type": "AWS::KMS::Key",
"ARN": "arn:aws:kms:eu-west-1:111122223333:key/8f2c1a9b-4d3e-4f6a-9c1b-2e5d7a8f3c04"
}
],
"eventType": "AwsApiCall",
"managementEvent": true,
"recipientAccountId": "111122223333",
"eventCategory": "Management",
"tlsDetails": {
"tlsVersion": "TLSv1.3",
"cipherSuite": "TLS_AES_128_GCM_SHA256",
"clientProvidedHostHeader": "kms.eu-west-1.amazonaws.com"
}
}Lectura camp a camp del que aquest esdeveniment ens està dient:
| Camp | Valor | Què significa aquí |
|---|---|---|
eventTime |
03:42:17Z |
Les 3:42 de la matinada. Anòmal per si sol |
userIdentity.type |
AssumedRole |
No va ser un usuari directe: algú va assumir un rol |
sessionIssuer.userName |
rol-restauracion-copias |
Quin rol |
principalId després dels : |
sesion-marta-copias |
Qui el va assumir: el nom de sessió |
mfaAuthenticated |
"false" |
Sense MFA. Segon indicador |
sourceIPAddress |
198.51.100.77 |
Des d'on. Cal comprovar si és coneguda |
userAgent |
aws-cli/2.15.30 |
Des de la CLI, no la consola. Va ser un script o una persona en terminal |
eventName |
Decrypt |
L'operació |
encryptionContext |
db-id, backup-id |
Què es va desxifrar: la còpia del 27 de juliol |
readOnly |
true |
No va modificar res |
errorCode |
(absent) | Va tenir èxit |
tlsDetails |
TLS 1.3 | Metadades de la connexió |
Tres camps mereixen comentari a part:
responseElementsésnullen les operacions de només lectura. En una operació d'escriptura —CreateBucket,RunInstances— conté el que va retornar l'API: l'ID del recurs creat. És el camp que et diu què es va crear.errorCodeierrorMessageapareixen només quan la crida va fallar.AccessDenied,UnauthorizedOperation,Client.InvalidParameterValue. Que CloudTrail registri també les crides que fallen és una de les seves propietats més valuoses: un atacant provant portes deixa un reguer d'AccessDeniedque és el senyal més net de reconeixement.encryptionContextés el que a 04-02 vam explicar com les «dades addicionals autenticades» del xifratge de sobre. Aquí es veu el seu valor de debò: és el que converteix un esdeveniment genèric deDecrypten «algú va desxifrar la còpia del 27 de juliol demercadofresco-pedidos». Sense ell, l'esdeveniment només diria que es va fer servir una clau.
userIdentity: els sis tipus i com es llegeixen
El camp userIdentity és on viu la resposta a «qui?», i té sis formes diferents:
type |
Significa | On és el nom real |
|---|---|---|
Root |
El compte arrel. Alarma sempre | arn |
IAMUser |
Un usuari IAM amb claus permanents | userName |
AssumedRole |
Algú va assumir un rol amb STS | sessionContext.sessionIssuer.userName + nom de sessió |
AWSService |
Un servei d'AWS actuant sol | invokedBy |
AWSAccount |
Un altre compte d'AWS | accountId |
FederatedUser |
Federació amb SAML / OIDC / Identity Center | sessionContext |
Unknown |
No determinat | — |
El més habitual, i el més confús, és AssumedRole. La instància EC2 de MercadoFresco no apareix
com a «instància»: apareix com el rol rol-mercadofresco-tienda amb un nom de sessió que és l'ID
de la instància. I quan la Marta assumeix un rol des del seu usuari, apareix com el rol amb el nom de
sessió que ella va triar.
D'aquí surt una de les pràctiques més útils de tot aquest mòdul: si a AssumeRole no hi poses un
nom de sessió identificatiu, CloudTrail et dirà «algú amb el rol d'administració va esborrar el
bucket» i no podràs saber qui. Amb --role-session-name marta.costa sí:
aws sts assume-role \
--role-arn arn:aws:iam::111122223333:role/rol-restauracion-copias \
--role-session-name marta.costa \
--profile mercadofresco-devA 04-01 vam introduir això de passada. Aquí es veu per què importa: el nom de sessió és el que converteix un rol compartit en una persona identificable. Amb AWS IAM Identity Center això es fa sol, amb el correu de l'usuari federat com a nom de sessió.
I una nota sobre AWSService. Molts esdeveniments legítims tenen invokedBy amb valors com
autoscaling.amazonaws.com o dlm.amazonaws.com. Són AWS actuant en nom teu: l'ASG llançant una
instància, DLM creant una instantània (02-02). No són alertes; són el sistema funcionant. Filtrar-los
correctament és la meitat de la feina de reduir el soroll en una investigació.
Qui va desxifrar l'última còpia
Ara sí, la resposta a la pregunta del mòdul 4. La consulta directa contra l'historial de 90 dies:
aws cloudtrail lookup-events \
--lookup-attributes AttributeKey=EventName,AttributeValue=Decrypt \
--start-time 2026-07-25T00:00:00Z \
--end-time 2026-07-30T00:00:00Z \
--profile mercadofresco-dev --region eu-west-1 \
--output json \
| jq -r '.Events[]
| select(.CloudTrailEvent | fromjson | .requestParameters.encryptionContext["aws:rds:db-id"] // "" | test("mercadofresco-pedidos"))
| (.CloudTrailEvent | fromjson)
| [.eventTime,
.userIdentity.sessionContext.sessionIssuer.userName // .userIdentity.userName,
(.userIdentity.arn | split("/") | last),
.sourceIPAddress,
.userIdentity.sessionContext.attributes.mfaAuthenticated,
.requestParameters.encryptionContext["aws:rds:backup-id"]]
| @tsv'Sortida real:
2026-07-27T03:00:14Z rds-backup-service AWSService rds.amazonaws.com - snapshot-2026-07-27 2026-07-27T09:15:41Z rol-mercadofresco-tienda i-0abc123def456 10.0.11.24 false - 2026-07-28T03:42:17Z rol-restauracion-copias sesion-marta-copias 198.51.100.77 false snapshot-2026-07-27
Tres línies i tres lectures completament diferents:
rds-backup-servicea les 03:00: és el mateix RDS xifrant la còpia automàtica. Normal, és la finestra de còpia que vam configurar a 02-04.rol-mercadofresco-tiendades de10.0.11.24: una instància de l'ASG, IP privada de la subxarxa d'aplicació (03-01), desxifrant dades de l'aplicació. Normal.rol-restauracion-copiasa les 03:42, des de198.51.100.77, sense MFA, sobre la còpiasnapshot-2026-07-27: això s'ha d'investigar.
I les preguntes que se'n deriven, en aquest ordre:
- És
198.51.100.77una IP coneguda? L'oficina? La VPN? El domicili d'algú? - Qui va assumir
rol-restauracion-copias? El nom de sessió diusesion-marta-copias, però això és una cadena que va triar qui va fer la crida, no una identitat verificada. Cal buscar l'esdevenimentAssumeRolecorresponent per veure qui el va assumir de debò. - Hi ha un esdeveniment
RestoreDBInstanceFromDBSnapshota prop? Es va restaurar la còpia en algun lloc? - Hi va haver un
AccessDeniedabans, senyal de temptejar?
Això no és una resposta, és el principi d'una investigació. I aquesta és precisament la lliçó: CloudTrail no diu si alguna cosa va malament. Diu què va passar, amb prou precisió perquè una persona decideixi. La investigació completa és més avall.
Esdeveniments de gestió enfront d'esdeveniments de dades
És la distinció que decideix la factura de CloudTrail:
| Esdeveniments de gestió | Esdeveniments de dades | |
|---|---|---|
| Què registren | Operacions sobre recursos | Operacions sobre el contingut |
| Exemples | CreateBucket, RunInstances, AssumeRole, Decrypt |
GetObject, PutObject, Invoke, GetItem |
| Volum | Baix: centenars o milers al dia | Altíssim: milions |
| Primera còpia | Gratis | De pagament sempre |
| Cost | 2 USD per 100.000 esdeveniments (còpies addicionals) | 0,10 USD per 100.000 esdeveniments |
| Activat per defecte | Sí | No |
El càlcul que cal fer abans d'activar esdeveniments de dades a S3. MercadoFresco serveix unes
40 milions de peticions al mes a mercadofresco-catalogo-fotos (fotos de producte, encara que
CloudFront en guarda la majoria a la memòria cau). Registrar-les totes:
40.000.000 × 0,10 / 100.000 = 40 USD/mes, més l'emmagatzematge a S3 d'un volum enorme de JSON, més el que costi consultar-ho amb Athena.
I no aporta gairebé res: són lectures públiques de fotos de tomàquets.
La regla és: activa esdeveniments de dades amb selectors d'abast mínim, només sobre el que importa.
aws cloudtrail put-event-selectors \
--trail-name trail-mercadofresco \
--advanced-event-selectors '[
{
"Name": "Esdeveniments de gestio complets",
"FieldSelectors": [
{ "Field": "eventCategory", "Equals": ["Management"] }
]
},
{
"Name": "Nomes les copies de la base de dades i els informes",
"FieldSelectors": [
{ "Field": "eventCategory", "Equals": ["Data"] },
{ "Field": "resources.type", "Equals": ["AWS::S3::Object"] },
{ "Field": "resources.ARN", "StartsWith": [
"arn:aws:s3:::mercadofresco-copias-basedatos/",
"arn:aws:s3:::mercadofresco-informes-analitica/"
]}
]
},
{
"Name": "Escriptures al cataleg, no lectures",
"FieldSelectors": [
{ "Field": "eventCategory", "Equals": ["Data"] },
{ "Field": "resources.type", "Equals": ["AWS::S3::Object"] },
{ "Field": "resources.ARN", "StartsWith": [
"arn:aws:s3:::mercadofresco-catalogo-fotos/"
]},
{ "Field": "readOnly", "Equals": ["false"] }
]
}
]' \
--profile mercadofresco-dev --region eu-west-1El que aconsegueix aquesta configuració:
| Bucket | Es registra | Volum/mes | Cost |
|---|---|---|---|
mercadofresco-copias-basedatos |
Tot: qui llegeix una còpia és crític | ~2.000 | 0,00 USD |
mercadofresco-informes-analitica |
Tot: dades de negoci de la Sara | ~15.000 | 0,02 USD |
mercadofresco-catalogo-fotos |
Només escriptures | ~40.000 | 0,04 USD |
mercadofresco-registros-web |
Res | — | 0,00 USD |
| Total | ~0,06 USD/mes |
De 40 USD a 0,06 USD sense perdre res rellevant. Aquest readOnly: false sobre el catàleg és la
decisió clau: llegir una foto de tomàquet no interessa a ningú; que algú pugi o esborri una foto
sí, perquè és una modificació del contingut del lloc.
Els tipus de recurs disponibles per a esdeveniments de dades inclouen AWS::S3::Object,
AWS::Lambda::Function, AWS::DynamoDB::Table (mòdul 6), AWS::SQS::Queue (07-01) i uns quants més.
Esdeveniments d'Insights
CloudTrail Insights analitza el volum de crides i detecta pics anòmals d'activitat d'escriptura o d'errors. No mira el contingut: mira el ritme.
Casos que detecta bé:
- Un script mal escrit que crida
DescribeInstances40.000 vegades en una hora. - Un augment sobtat d'
AccessDenied: algú provant permisos. - Un pic de
TerminateInstancesa les 4 de la matinada.
aws cloudtrail put-insight-selectors \
--trail-name trail-mercadofresco \
--insight-selectors '[
{"InsightType": "ApiCallRateInsight"},
{"InsightType": "ApiErrorRateInsight"}
]' \
--profile mercadofresco-dev --region eu-west-1Cost: 0,35 USD per cada 100.000 esdeveniments de gestió analitzats. Amb ~150.000 esdeveniments mensuals, MercadoFresco paga uns 0,53 USD/mes. És de les coses més barates d'aquest mòdul i de les que avisa més aviat.
Les dues limitacions honestes: necessita almenys 7 dies de línia base abans de servir per a res, i només detecta anomalies de volum, no d'intenció. Una sola crida maliciosa perfectament executada no genera cap insight. Per a això hi ha les alarmes específiques de la secció següent.
Trails multiregió i d'organització
Multiregió ja ho vam activar amb --is-multi-region-trail, i val la pena insistir en per què. Un
trail d'una sola regió és com una càmera de seguretat que només enfoca la porta principal: un atacant
que aconsegueixi credencials crearà les seves instàncies de mineria a ap-southeast-2, on ningú no mira mai.
Amb el trail multiregió, aquestes crides apareixen al mateix bucket.
Cost de la multiregió: zero. La primera còpia dels esdeveniments de gestió és gratuïta a totes les regions. No hi ha cap raó per no activar-la.
Trail d'organització: si tens diversos comptes sota AWS Organizations, un sol trail creat al compte de gestió registra tots els comptes membre, i aquests no el poden desactivar ni veure. És la configuració correcta per a qualsevol organització amb més d'un compte, i és la lliçó 09-04. MercadoFresco té un sol compte avui; la separació entre producció, preproducció i registre és un objectiu declarat.
Enviar el trail a CloudWatch Logs
El bucket d'S3 és l'arxiu. Per reaccionar en calent cal que els esdeveniments arribin també a CloudWatch Logs, on poden convertir-se en mètriques i alarmes (05-01).
# 1. Grup de registres amb retencio finita
aws logs create-log-group \
--log-group-name /aws/cloudtrail/mercadofresco \
--profile mercadofresco-dev --region eu-west-1
aws logs put-retention-policy \
--log-group-name /aws/cloudtrail/mercadofresco \
--retention-in-days 90 \
--profile mercadofresco-dev --region eu-west-1Fixa-t'hi: 90 dies a CloudWatch Logs, 7 anys a S3. Els dos destins tenen propòsits diferents i retencions diferents. Logs és per alarmar i consultar el que és recent; S3 és l'arxiu legal. Duplicar 7 anys a CloudWatch Logs costaria una fortuna sense aportar res.
El rol que CloudTrail necessita:
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": { "Service": "cloudtrail.amazonaws.com" },
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {
"aws:SourceArn": "arn:aws:cloudtrail:eu-west-1:111122223333:trail/trail-mercadofresco"
}
}
}]
}Amb la política de permisos:
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": ["logs:CreateLogStream", "logs:PutLogEvents"],
"Resource": "arn:aws:logs:eu-west-1:111122223333:log-group:/aws/cloudtrail/mercadofresco:log-stream:*"
}]
}Cost: la ingesta dels esdeveniments de gestió de MercadoFresco són uns 0,4 GB al mes: 0,25 USD. Si activessis esdeveniments de dades d'alt volum, això es dispararia; és una altra raó per als selectors restrictius.
Filtres de mètriques i alarmes de seguretat
Aquí és on CloudTrail i CloudWatch es troben i es converteixen en detecció, no només en registre. Aquestes són les cinc alarmes que munta MercadoFresco, i les cinc són estàndard en qualsevol auditoria de seguretat (apareixen al CIS Benchmark d'AWS, que veurem a 05-04).
1. Ús del compte arrel. Després de 04-01, l'arrel no s'hauria de fer servir mai:
aws logs put-metric-filter \
--log-group-name /aws/cloudtrail/mercadofresco \
--filter-name filtro-uso-root \
--filter-pattern '{ $.userIdentity.type = "Root" && $.userIdentity.invokedBy NOT EXISTS && $.eventType != "AwsServiceEvent" }' \
--metric-transformations \
metricName=UsoDeRoot,metricNamespace=MercadoFresco/Seguridad,\
metricValue=1,defaultValue=0 \
--profile mercadofresco-dev --region eu-west-1
aws cloudwatch put-metric-alarm \
--alarm-name mercadofresco-uso-root \
--alarm-description "ALGU HA FET SERVIR EL COMPTE ARREL" \
--namespace MercadoFresco/Seguridad --metric-name UsoDeRoot \
--statistic Sum --period 300 --evaluation-periods 1 \
--threshold 0 --comparison-operator GreaterThanThreshold \
--treat-missing-data notBreaching \
--alarm-actions arn:aws:sns:eu-west-1:111122223333:alertas-mercadofresco \
--profile mercadofresco-dev --region eu-west-1Aquest $.userIdentity.invokedBy NOT EXISTS exclou els esdeveniments en què un servei d'AWS actua en
nom del compte, que són legítims i freqüents. Sense ell, l'alarma seria pur soroll.
2. Aturada o esborrat d'un trail. La primera acció de qualsevol que vulgui amagar el seu rastre:
aws logs put-metric-filter \
--log-group-name /aws/cloudtrail/mercadofresco \
--filter-name filtro-cambio-trail \
--filter-pattern '{ ($.eventName = "StopLogging") || ($.eventName = "DeleteTrail") || ($.eventName = "UpdateTrail") || ($.eventName = "PutEventSelectors") }' \
--metric-transformations \
metricName=CambiosEnTrail,metricNamespace=MercadoFresco/Seguridad,\
metricValue=1,defaultValue=0 \
--profile mercadofresco-dev --region eu-west-1Amb la seva alarma amb llindar 0, exactament igual que l'anterior. Aquesta és l'alarma més important d'aquesta lliçó: és la que avisa que el sistema d'auditoria està sent atacat.
3. Ús de KMS sobre les còpies de la base de dades. La que respon directament a la pregunta del mòdul 4, però en calent:
aws logs put-metric-filter \
--log-group-name /aws/cloudtrail/mercadofresco \
--filter-name filtro-descifrado-copias \
--filter-pattern '{ $.eventSource = "kms.amazonaws.com" && $.eventName = "Decrypt" && $.requestParameters.encryptionContext."aws:rds:db-id" = "*mercadofresco-pedidos*" && $.userIdentity.type != "AWSService" }' \
--metric-transformations \
metricName=DescifradoCopias,metricNamespace=MercadoFresco/Seguridad,\
metricValue=1,defaultValue=0 \
--profile mercadofresco-dev --region eu-west-1Aquest $.userIdentity.type != "AWSService" exclou el mateix RDS xifrant les seves còpies automàtiques
cada nit, que és la línia 1 de la nostra investigació. El que queda és activitat humana o d'script
sobre una còpia de la base de dades, i això sempre mereix un avís.
4. Accessos denegats repetits. El reguer que deixa el reconeixement:
aws logs put-metric-filter \
--log-group-name /aws/cloudtrail/mercadofresco \
--filter-name filtro-accesos-denegados \
--filter-pattern '{ ($.errorCode = "AccessDenied*") || ($.errorCode = "UnauthorizedOperation") }' \
--metric-transformations \
metricName=AccesosDenegados,metricNamespace=MercadoFresco/Seguridad,\
metricValue=1,defaultValue=0 \
--profile mercadofresco-dev --region eu-west-1
aws cloudwatch put-metric-alarm \
--alarm-name mercadofresco-accesos-denegados \
--alarm-description "Pic d'AccessDenied: possible reconeixement" \
--namespace MercadoFresco/Seguridad --metric-name AccesosDenegados \
--statistic Sum --period 300 --evaluation-periods 2 --datapoints-to-alarm 2 \
--threshold 20 --comparison-operator GreaterThanThreshold \
--treat-missing-data notBreaching \
--alarm-actions arn:aws:sns:eu-west-1:111122223333:alertas-mercadofresco \
--profile mercadofresco-dev --region eu-west-1Aquí el llindar no és 0, i la raó és honesta: els AccessDenied són constants en qualsevol
compte viu. Un desenvolupador provant, una eina que consulta alguna cosa que no pot, un SDK que
sondeja. 20 en cinc minuts sostinguts durant dos períodes sí que és un senyal.
5. Canvis en la política d'un grup de seguretat o a IAM.
aws logs put-metric-filter \
--log-group-name /aws/cloudtrail/mercadofresco \
--filter-name filtro-cambios-seguridad \
--filter-pattern '{ ($.eventName = "AuthorizeSecurityGroupIngress") || ($.eventName = "CreateAccessKey") || ($.eventName = "AttachRolePolicy") || ($.eventName = "PutBucketPolicy") || ($.eventName = "DeleteBucketPolicy") || ($.eventName = "PutKeyPolicy") }' \
--metric-transformations \
metricName=CambiosDeSeguridad,metricNamespace=MercadoFresco/Seguridad,\
metricValue=1,defaultValue=0 \
--profile mercadofresco-dev --region eu-west-1Amb llindar 0 en horari nocturn seria massa sorollós durant un desplegament diürn. MercadoFresco el posa a llindar 0 amb període de 5 minuts, acceptant el soroll: prefereix assabentar-se de tots els canvis de seguretat. Que l'alarma sigui informativa i no urgent és una decisió vàlida, sempre que estigui documentada i no acabi sent ignorada per costum.
Consultar amb Athena
Per investigar de debò cal consultar els fitxers d'S3 amb SQL. Amazon Athena executa SQL directament sobre els objectes d'un bucket, sense carregar res en cap base de dades.
Athena es veu amb més profunditat al mòdul 6 quan es parla d'analítica. Aquí en fem servir el just per investigar CloudTrail, que és el seu cas d'ús més comú.
Pas 1: la taula. La manera còmoda de crear-la és des de la consola de CloudTrail («Create Athena table»), que genera aquest DDL. Convé entendre'l:
CREATE DATABASE IF NOT EXISTS auditoria_mercadofresco;
CREATE EXTERNAL TABLE auditoria_mercadofresco.cloudtrail_mercadofresco (
eventVersion STRING,
userIdentity STRUCT<
type: STRING,
principalId: STRING,
arn: STRING,
accountId: STRING,
userName: STRING,
invokedBy: STRING,
accessKeyId: STRING,
sessionContext: STRUCT<
attributes: STRUCT<
mfaAuthenticated: STRING,
creationDate: STRING>,
sessionIssuer: STRUCT<
type: STRING,
principalId: STRING,
arn: STRING,
accountId: STRING,
userName: STRING>>>,
eventTime STRING,
eventSource STRING,
eventName STRING,
awsRegion STRING,
sourceIPAddress STRING,
userAgent STRING,
errorCode STRING,
errorMessage STRING,
requestParameters STRING,
responseElements STRING,
requestID STRING,
eventID STRING,
readOnly STRING,
resources ARRAY<STRUCT<
arn: STRING,
accountId: STRING,
type: STRING>>,
eventType STRING,
recipientAccountId STRING,
vpcEndpointId STRING
)
PARTITIONED BY (region STRING, anio STRING, mes STRING, dia STRING)
ROW FORMAT SERDE 'com.amazon.emr.hive.serde.CloudTrailSerde'
STORED AS INPUTFORMAT 'com.amazon.emr.cloudtrail.CloudTrailInputFormat'
OUTPUTFORMAT 'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat'
LOCATION 's3://mercadofresco-auditoria-cloudtrail/AWSLogs/111122223333/CloudTrail/';Dues coses que cal veure en aquest DDL:
requestParametersiresponseElementssónSTRING, no estructures. El seu contingut varia segons l'esdeveniment, així que es consulten amb funcions JSON:json_extract_scalar(requestParameters, '$.keyId').PARTITIONED BYés el que decideix el cost. Athena cobra 5 USD per TB escanejat. Sense particions, cada consulta llegeix tots els fitxers del bucket. Amb particions i unWHEREsobre elles, llegeix només els dies que demanis.
Pas 2: registrar les particions. Amb la projecció de particions s'automatitza:
ALTER TABLE auditoria_mercadofresco.cloudtrail_mercadofresco
SET TBLPROPERTIES (
'projection.enabled' = 'true',
'projection.region.type' = 'enum',
'projection.region.values' = 'eu-west-1,us-east-1,eu-central-1',
'projection.anio.type' = 'integer',
'projection.anio.range' = '2026,2030',
'projection.mes.type' = 'integer',
'projection.mes.range' = '1,12',
'projection.mes.digits' = '2',
'projection.dia.type' = 'integer',
'projection.dia.range' = '1,31',
'projection.dia.digits' = '2',
'storage.location.template' =
's3://mercadofresco-auditoria-cloudtrail/AWSLogs/111122223333/CloudTrail/${region}/${anio}/${mes}/${dia}'
);Sense això caldria executar MSCK REPAIR TABLE o ALTER TABLE ADD PARTITION cada dia. Amb la
projecció, Athena dedueix les particions del patró de rutes.
Les consultes que responen preguntes reals
1. Qui va assumir rol-mercadofresco-tienda, i quan?
SELECT eventtime,
userIdentity.arn AS qui,
json_extract_scalar(requestParameters, '$.roleSessionName') AS sessio,
sourceipaddress,
useragent,
errorcode
FROM auditoria_mercadofresco.cloudtrail_mercadofresco
WHERE anio = '2026' AND mes = '07'
AND eventname = 'AssumeRole'
AND json_extract_scalar(requestParameters, '$.roleArn')
LIKE '%rol-mercadofresco-tienda%'
ORDER BY eventtime DESC
LIMIT 100;2. Qui va llegir el secret de mfadmin? La pregunta que vam deixar pendent a 04-03:
SELECT eventtime,
COALESCE(userIdentity.sessionContext.sessionIssuer.userName,
userIdentity.userName) AS identitat,
split_part(userIdentity.arn, '/', 3) AS sessio,
sourceipaddress,
userIdentity.sessionContext.attributes.mfaAuthenticated AS amb_mfa,
errorcode
FROM auditoria_mercadofresco.cloudtrail_mercadofresco
WHERE anio = '2026' AND mes = '07'
AND eventsource = 'secretsmanager.amazonaws.com'
AND eventname = 'GetSecretValue'
AND json_extract_scalar(requestParameters, '$.secretId')
LIKE '%mercadofresco/produccion/rds/mfadmin%'
ORDER BY eventtime DESC;El que s'espera en el resultat: el rol de la botiga llegint-lo en arrencar cada instància, i
rol-rotacion-mfadmin cada 30 dies fent la rotació (04-03). Qualsevol altra identitat és una
troballa.
3. Totes les crides denegades, agrupades. La consulta de reconeixement:
SELECT COALESCE(userIdentity.sessionContext.sessionIssuer.userName,
userIdentity.userName, userIdentity.type) AS identitat,
sourceipaddress,
eventsource,
eventname,
count(*) AS intents,
min(eventtime) AS primer,
max(eventtime) AS ultim
FROM auditoria_mercadofresco.cloudtrail_mercadofresco
WHERE anio = '2026' AND mes = '07'
AND errorcode IN ('AccessDenied', 'AccessDeniedException', 'UnauthorizedOperation')
GROUP BY 1, 2, 3, 4
HAVING count(*) > 5
ORDER BY intents DESC
LIMIT 50;Aquesta consulta té un ús doble molt valuós, i convé subratllar-ho:
- Seguretat: una identitat desconeguda amb 400
AccessDeniedsobre 30 serveis diferents en deu minuts és reconeixement. Algú està mapant el que pot fer. - Operació:
rol-mercadofresco-tiendaamb 3.000AccessDeniedsobres3:GetObjectsignifica que apol-mercadofresco-tiendali falta un permís i hi ha una funcionalitat trencada que ningú no ha reportat. ElsAccessDeniedsón també un detector d'errors de configuració.
4. Activitat fora d'horari, que és la que sol delatar.
SELECT eventtime, eventname, eventsource,
COALESCE(userIdentity.sessionContext.sessionIssuer.userName,
userIdentity.userName) AS identitat,
sourceipaddress
FROM auditoria_mercadofresco.cloudtrail_mercadofresco
WHERE anio = '2026' AND mes = '07'
AND readonly = 'false'
AND userIdentity.type != 'AWSService'
AND (hour(from_iso8601_timestamp(eventtime)) < 7
OR hour(from_iso8601_timestamp(eventtime)) > 21)
ORDER BY eventtime DESC;Filtrar userIdentity.type != 'AWSService' és imprescindible: si no, la sortida s'omple de còpies
automàtiques, instantànies de DLM i activitat de l'ASG a les 3 de la matinada, que és completament
normal.
5. Totes les IP d'origen, per detectar les desconegudes.
SELECT sourceipaddress,
count(*) AS crides,
count(DISTINCT eventname) AS accions_diferents,
array_agg(DISTINCT COALESCE(userIdentity.sessionContext.sessionIssuer.userName,
userIdentity.userName)) AS identitats,
min(eventtime) AS primera, max(eventtime) AS ultima
FROM auditoria_mercadofresco.cloudtrail_mercadofresco
WHERE anio = '2026' AND mes = '07'
AND userIdentity.type != 'AWSService'
AND sourceipaddress NOT LIKE '10.%'
GROUP BY sourceipaddress
ORDER BY crides DESC;Una IP que apareix per primer cop, fa 12 crides i desapareix és molt més sospitosa que una que en fa 40.000 des de fa mesos.
6. La consulta de la còpia desxifrada, ara en SQL.
SELECT eventtime,
COALESCE(userIdentity.sessionContext.sessionIssuer.userName,
userIdentity.userName) AS identitat,
split_part(userIdentity.arn, '/', 3) AS sessio,
sourceipaddress,
json_extract_scalar(requestParameters, '$.encryptionContext."aws:rds:backup-id"') AS copia
FROM auditoria_mercadofresco.cloudtrail_mercadofresco
WHERE anio = '2026' AND mes = '07'
AND eventsource = 'kms.amazonaws.com'
AND eventname = 'Decrypt'
AND userIdentity.type != 'AWSService'
AND requestParameters LIKE '%mercadofresco-pedidos%'
ORDER BY eventtime DESC;Control de cost a Athena: cada consulta cobra per TB escanejat. Posar sempre el WHERE de
partició (anio, mes, dia) és la diferència entre 0,01 USD i uns quants euros per consulta. I
es pot fixar un límit dur per grup de treball:
aws athena update-work-group --work-group primary \
--configuration-updates 'BytesScannedCutoffPerQuery=10737418240' \
--profile mercadofresco-dev --region eu-west-1Deu GiB màxim per consulta. Si algú escriu un SELECT * sense WHERE, la consulta es cancel·la sola
en lloc de generar una factura.
CloudTrail Lake
CloudTrail Lake és l'alternativa gestionada a muntar Athena a mà: un magatzem d'esdeveniments gestionat per AWS, amb SQL directe des de la consola de CloudTrail i sense taules per crear.
| S3 + Athena | CloudTrail Lake | |
|---|---|---|
| Configuració | Bucket, política, taula, particions | Una comanda |
| Consulta | SQL (Presto) des d'Athena | SQL des de CloudTrail |
| Retenció | La que vulguis a S3 | Fins a 10 anys |
| Cost d'ingesta | Només S3 (cèntims) | ~2,50 USD/GB |
| Cost de consulta | 5 USD/TB escanejat | 0,005 USD/GB escanejat |
| Cost total | Molt menor | Major |
| Esforç | Mitjà | Mínim |
| Integració | Te la muntes tu | Nativa amb Organizations |
aws cloudtrail create-event-data-store \
--name lake-mercadofresco \
--retention-period 2555 \
--multi-region-enabled \
--advanced-event-selectors '[{
"Name": "Gestio",
"FieldSelectors": [{ "Field": "eventCategory", "Equals": ["Management"] }]
}]' \
--profile mercadofresco-dev --region eu-west-1La decisió de MercadoFresco: es queda amb S3 + Athena. Amb 0,4 GB mensuals d'esdeveniments, Lake costaria 1 USD al mes d'ingesta —no és dramàtic— però el bucket d'S3 ja existeix, ja està protegit amb Object Lock i ja alimenta l'arxiu legal de 7 anys. Lake s'anota com a opció a reconsiderar si el compte creix i muntar particions a mà comença a fer mal.
Amb molts comptes i equips que no són de plataforma, Lake guanya clarament: ningú no ha d'aprendre a crear taules externes a Athena per investigar un incident.
Investigació d'un incident, pas a pas
Tornem a la troballa: rol-restauracion-copias va desxifrar la còpia snapshot-2026-07-27 a les 03:42
des de 198.51.100.77, sense MFA. Aquest és el procediment complet, i serveix com a plantilla per a
qualsevol investigació.
Pas 0. Congelar l'evidència. Abans de res.
# Verificar que els registres no han estat manipulats
aws cloudtrail validate-logs \
--trail-arn arn:aws:cloudtrail:eu-west-1:111122223333:trail/trail-mercadofresco \
--start-time 2026-07-27T00:00:00Z --end-time 2026-07-29T00:00:00Z \
--profile mercadofresco-dev --region eu-west-1
# Copiar els fitxers del periode a un prefix d'investigacio
aws s3 sync \
s3://mercadofresco-auditoria-cloudtrail/AWSLogs/111122223333/CloudTrail/eu-west-1/2026/07/28/ \
s3://mercadofresco-auditoria-cloudtrail/investigaciones/inc-2026-07-28/ \
--profile mercadofresco-devAixò es fa primer i sempre. Si l'incident acaba en un procediment formal, el que importa no és el que vas descobrir, sinó que puguis demostrar que les dades no van canviar mentre investigaves.
Pas 1. Qui va assumir el rol? El nom de sessió sesion-marta-copias és text lliure triat per
qui va fer la crida. Cal buscar l'AssumeRole:
SELECT eventtime, userIdentity.arn AS qui_el_va_assumir,
userIdentity.type, sourceipaddress, useragent,
userIdentity.sessionContext.attributes.mfaAuthenticated AS amb_mfa,
json_extract_scalar(requestParameters, '$.roleSessionName') AS nom_sessio
FROM auditoria_mercadofresco.cloudtrail_mercadofresco
WHERE anio='2026' AND mes='07' AND dia='28'
AND eventname = 'AssumeRole'
AND json_extract_scalar(requestParameters, '$.roleArn') LIKE '%rol-restauracion-copias%'
ORDER BY eventtime;Resultat: arn:aws:iam::111122223333:user/luis.dev, a les 03:41:52, des de 198.51.100.77,
mfaAuthenticated: false, agent aws-cli/2.15.30.
Pas 2. Reconstruir tota la sessió. L'accessKeyId temporal (ASIA...) identifica la sessió
completa:
SELECT eventtime, eventsource, eventname, errorcode,
json_extract_scalar(requestParameters, '$.dBInstanceIdentifier') AS instancia
FROM auditoria_mercadofresco.cloudtrail_mercadofresco
WHERE anio='2026' AND mes='07' AND dia='28'
AND userIdentity.accessKeyId = 'ASIA1234567890ABCDEF'
ORDER BY eventtime;Resultat:
| Hora | Esdeveniment | Resultat |
|---|---|---|
| 03:41:52 | AssumeRole |
OK |
| 03:42:03 | DescribeDBSnapshots |
OK |
| 03:42:17 | kms:Decrypt |
OK |
| 03:42:19 | RestoreDBInstanceFromDBSnapshot |
OK — instància pedidos-copia-luis |
| 03:58:44 | CreateDBSnapshot |
AccessDenied |
| 04:30:12 | DeleteDBInstance |
OK |
Pas 3. Interpretar. El patró és coherent i força llegible: algú va restaurar una còpia, va
intentar crear una instantània nova (denegat), i al cap de 45 minuts va esborrar la instància
restaurada. No hi ha exfiltració visible —cap crida a S3, cap CreateDBSnapshot amb èxit, cap
instància deixada en marxa—. Sembla feina legítima feta malament: restaurar una còpia per
comprovar alguna cosa, de matinada, sense avisar i sense MFA.
Pas 4. Confirmar amb la persona. I aquí una advertència important, perquè és on més s'equivoca la gent: CloudTrail diu què va passar, no per què. No s'acusa mai ningú amb un registre a la mà sense parlar-hi primer. La conversa amb el Luis aclareix que estava comprovant si una còpia era restaurable després d'un avís de la Sara, i que ho va fer de matinada per no carregar la base de dades en hores de producció. Intenció correcta, procediment incorrecte.
Pas 5. Comprovar que no hi ha res més. Encara que l'explicació quadri, es verifica:
SELECT eventtime, eventname, sourceipaddress
FROM auditoria_mercadofresco.cloudtrail_mercadofresco
WHERE anio='2026' AND mes='07'
AND sourceipaddress = '198.51.100.77'
ORDER BY eventtime;Si aquesta IP només apareix en horari de feina del Luis durant mesos, és casa seva o la seva oficina. Si apareix una vegada, a les 3 de la matinada, i mai més, la conversa és una altra.
Pas 6. Accions correctives. Sense culpables i amb dates, igual que el post-mortem de 04-04:
| Acció | Responsable | Termini |
|---|---|---|
Exigir MFA per assumir rol-restauracion-copias (condició aws:MultiFactorAuthPresent, 04-01) |
Marta | 3 dies |
Alarma mercadofresco-descifrado-copias, ja creada, verificada amb simulacre |
Marta | 1 dia |
| Procediment escrit: restaurar còpies es fa en preproducció i s'avisa | Marta | 1 setmana |
| Prova de restauració programada i automatitzada trimestral | Luis | 1 mes |
Etiquetar Entorno=temporal les instàncies restaurades i esborrar-les soles a les 8 h |
Luis | 1 mes |
Fixa't en l'última fila i en l'anterior: la resposta correcta a «el Luis va restaurar una còpia d'amagat» no és prohibir-ho. És fer que provar restauracions sigui fàcil, visible i rutinari. Una prova de restauració és una pràctica excel·lent que a 02-04 vam recomanar explícitament; el problema era el procediment, no l'activitat.
Relació amb IAM Access Analyzer
CloudTrail no només serveix per investigar: serveix per construir permisos correctes. IAM Access Analyzer pot llegir el teu historial de CloudTrail i generar una política de privilegi mínim amb exactament les accions que una identitat ha fet servir de debò en un període.
aws accessanalyzer start-policy-generation \
--policy-generation-details '{
"principalArn": "arn:aws:iam::111122223333:role/rol-mercadofresco-tienda"
}' \
--cloud-trail-details '{
"trails": [{
"cloudTrailArn": "arn:aws:cloudtrail:eu-west-1:111122223333:trail/trail-mercadofresco",
"regions": ["eu-west-1"],
"allRegions": false
}],
"accessRole": "arn:aws:iam::111122223333:role/rol-access-analyzer",
"startTime": "2026-06-01T00:00:00Z",
"endTime": "2026-07-31T00:00:00Z"
}' \
--profile mercadofresco-dev --region eu-west-1Això tanca el cercle amb 04-01. Allà vam escriure pol-mercadofresco-tienda a mà, raonant sobre
què havia de poder fer la botiga. Amb dos mesos de CloudTrail, Access Analyzer diu què va fer de
debò, i la diferència entre les dues llistes és un regal:
- Accions que són a la política i no es fan servir mai: candidates a treure.
- Accions que l'aplicació intenta i li són denegades: funcionalitat trencada que ningú no va reportar.
Access Analyzer fa més coses —detectar recursos accessibles des de fora del compte, validar polítiques— però aquesta és la seva relació directa amb CloudTrail i amb el que vam aprendre a 04-01.
Retenció, cost i neteja
| Concepte | Preu | MercadoFresco |
|---|---|---|
| Historial d'esdeveniments, 90 dies | Gratis | — |
| Primera còpia d'esdeveniments de gestió | Gratis | 0,00 USD |
| Còpies addicionals d'esdeveniments de gestió | 2,00 USD / 100.000 | 0,00 USD (només un trail) |
| Esdeveniments de dades | 0,10 USD / 100.000 | 0,06 USD (amb selectors) |
| Insights | 0,35 USD / 100.000 analitzats | 0,53 USD |
| Emmagatzematge a S3 | 0,023 USD/GB/mes | ~0,05 USD |
| Ingesta a CloudWatch Logs | ~0,63 USD/GB | 0,25 USD |
| Alarmes (5) | 0,10 USD cadascuna | 0,50 USD |
| Athena | 5 USD/TB escanejat | ~0,20 USD |
| Total | ~1,59 USD/mes |
És de les coses més barates que es poden muntar a AWS, i l'argument definitiu: la primera còpia dels esdeveniments de gestió multiregió és gratuïta. No hi ha cap excusa tècnica ni econòmica per no tenir un trail. Que un compte de producció no en tingui és sempre una troballa d'auditoria.
Cicle de vida a S3, perquè els 7 anys no es paguin a preu d'emmagatzematge estàndard:
{
"Rules": [{
"ID": "auditoria-a-frio",
"Status": "Enabled",
"Filter": { "Prefix": "AWSLogs/" },
"Transitions": [
{ "Days": 90, "StorageClass": "STANDARD_IA" },
{ "Days": 365, "StorageClass": "GLACIER_IR" },
{ "Days": 730, "StorageClass": "DEEP_ARCHIVE" }
]
}]
}Compte amb dues coses: DEEP_ARCHIVE triga fins a 12 hores a restaurar, cosa que pot ser
inacceptable en una investigació urgent —per això els dos primers anys es queden en classes d'accés
ràpid—, i si has activat Object Lock en mode COMPLIANCE, la regla de cicle de vida no pot
expirar objectes abans que venci la retenció. Els pot canviar de classe, no esborrar-los.
Neteja, si has muntat això només per practicar:
# Aturar i esborrar el trail
aws cloudtrail stop-logging --name trail-mercadofresco \
--profile mercadofresco-dev --region eu-west-1
aws cloudtrail delete-trail --name trail-mercadofresco \
--profile mercadofresco-dev --region eu-west-1
# Alarmes i filtres
aws cloudwatch delete-alarms --alarm-names \
mercadofresco-uso-root mercadofresco-cambio-trail \
mercadofresco-accesos-denegados mercadofresco-descifrado-copias \
mercadofresco-cambios-seguridad \
--profile mercadofresco-dev --region eu-west-1
# Grup de registres
aws logs delete-log-group --log-group-name /aws/cloudtrail/mercadofresco \
--profile mercadofresco-dev --region eu-west-1
# Lake, si el vas crear: primer treure la proteccio d'esborrat
aws cloudtrail update-event-data-store \
--event-data-store <ARN> --no-termination-protection-enabled \
--profile mercadofresco-dev --region eu-west-1
aws cloudtrail delete-event-data-store --event-data-store <ARN> \
--profile mercadofresco-dev --region eu-west-1El bucket no s'esborra a la lleugera. Si vas activar Object Lock en mode
COMPLIANCE, els objectes no es poden esborrar fins que venci la retenció —ni tu, ni el compte arrel, ni el suport d'AWS—, i el bucket no es pot eliminar mentre contingui objectes. És una propietat desitjada, no una fallada. Practica Object Lock en un compte de proves i amb retencions de dies, mai d'anys.
Errors Habituals i Consells
1. Crear el trail i no cridar start-logging. create-trail no arrenca el registre. Comprova
sempre amb get-trail-status que IsLogging és true.
2. Trail d'una sola regió. Un atacant crea recursos on ningú no mira. La multiregió és gratis i no té contrapartida.
3. Un bucket d'auditoria que es pot esborrar. Sense política de Deny, sense versionat i sense
Object Lock, el registre dura el que trigui algú a executar una comanda. I el nivell definitiu és un
compte separat (09-04).
4. Activar esdeveniments de dades sense selectors. Registrar tots els GetObject d'un bucket de
fotos són desenes d'USD al mes per dades inútils. Selectors per prefix i readOnly: false.
5. No posar nom de sessió en assumir un rol. CloudTrail dirà «algú amb aquest rol», i no hi haurà
manera de saber qui. --role-session-name amb el nom de la persona.
6. Buscar a CloudTrail el que diu la teva aplicació. No hi és. CloudTrail registra crides a l'API d'AWS; els errors del teu codi són a CloudWatch Logs (05-01).
7. Consultes d'Athena sense WHERE de partició. Escanegen el bucket sencer a 5 USD per TB. Posa
sempre anio, mes i dia, i fixa BytesScannedCutoffPerQuery al grup de treball.
8. Ignorar els AccessDenied. Són alhora el detector de reconeixement i el detector de
permisos mal configurats. Revisar-los setmanalment troba coses trencades que ningú no va reportar.
9. No verificar mai la integritat. validate-logs un cop al mes, amb la sortida desada. Sense
això, la validació està activada però no serveix de res.
10. Retenció infinita a CloudWatch Logs per als esdeveniments de CloudTrail. L'arxiu llarg va a S3, que costa 20 vegades menys. A Logs, 90 dies.
11. Acusar algú amb un registre a la mà. CloudTrail diu què va passar, no per què. Cal investigar, contrastar i després parlar amb la persona. Un incident mal gestionat fa més mal que l'incident.
12. Activar Object Lock en mode COMPLIANCE amb 7 anys «per provar». No hi ha marxa enrere.
Practica amb GOVERNANCE i amb retencions de dies.
13. Suposar que el nom de sessió identifica algú. És una cadena lliure. La identitat real
és a l'esdeveniment AssumeRole corresponent.
Consell final: revisa CloudTrail quan no hi ha incidents. Mitja hora al mes executant les
consultes d'IP desconegudes, AccessDenied agrupats i activitat fora d'horari troba coses
—permisos trencats, processos zombis, claus oblidades d'un antic proveïdor— que d'altra manera només
apareixen el dia que hi ha un problema de debò.
Exercicis
Els tres exercicis treballen sobre la taula auditoria_mercadofresco.cloudtrail_mercadofresco i el
trail trail-mercadofresco definits en aquesta lliçó.
Exercici 1: dissenyar l'estratègia d'esdeveniments de dades
MercadoFresco vol activar esdeveniments de dades, però amb criteri i amb un pressupost de 5 USD al mes. Volum mensual real:
| Recurs | Operacions/mes | Repartiment |
|---|---|---|
mercadofresco-catalogo-fotos |
40.000.000 | 99,9 % lectures |
mercadofresco-copias-basedatos |
2.400 | 60 % escriptures |
mercadofresco-informes-analitica |
180.000 | 70 % lectures (Sara) |
mercadofresco-registros-web |
8.000.000 | 99 % escriptures (l'ALB) |
Lambda mercadofresco-generar-miniaturas |
620.000 invocacions | — |
Lambda mercadofresco-estado-pedido |
660.000 invocacions | — |
Requisits: cal poder saber sempre qui ha llegit o tocat una còpia de la base de dades; cal detectar si algú descarrega massivament els informes de negoci; cal detectar si algú altera les fotos del catàleg; el pressupost és de 5 USD.
Escriu els selectors avançats complets en JSON, calcula el cost de cadascun, justifica què deixes fora i per què, i proposa quina alarma muntaries sobre els esdeveniments de dades que sí que registres.
Exercici 2: la investigació completa
Un dilluns al matí, l'alarma mercadofresco-accesos-denegados ha saltat dues vegades durant el
cap de setmana. Aquestes són les dades inicials:
- Dissabte 02:14 a 02:31: 340 esdeveniments amb
errorCode = AccessDenied. - Tots des de
sourceIPAddress = 203.0.113.201. - Tots amb
userIdentity.type = "IAMUser",userName = "integracion-proveedor". userAgent:aws-cli/2.9.19 Python/3.9.11 Windows/10.- Els serveis afectats:
iam,s3,ec2,rds,secretsmanager,kms,organizations. - Diumenge 22:40: 6 esdeveniments sense
errorCodedes de la mateixa IP i el mateix usuari:ListBuckets,GetBucketLocation,ListObjectsV2sobremercadofresco-informes-analitica,GetObject× 3.
Escriu: les consultes SQL exactes que executaries i en quin ordre; quina conclusió treus de cadascuna; quines accions de contenció prendries i en quin ordre exacte; i què et diu el fet que les 340 crides fallessin però 6 tinguessin èxit. Indica també quina informació no podràs obtenir de CloudTrail i on la buscaries.
Exercici 3: convertir CloudTrail en detecció proactiva
La Marta vol passar d'investigar després a detectar en el moment. Defineix cinc deteccions addicionals a les cinc alarmes de la lliçó, orientades al risc real de MercadoFresco:
- Algú crea una clau d'accés permanent per a un usuari IAM (després de 04-01, ningú no ho hauria de fer).
- Algú modifica la política de la clau
alias/mercadofresco-datos. - Un rol d'aplicació es fa servir des d'una IP fora de la VPC.
- Algú desactiva el xifratge d'un bucket o activa l'accés públic.
- Es crea un recurs en una regió que MercadoFresco no fa servir.
Per a cadascuna: escriu el patró del filtre de mètriques, decideix el llindar i els períodes de l'alarma, digues si ha de despertar algú de matinada o només generar un correu, i explica quin fals positiu esperes i com l'evitaries. Per a les que no es puguin resoldre amb un filtre de mètriques, indica quina eina faries servir (i avança quina serà la lliçó que la cobreixi).
Solucions
Solució 1
Càlcul de partida. Registrar-ho tot:
| Recurs | Esdeveniments | Cost |
|---|---|---|
| Catàleg | 40.000.000 | 40,00 USD |
| Registres web | 8.000.000 | 8,00 USD |
| Informes | 180.000 | 0,18 USD |
| Lambdes | 1.280.000 | 1,28 USD |
| Còpies | 2.400 | 0,00 USD |
| Total | 49,5 M | 49,46 USD |
Deu vegades el pressupost, i amb el 97 % de la despesa en lectures de fotos de tomàquets.
Selectors proposats:
[
{
"Name": "Gestio completa",
"FieldSelectors": [
{ "Field": "eventCategory", "Equals": ["Management"] }
]
},
{
"Name": "Copies de base de dades: TOT",
"FieldSelectors": [
{ "Field": "eventCategory", "Equals": ["Data"] },
{ "Field": "resources.type", "Equals": ["AWS::S3::Object"] },
{ "Field": "resources.ARN", "StartsWith": ["arn:aws:s3:::mercadofresco-copias-basedatos/"] }
]
},
{
"Name": "Informes de negoci: TOT",
"FieldSelectors": [
{ "Field": "eventCategory", "Equals": ["Data"] },
{ "Field": "resources.type", "Equals": ["AWS::S3::Object"] },
{ "Field": "resources.ARN", "StartsWith": ["arn:aws:s3:::mercadofresco-informes-analitica/"] }
]
},
{
"Name": "Cataleg: NOMES escriptures",
"FieldSelectors": [
{ "Field": "eventCategory", "Equals": ["Data"] },
{ "Field": "resources.type", "Equals": ["AWS::S3::Object"] },
{ "Field": "resources.ARN", "StartsWith": ["arn:aws:s3:::mercadofresco-catalogo-fotos/"] },
{ "Field": "readOnly", "Equals": ["false"] }
]
}
]Cost resultant:
| Selector | Esdeveniments/mes | Cost |
|---|---|---|
| Gestió | ~150.000 | 0,00 USD (primera còpia gratis) |
| Còpies de BD | 2.400 | 0,002 USD |
| Informes | 180.000 | 0,18 USD |
| Catàleg, només escriptures (0,1 %) | ~40.000 | 0,04 USD |
| Total | 222.400 | ~0,22 USD/mes |
Molt per sota dels 5 USD i complint els tres requisits.
Què queda fora i per què:
- Lectures del catàleg (40 M): són fotos públiques de producte servides per CloudFront. Llegir-les no té cap valor de seguretat i costa 40 USD. Si algun dia calgués analitzar patrons d'accés, hi ha els registres d'accés d'S3 i els de CloudFront, molt més barats.
mercadofresco-registros-web(8 M): és l'ALB escrivint els seus propis registres. Registrar que l'ALB escriu els seus registres és recursiu i inútil: 8 USD per confirmar el que ja sabem.- Invocacions de Lambda (1,28 M): 1,28 USD, i ja tenim millor visibilitat d'aquestes funcions amb
les mètriques d'
AWS/Lambda, els seus registres i les traces d'X-Ray (05-02). CloudTrail només diria «es va invocar», que és el menys informatiu de tot.
Alarmes sobre els esdeveniments de dades que sí que registrem:
# 1. Qualsevol lectura humana d'una copia de la base de dades
aws logs put-metric-filter \
--log-group-name /aws/cloudtrail/mercadofresco \
--filter-name filtro-lectura-copias \
--filter-pattern '{ $.eventName = "GetObject" && $.requestParameters.bucketName = "mercadofresco-copias-basedatos" && $.userIdentity.type != "AWSService" }' \
--metric-transformations \
metricName=LecturaCopiasBD,metricNamespace=MercadoFresco/Seguridad,\
metricValue=1,defaultValue=0 \
--profile mercadofresco-dev --region eu-west-1Amb llindar 0: ningú no té per què descarregar una còpia de la base de dades sense que algú se n'assabenti.
# 2. Descarrega massiva d'informes: llindar, no zero
aws cloudwatch put-metric-alarm \
--alarm-name mercadofresco-descarga-masiva-informes \
--namespace MercadoFresco/Seguridad --metric-name DescargaInformes \
--statistic Sum --period 300 --evaluation-periods 1 \
--threshold 500 --comparison-operator GreaterThanThreshold \
--treat-missing-data notBreaching \
--alarm-actions arn:aws:sns:eu-west-1:111122223333:alertas-mercadofresco \
--profile mercadofresco-dev --region eu-west-1Aquí el llindar no pot ser 0: la Sara llegeix informes cada dia, és la seva feina. 500 objectes en cinc minuts no és llegir un informe, és descarregar-se el magatzem sencer.
# 3. Escriptures al cataleg fora del proces normal
# Nomes rol-mercadofresco-tienda i rol-lambda-miniaturas hi han d'escriure.
--filter-pattern '{ ($.eventName = "PutObject" || $.eventName = "DeleteObject") && $.requestParameters.bucketName = "mercadofresco-catalogo-fotos" && $.userIdentity.sessionContext.sessionIssuer.userName != "rol-mercadofresco-tienda" && $.userIdentity.sessionContext.sessionIssuer.userName != "rol-lambda-miniaturas" }'Aquest últim és el patró més valuós dels tres: llista blanca d'identitats esperades, alarma sobre tota la resta. És més robust que enumerar el que és dolent, perquè cobreix també el que no has previst.
Solució 2
Lectura inicial de les dades. Tres senyals molt clars, abans d'executar cap consulta:
- 340
AccessDenieden 17 minuts sobre 7 serveis diferents, incloent-hiiamiorganizations. Això no és una aplicació amb un permís mal posat: una aplicació falla sempre a la mateixa crida. Això és enumeració sistemàtica de permisos. IAMUseramb clau permanent, exactament el que 04-01 diu que no ha d'existir. Un usuari d'«integració amb proveïdor» és el vector clàssic: clau que es va crear una vegada, es va posar en un fitxer de configuració, i que ningú no ha rotat en tres anys.- 20 hores de silenci i després 6 crides amb èxit, quirúrgiques, sobre els informes de negoci. Aquest espaiat és el patró: primer es mapa què es pot fer, després es torna a buscar el que funciona.
Consulta 1: tota l'activitat d'aquest usuari, no només la del cap de setmana.
SELECT eventtime, eventsource, eventname, errorcode,
sourceipaddress, useragent, awsregion
FROM auditoria_mercadofresco.cloudtrail_mercadofresco
WHERE anio = '2026'
AND userIdentity.userName = 'integracion-proveedor'
ORDER BY eventtime DESC;Què busco: quan va començar tot, si aquella IP havia aparegut abans, i què feia aquest usuari
normalment. Si la seva activitat legítima és un PutObject diari a les 06:00 des d'una altra IP, tot
el del cap de setmana és intrusió.
Consulta 2: què va tenir èxit. És l'únic que importa de debò.
SELECT eventtime, eventsource, eventname,
json_extract_scalar(requestParameters, '$.bucketName') AS bucket,
json_extract_scalar(requestParameters, '$.key') AS objecte,
sourceipaddress
FROM auditoria_mercadofresco.cloudtrail_mercadofresco
WHERE anio = '2026' AND mes = '07'
AND userIdentity.userName = 'integracion-proveedor'
AND errorcode IS NULL
ORDER BY eventtime;Què busco: l'abast exacte de la bretxa. Els 340 errors són soroll; els 6 èxits són
l'incident. Aquí surt quins tres objectes concrets es van descarregar de
mercadofresco-informes-analitica, i això determina si hi ha dades personals implicades i si hi ha
obligació de notificar.
Consulta 3: la clau d'accés, per veure si es va fer servir des de més llocs.
SELECT sourceipaddress, useragent, awsregion,
count(*) AS crides,
min(eventtime) AS primera, max(eventtime) AS ultima
FROM auditoria_mercadofresco.cloudtrail_mercadofresco
WHERE anio = '2026'
AND userIdentity.accessKeyId = 'AKIA...'
GROUP BY 1, 2, 3
ORDER BY primera;Què busco: si la clau es fa servir també des de la IP legítima del proveïdor, està compromesa
però segueix en ús legítim, i desactivar-la trencarà una integració. Si només es fa servir des de
203.0.113.201 des de fa un mes, el proveïdor ja no la fa servir i desactivar-la no trenca res.
Consulta 4: d'on va sortir aquesta clau i quan.
SELECT eventtime, userIdentity.arn AS qui_la_va_crear, sourceipaddress
FROM auditoria_mercadofresco.cloudtrail_mercadofresco
WHERE eventname IN ('CreateAccessKey', 'CreateUser')
AND json_extract_scalar(responseElements, '$.accessKey.userName') = 'integracion-proveedor';Què busco: si la clau es va crear fa dos anys i mai no es va rotar, o si es va crear el divendres passat, cas en què el compromís és més profund: algú ja tenia accés i es va obrir una porta del darrere.
Consulta 5: correlació per IP, més enllà d'aquest usuari.
SELECT eventtime, userIdentity.arn, eventname, errorcode
FROM auditoria_mercadofresco.cloudtrail_mercadofresco
WHERE anio = '2026'
AND sourceipaddress = '203.0.113.201'
ORDER BY eventtime;Què busco: si aquesta IP ha fet servir altres identitats, l'abast és molt més gran.
Contenció, en aquest ordre exacte:
| # | Acció | Per què en aquest ordre |
|---|---|---|
| 1 | Desactivar la clau, no esborrar-la: aws iam update-access-key --status Inactive |
Talla l'accés conservant l'evidència. Esborrar-la destrueix informació |
| 2 | Congelar l'evidència: validate-logs + copiar els fitxers del període |
Abans de tocar res més |
| 3 | Revisar quins permisos tenia l'usuari i què hauria pogut fer | Defineix el pitjor cas possible |
| 4 | Determinar què es va descarregar exactament (consulta 2) | Decideix si hi ha notificació obligatòria |
| 5 | Avisar la direcció i compliment | Si hi ha dades personals, hi ha terminis legals |
| 6 | Buscar persistència: CreateUser, CreateAccessKey, CreateRole, PutUserPolicy en el període |
Un atacant deixa portes del darrere |
| 7 | Rotar credencials relacionades i revisar el secret de mfadmin |
Per si hi va haver moviment lateral |
| 8 | Només llavors, contactar amb el proveïdor | Pot ser que el compromès sigui ell |
Què significa que 340 fallessin i 6 tinguessin èxit. És la millor notícia possible: el privilegi mínim va funcionar. La feina de 04-01 és el que va impedir que aquelles 340 crides —a IAM, a KMS, a Secrets Manager, a RDS, a Organizations— fessin mal. La bretxa es va limitar als permisos que aquell usuari tenia legítimament, que era llegir informes.
I alhora és la pitjor notícia sobre el disseny: aquell usuari mai no hauria d'haver tingut una clau
permanent. L'accés d'un proveïdor s'ha de fer amb un rol assumible amb condició d'identitat
externa (sts:ExternalId, 04-01), amb credencials temporals que caduquen soles. Aquest és l'arranjament de fons.
El que CloudTrail NO et pot dir:
| Pregunta | On buscar |
|---|---|
| Què contenien exactament els objectes descarregats? | Al mateix bucket, mirant aquelles claus |
| Com es va filtrar la clau? | Repositoris de codi, portàtils, el proveïdor |
| Hi va haver accés a la base de dades amb credencials robades? | Registres de PostgreSQL (05-01) |
| Es va exfiltrar per xarxa des d'una instància? | VPC Flow Logs (03-01) |
| Qui és la persona darrere d'aquella IP? | Ningú a AWS. És feina policial |
| Es va modificar alguna cosa dins de l'aplicació? | Registres d'aplicació (05-01) |
Aquesta taula és la lliçó més important de l'exercici: CloudTrail és una peça de la investigació, no la investigació sencera. Dona el «qui, què, quan, des d'on» del pla de control d'AWS. Tota la resta és en altres fonts, i per això aquest mòdul sencer té sentit com a conjunt.
Solució 3
Detecció 1: creació de clau d'accés permanent.
{ ($.eventName = "CreateAccessKey") || ($.eventName = "CreateUser") || ($.eventName = "CreateLoginProfile") }| Paràmetre | Valor | Justificació |
|---|---|---|
| Llindar | 0 | Després de 04-01, no se n'hauria de crear cap |
| Període / avaluació | 300 s / 1 | Immediat |
| Desperta? | Sí | És el pas clàssic de persistència després d'un compromís |
| Fals positiu esperat | Alta legítima d'un usuari nou | S'avisa abans pel canal de l'equip; l'avís es marca com a esperat |
Detecció 2: canvi en la política de la clau KMS.
{ ($.eventSource = "kms.amazonaws.com") && (($.eventName = "PutKeyPolicy") || ($.eventName = "ScheduleKeyDeletion") || ($.eventName = "DisableKey") || ($.eventName = "DisableKeyRotation")) }| Paràmetre | Valor | Justificació |
|---|---|---|
| Llindar | 0 | Canviar la política d'alias/mercadofresco-datos és un esdeveniment d'altíssim impacte |
| Desperta? | Sí, sempre | ScheduleKeyDeletion sobre aquesta clau deixaria illegibles les còpies i la base de dades. És l'esdeveniment més destructiu de tot el compte |
| Fals positiu | Canvi planificat | Es fa amb avís previ i s'anota |
Val la pena subratllar-ho: a 04-02 vam veure que esborrar una clau KMS té un període d'espera de 7 a 30 dies precisament per donar temps a reaccionar. Aquesta alarma és el que converteix aquell període d'espera en una defensa real: sense ella, l'espera passa sense que ningú se n'assabenti.
Detecció 3: rol d'aplicació fet servir des de fora de la VPC.
Un filtre de mètriques no pot fer això bé. La sintaxi de patrons de CloudWatch Logs no admet comparacions de rang d'IP ni negació de prefixos de manera fiable. Es pot aproximar:
{ ($.userIdentity.sessionContext.sessionIssuer.userName = "rol-mercadofresco-tienda") && ($.sourceIPAddress != "10.0.*") }…però és fràgil i generarà falsos positius, perquè les crides a través d'un punt d'enllaç de VPC
(vpce-mercadofresco-s3) o des de certs serveis apareixen amb altres adreces.
L'eina correcta és una altra, i aquí cal ser honest:
- Amazon GuardDuty té una troballa específica per a això:
UnauthorizedAccess:IAMUser/InstanceCredentialExfiltration, que detecta credencials de rol d'instància usades des de fora d'AWS. És exactament aquest cas i funciona sense configuració. - Alternativament, una condició a la política de confiança del rol amb
aws:SourceVpc, que directament impedeix l'ús des de fora en lloc de detectar-lo. Prevenir és millor que detectar (04-01).
GuardDuty i Security Hub es veuen en visió general a 05-04.
Detecció 4: xifratge desactivat o accés públic en un bucket.
{ ($.eventSource = "s3.amazonaws.com") && (($.eventName = "DeleteBucketEncryption") || ($.eventName = "PutBucketAcl") || ($.eventName = "DeletePublicAccessBlock") || ($.eventName = "PutBucketPolicy")) }| Paràmetre | Valor | Justificació |
|---|---|---|
| Llindar | 0 | Cap d'aquestes quatre no hauria de passar en producció |
| Desperta? | Correu immediat; SMS només si és sobre buckets crítics | PutBucketPolicy passa legítimament en desplegar |
| Fals positiu | Desplegaments d'infraestructura com a codi | Es filtra per identitat: si ve del rol del pipeline (mòdul 8), no alarma |
Però aquesta detecció té un límit seriós: només detecta el canvi, no l'estat. Si el xifratge ja estava desactivat des d'abans de crear l'alarma, mai no saltarà. I no ho pot corregir.
Per a això existeix una eina específica, que és la lliçó següent: AWS Config. Config avalua l'estat actual de tots els recursos contra regles, detecta les desviacions existents —no només les noves— i les pot corregir automàticament. És exactament la cinquena pregunta que va deixar oberta el mòdul 4.
Detecció 5: recurs creat en una regió no utilitzada.
{ ($.awsRegion != "eu-west-1") && ($.awsRegion != "us-east-1") && ($.readOnly = "false") && ($.userIdentity.type != "AWSService") }| Paràmetre | Valor | Justificació |
|---|---|---|
| Llindar | 0 | MercadoFresco només fa servir eu-west-1 i us-east-1 (CloudFront, WAF, ACM) |
| Desperta? | Sí | Crear recursos en regions no utilitzades és la firma clàssica de la mineria de criptomonedes amb credencials robades |
| Fals positiu | Algú provant alguna cosa | Molt rar, i mereix la conversa |
Aquesta detecció depèn completament que el trail sigui multiregió. Sense --is-multi-region-trail
no veuries absolutament res, i és l'argument definitiu per activar-lo.
I la prevenció, millor que la detecció: una política de control de serveis (SCP) que denegui tot fora de les regions permeses. Requereix Organizations: 09-04.
Resum de les cinc:
| # | Detecció | Filtre de mètriques? | Eina correcta |
|---|---|---|---|
| 1 | Clau d'accés creada | Sí | CloudTrail + alarma |
| 2 | Política de KMS modificada | Sí | CloudTrail + alarma |
| 3 | Rol usat fora de la VPC | No | GuardDuty, o condició a la política de confiança |
| 4 | Xifratge / accés públic de bucket | Parcial | AWS Config (05-04) |
| 5 | Recurs en regió no utilitzada | Sí | CloudTrail multiregió + alarma; millor SCP (09-04) |
La conclusió de l'exercici: CloudTrail detecta esdeveniments, no estats. És perfecte per a «algú ha fet X», i insuficient per a «el bucket Y està mal configurat des de fa tres mesos». Aquesta segona pregunta necessita una altra eina, i és la lliçó següent.
Conclusió
MercadoFresco ja sap qui va fer què. Tens clara la diferència essencial que defineix aquest servei: CloudTrail registra crides a l'API d'AWS; CloudWatch Logs registra el que diu la teva aplicació. L'un respon «qui va esborrar el bucket?», l'altre «per què va fallar el pagament?». No competeixen: cobreixen universos diferents, i buscar en el que no toca costa hores.
Saps que l'historial d'esdeveniments de 90 dies sempre hi és, gratis, i coneixes les seves cinc
limitacions —90 dies, només gestió, un atribut per cerca, sense SQL, sense immutabilitat— que són
exactament les raons per crear un trail. Has creat trail-mercadofresco amb les quatre
banderes que importen: multiregió (gratis, i sense ella un atacant crea recursos on ningú no
mira), esdeveniments globals, validació d'integritat i xifratge amb alias/mercadofresco-datos.
I saps que create-trail no arrenca el registre: cal cridar start-logging i comprovar-ho
amb get-trail-status.
Has muntat el bucket mercadofresco-auditoria-cloudtrail amb les quatre capes de protecció —política
amb Deny d'esborrat, versionat, Object Lock en mode COMPLIANCE que ni el compte arrel no pot
saltar-se, i el compte separat com a pas pendent de 09-04— perquè un registre d'auditoria que
l'atacant pot esborrar no és un registre d'auditoria. I coneixes la cadena de resums signats que
fa impossible alterar un fitxer sense trencar-la, amb validate-logs executat el primer dilluns de
cada mes.
Saps llegir un esdeveniment complet: eventTime, eventSource, eventName, sourceIPAddress,
userAgent, requestParameters, responseElements (nul en les lectures) i errorCode, que només
apareix quan la crida falla —i que és una de les propietats més valuoses de CloudTrail, perquè un
atacant provant portes deixa un reguer d'AccessDenied—. Domines els sis tipus de
userIdentity, amb AssumedRole com el més freqüent i el més confús, i saps que el nom
de sessió és l'única cosa que converteix un rol compartit en una persona identificable, i que és
text lliure: la identitat real és a l'AssumeRole corresponent.
I has respost la tercera pregunta del mòdul 4. Qui va desxifrar l'última còpia: tres esdeveniments
de Decrypt, dos de normals —RDS xifrant la seva còpia automàtica i una instància de l'ASG des de
10.0.11.24— i un que no ho era: rol-restauracion-copias, a les 03:42, des de
198.51.100.77, sense MFA, sobre snapshot-2026-07-27. Gràcies a l'encryptionContext de 04-02,
que és el que converteix un esdeveniment genèric de KMS en «algú va desxifrar la còpia del 27 de
juliol de mercadofresco-pedidos».
Saps distingir esdeveniments de gestió (primera còpia gratis) d'esdeveniments de dades (0,10 USD per 100.000, i volum milionari), i has escrit selectors avançats que baixen la factura de 40 USD a 0,06 USD sense perdre res: tot sobre les còpies i els informes, només escriptures sobre el catàleg, res sobre els registres web. Coneixes Insights per 0,53 USD al mes, i les seves dues limitacions: 7 dies de línia base i que només detecta anomalies de volum.
Has enviat el trail també a CloudWatch Logs —90 dies allà, 7 anys a S3, cada destí amb el seu
propòsit— i has muntat les cinc alarmes de seguretat estàndard: ús del compte arrel, canvis
al trail (la més important de totes, perquè avisa que l'auditoria està sent atacada),
desxifratge de còpies excloent-hi el mateix RDS, pics d'AccessDenied amb llindar realista, i canvis
de configuració de seguretat. I saps consultar amb Athena —amb el WHERE de partició sempre
posat i BytesScannedCutoffPerQuery com a xarxa de seguretat— per respondre qui va assumir un rol, qui
va llegir el secret de mfadmin, quines crides van fallar i quines IP desconegudes han aparegut; amb
CloudTrail Lake com a alternativa gestionada, avaluada i descartada amb criteri.
Has recorregut una investigació completa: congelar l'evidència abans de res, trobar
l'AssumeRole real, reconstruir la sessió sencera per accessKeyId, interpretar el patró, parlar
amb la persona abans de concloure res —CloudTrail diu què va passar, no per què— i tancar amb accions
correctives amb responsable i data, entre elles la més important: fer que provar restauracions
sigui fàcil i visible en lloc de prohibir-ho. I coneixes IAM Access Analyzer generant polítiques de
privilegi mínim a partir de l'ús real, que tanca el cercle amb 04-01. Tot per 1,59 USD al mes.
Però fixa't en el que aquest servei no pot fer, perquè l'exercici 3 ho va deixar clar:
CloudTrail detecta esdeveniments, no estats. Sap que algú va cridar DeleteBucketEncryption aquest
matí. No sap que el bucket mercadofresco-registros-web fa cinc mesos que està sense xifrar perquè
mai no es va configurar. No sap que hi ha un grup de seguretat amb 0.0.0.0/0 al port 22 des d'una
prova de març. No pot dir quants recursos incompleixen la política d'etiquetatge obligatori de
MercadoFresco. I no cal dir que no ho pot arreglar sol.
Aquesta és la cinquena i última pregunta que va deixar oberta el mòdul 4: res no avisa si algú
desactiva el xifratge d'un bucket o obre un grup de seguretat al món. A la lliçó 05-04, «AWS
Config», veurem què és l'element de configuració d'un recurs i la seva línia temporal, en què es
diferencia exactament de CloudTrail —qui va fer la crida enfront de com va quedar el recurs—, com
s'activa l'enregistrador de configuració amb el seu canal de lliurament, les regles gestionades que
MercadoFresco necessita (s3-bucket-server-side-encryption-enabled, restricted-ssh,
required-tags i companyia), les regles pròpies amb Lambda i amb Guard, la remediació automàtica
amb documents de Systems Manager que torna a xifrar un bucket o tanca un grup de seguretat sense que
ningú intervingui, els paquets de conformitat alineats amb CIS i PCI DSS, i el cost real per element
de configuració, que és la partida que més fàcilment es dispara de tot aquest mòdul.
Curs d'AWS
Mòdul 1: Introducció a AWS
- Què és AWS?
- Configuració del teu compte d'AWS
- Infraestructura global d'AWS
- Consola d'administració d'AWS
- AWS CLI i SDK
Mòdul 2: Serveis principals d'AWS
Mòdul 3: Xarxes i lliurament de contingut
- Amazon VPC
- Grups de seguretat i llistes de control d'accés
- Elastic Load Balancing
- Amazon CloudFront
- Route 53
Mòdul 4: Seguretat i identitat
- AWS Identity and Access Management (IAM)
- AWS Key Management Service (KMS)
- Secrets Manager i Parameter Store
- AWS Shield
- AWS WAF
Mòdul 5: Monitoratge i gestió
- Amazon CloudWatch
- AWS X-Ray i traçabilitat distribuïda
- AWS CloudTrail
- AWS Config
- AWS Trusted Advisor
Mòdul 6: Bases de dades
- Com triar la base de dades adequada
- Amazon DynamoDB
- Amazon Aurora
- Amazon Redshift
- Amazon ElastiCache
Mòdul 7: Integració d'aplicacions
- Amazon SQS
- Amazon SNS
- Amazon EventBridge
- AWS Step Functions
- Patrons d'integració: idempotència, reintents i cues de missatges fallits
