Qui a supprimé cette instance EC2 ? Retrouver le responsable avec CloudTrail
Une instance EC2 disparaît sans explication un matin — aucune alerte, aucun ticket, et l'équipe pointe dans toutes les directions. Retrouver qui a déclenché l'action TerminateInstances est exactement le cas d'usage pour lequel CloudTrail Event History existe, à condition de savoir où chercher et comment interpréter ce qu'on y trouve.
TL;DR — Retrouver l'auteur d'une suppression EC2
| Étape | Action | Ce qu'on obtient |
|---|---|---|
| 1 | Ouvrir CloudTrail Event History | Journal des appels API des 90 derniers jours |
| 2 | Filtrer sur TerminateInstances | Liste des événements de terminaison |
| 3 | Identifier l'instance cible | Correspondance par ID de ressource |
| 4 | Lire le champ userIdentity | IAM user, rôle assumé, ou service AWS |
| 5 | Corréler avec sourceIPAddress et userAgent | Contexte d'exécution (console, CLI, SDK) |
Comment CloudTrail enregistre les actions EC2
CloudTrail capture les appels API effectués contre les services AWS et les stocke sous forme d'événements JSON. Pour EC2, chaque action de l'API EC2 — y compris TerminateInstances — génère un enregistrement contenant l'identité de l'appelant, l'heure, la région, les paramètres de la requête, et la réponse du service.
L'Event History est la vue console de ces événements, disponible sans configuration supplémentaire pour les 90 derniers jours dans chaque région. Au-delà de cette fenêtre, il faut un trail actif qui écrit dans S3. Si aucun trail n'a été configuré, l'Event History reste la seule source disponible — et elle est limitée à 90 jours.
- Appelant — L'entité (utilisateur IAM, rôle, service) qui émet l'appel API.
- API EC2 TerminateInstances — L'appel qui déclenche la terminaison de l'instance.
- CloudTrail — Intercepte et enregistre l'événement avec tous les métadonnées.
- Event History (90 jours) — Accessible directement depuis la console, sans trail configuré.
- S3 / CloudWatch Logs — Destination d'un trail actif pour la rétention longue durée et les requêtes Athena.
Rechercher l'événement TerminateInstances dans Event History
La recherche dans l'Event History se fait par nom d'événement ou par ID de ressource. Commencer par le nom d'événement est plus direct — EC2 n'utilise qu'un seul nom d'API pour terminer des instances, quelle que soit la méthode d'invocation (console, CLI, SDK, Auto Scaling).
Via la console AWS
Naviguer vers CloudTrail → Event History dans la région où l'instance existait. Sélectionner le filtre Event name et saisir TerminateInstances. Ajuster la plage de dates si nécessaire. Cliquer sur un événement pour afficher le JSON complet.
Via la CLI AWS
La CLI permet de filtrer directement et d'extraire les champs pertinents sans parcourir la console manuellement.
aws cloudtrail lookup-events \
--lookup-attributes AttributeKey=EventName,AttributeValue=TerminateInstances \
--region us-east-1 \
--query 'Events[*].{Heure:EventTime,Utilisateur:Username,Evenement:EventName,Detail:CloudTrailEvent}' \
--output table
Pour cibler une instance spécifique par son ID, filtrer sur la ressource :
aws cloudtrail lookup-events \
--lookup-attributes AttributeKey=ResourceName,AttributeValue=i-0abcd1234efgh5678 \
--region us-east-1 \
--output json
La commande lookup-events ne retourne que les événements des 90 derniers jours et est limitée à un seul attribut de filtre à la fois. Pour des requêtes multi-critères, il faut passer par Athena sur les logs S3 d'un trail.
Interpréter le JSON de l'événement CloudTrail
L'événement brut contient tout ce dont on a besoin. Voici la structure des champs critiques pour l'investigation :
🔽 Exemple d'événement TerminateInstances (cliquer pour développer)
{
"eventVersion": "1.08",
"userIdentity": {
"type": "AssumedRole",
"principalId": "AROAEXAMPLEID:session-name",
"arn": "arn:aws:sts::123456789012:assumed-role/DevOpsRole/session-name",
"accountId": "123456789012",
"sessionContext": {
"sessionIssuer": {
"type": "Role",
"principalId": "AROAEXAMPLEID",
"arn": "arn:aws:iam::123456789012:role/DevOpsRole",
"accountId": "123456789012",
"userName": "DevOpsRole"
}
}
},
"eventTime": "2024-03-15T09:23:11Z",
"eventSource": "ec2.amazonaws.com",
"eventName": "TerminateInstances",
"awsRegion": "us-east-1",
"sourceIPAddress": "203.0.113.42",
"userAgent": "aws-cli/2.15.0 Python/3.11.6",
"requestParameters": {
"instancesSet": {
"items": [
{ "instanceId": "i-0abcd1234efgh5678" }
]
}
},
"responseElements": {
"instancesSet": {
"items": [
{
"instanceId": "i-0abcd1234efgh5678",
"currentState": { "code": 32, "name": "shutting-down" },
"previousState": { "code": 16, "name": "running" }
}
]
}
}
}
Les champs à lire en priorité
| Champ | Ce qu'il révèle |
|---|---|
userIdentity.type | IAMUser, AssumedRole, Root, ou AWSService |
userIdentity.arn | ARN complet de l'entité appelante |
userIdentity.sessionContext.sessionIssuer.arn | Le rôle IAM source (si AssumedRole) |
sourceIPAddress | IP d'origine — ou nom de service si appelé par un service AWS |
userAgent | Outil utilisé : console, CLI, SDK, Terraform, etc. |
requestParameters.instancesSet | IDs des instances ciblées |
eventTime | Horodatage UTC de l'appel API |
Le champ userIdentity.type est le premier à lire. Un type AssumedRole signifie qu'une entité a assumé un rôle IAM — le vrai responsable est dans sessionContext.sessionIssuer, pas dans l'ARN de surface. C'est là que beaucoup d'investigations s'arrêtent trop tôt.
Cas concrets : lire l'identité selon le type d'appelant
- IAMUser — Un utilisateur IAM avec des credentials statiques. Le champ
userNameidentifie directement la personne. - AssumedRole — Un rôle assumé via STS. Remonter à
sessionIssuer.arnpour trouver le rôle, puis identifier qui peut l'assumer via la politique de confiance. - Root — Le compte root AWS. Cas critique — déclencher immédiatement une investigation de sécurité.
- AWSService — Un service AWS (ex: Auto Scaling, EC2 Image Builder) a déclenché la terminaison. Le champ
invokedByindique le service.
Extraction CLI du champ userIdentity uniquement
aws cloudtrail lookup-events \
--lookup-attributes AttributeKey=EventName,AttributeValue=TerminateInstances \
--region us-east-1 \
--query 'Events[*].CloudTrailEvent' \
--output text | python3 -c "
import sys, json
for line in sys.stdin:
try:
evt = json.loads(line)
uid = evt.get('userIdentity', {})
print('Type:', uid.get('type'))
print('ARN:', uid.get('arn'))
print('IP:', evt.get('sourceIPAddress'))
print('Agent:', evt.get('userAgent'))
print('Heure:', evt.get('eventTime'))
print('---')
except:
pass
"
Scénario réel : la mauvaise piste du rôle partagé
L'instance i-0abcd1234efgh5678 disparaît un vendredi soir. CloudTrail montre userIdentity.type: AssumedRole avec l'ARN arn:aws:sts::123456789012:assumed-role/DeployRole/pipeline-run-4821. Première réaction : c'est le pipeline CI/CD. Mais en regardant userAgent, on lit Terraform/1.6.0 — pas le runner habituel du pipeline.
En remontant à la politique de confiance du rôle DeployRole, on découvre qu'il peut être assumé par plusieurs entités, dont un développeur qui testait un script Terraform localement avec ses propres credentials. L'ARN de session pipeline-run-4821 était un nom de session qu'il avait défini manuellement.
Un nom de session STS ne prouve pas l'origine de l'appelant. Seule la combinaisonsessionIssuer+sourceIPAddress+userAgentpermet de reconstituer le contexte réel.
La correction : activer CloudTrail Insights pour détecter les volumes anormaux d'appels API, et restreindre la politique de confiance du rôle aux seules entités légitimes.
Politique IAM minimale pour l'investigation CloudTrail
Pour qu'un analyste puisse effectuer cette investigation sans accès administrateur :
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "CloudTrailLookupEvents",
"Effect": "Allow",
"Action": [
"cloudtrail:LookupEvents"
],
"Resource": "*"
},
{
"Sid": "EC2DescribeForContext",
"Effect": "Allow",
"Action": [
"ec2:DescribeInstances"
],
"Resource": "*"
}
]
}
cloudtrail:LookupEvents ne supporte pas les restrictions au niveau de la ressource — Resource: * est requis. C'est documenté dans la Service Authorization Reference pour CloudTrail.
Aller plus loin : requêtes Athena pour les trails S3
Si l'instance a été supprimée il y a plus de 90 jours, ou si on a besoin de filtres multi-critères, il faut interroger les logs d'un trail stocké dans S3 via Athena. Cela suppose qu'un trail était actif au moment de l'événement.
SELECT
eventtime,
useridentity.type AS identity_type,
useridentity.arn AS caller_arn,
useridentity.sessioncontext.sessionissuer.arn AS role_arn,
sourceipaddress,
useragent,
json_extract_scalar(requestparameters, '$.instancesSet.items[0].instanceId') AS instance_id
FROM cloudtrail_logs
WHERE
eventsource = 'ec2.amazonaws.com'
AND eventname = 'TerminateInstances'
AND eventtime >= '2024-03-01T00:00:00Z'
ORDER BY eventtime DESC;
La table Athena doit être créée au préalable en pointant vers le bucket S3 du trail. AWS propose un DDL standard dans la documentation CloudTrail pour créer cette table.
Conclusion et prochaines étapes — Retrouver qui a supprimé une ressource AWS
CloudTrail Event History donne une réponse directe à la question 'qui a fait quoi' pour les 90 derniers jours, sans configuration préalable. Pour toute investigation sérieuse, la lecture du champ userIdentity complet — et pas seulement l'ARN de surface — est non négociable.
- Activer un trail multi-région dans chaque compte pour dépasser la limite des 90 jours.
- Configurer des alertes CloudWatch sur les événements
TerminateInstancespour une détection en temps réel. - Restreindre les politiques de confiance des rôles IAM aux seules entités légitimes.
- Consulter la documentation officielle CloudTrail Event History pour les limites et options de filtrage.
Glossaire
| Terme | Définition |
|---|---|
| Event History | Vue console des événements CloudTrail des 90 derniers jours, disponible sans trail configuré. |
| userIdentity | Champ JSON CloudTrail décrivant l'entité ayant effectué l'appel API (utilisateur, rôle, service). |
| AssumedRole | Type d'identité indiquant qu'un rôle IAM a été assumé via STS — l'appelant réel est dans sessionIssuer. |
| Trail | Configuration CloudTrail qui envoie les événements vers S3 ou CloudWatch Logs pour une rétention longue durée. |
| TerminateInstances | Action API EC2 qui arrête définitivement une ou plusieurs instances — irréversible sans AMI ou snapshot préalable. |
Commentaires
Enregistrer un commentaire