Aller au contenu

Rôles IAM sur EC2 — profils d'instance, IMDSv2 et l'antipatron de la clé d'accès qui ressort encore en 2026

Une instance EC2 avec des identifiants AWS codés en dur dans /etc/aws/credentials est un problème de 2014 qui apparaît encore aux audits en 2026. Les profils d'instance corrigent ça. Voici le montage least-privilege, l'application d'IMDSv2 et la boucle de nettoyage des permissions inutilisées.

Un audit de migration sur un parc de 40 instances révèle un patron familier : la moitié des EC2 ont un fichier ~/.aws/credentials détenu par l’utilisateur applicatif, contenant une clé d’accès à long terme d’un utilisateur IAM avec AmazonS3FullAccess attachée. La clé a été émise en 2019 lorsque l’équipe ne savait pas encore qu’EC2 pouvait obtenir ses identifiants du service de métadonnées d’instance. Personne ne l’a fait tourner. L’utilisateur IAM s’est connecté à la console exactement zéro fois — seule la clé d’accès est utilisée. Toutes les machines du parc partagent la même clé. La bonne réponse, ce sont les profils d’instance depuis 2014, et le chemin pour sortir du patron de la clé d’accès est court — mais chacun des trois morceaux (le rôle, l’application d’IMDSv2, le nettoyage des permissions inutilisées) a un cas limite à connaître.

Ce qui se passe

Un profil d’instance IAM est un conteneur pour un rôle IAM qu’une instance EC2 peut assumer. Lorsque l’instance est lancée avec le profil attaché, le SDK AWS sur l’instance peut récupérer des identifiants à courte durée auprès du service de métadonnées d’instance (IMDS) à http://169.254.169.254/latest/meta-data/iam/security-credentials/<nom-du-rôle>. Les identifiants tournent automatiquement toutes les quelques heures. Pas de clé d’accès, pas de clé secrète, aucun fichier à oublier.

L’antipatron que la migration a trouvé — une clé d’accès à long terme sur disque — est pire qu’il n’y paraît. La clé est un identifiant permanent. Si l’instance est un jour compromise (un webshell, une dépendance vulnérable, un développeur avec un portable perdu), l’attaquant repart avec un identifiant qui fonctionne de n’importe où et qu’on peut rejouer pendant des jours avant que quelqu’un s’en aperçoive. Les identifiants de profil d’instance, eux, sont signés SigV4 contre le rôle de l’instance et bornés par un TTL court. Un identifiant exfiltré des métadonnées expire avant que l’attaquant n’en fasse grand-chose depuis l’extérieur.

IMDSv1 est le piège. Le service de métadonnées original est en requête-réponse HTTP sans authentification — n’importe quel processus sur l’instance, y compris un processus tournant dans un conteneur dont l’utilisateur ignorait qu’il avait accès au réseau de l’hôte, peut le lire. Le patron bien documenté SSRF-vers-vol-d’identifiants (forger une requête depuis un serveur web mal configuré vers 169.254.169.254, exfiltrer les identifiants du rôle) existe parce qu’IMDSv1 n’a aucune défense. IMDSv2 corrige cela en exigeant un jeton de session obtenu via PUT — un SSRF qui ne peut émettre que des GET ne peut pas compléter le handshake. Les nouveaux comptes AWS sont par défaut en IMDSv2-required depuis 2024, mais les instances lancées à partir d’AMI plus anciennes ou de modules Terraform plus anciens peuvent encore autoriser IMDSv1.

La question du nettoyage est distincte. Un profil d’instance attaché au lancement tend à accumuler les permissions au fil des ans — quelqu’un a besoin de lecture S3 pour une sauvegarde, quelqu’un d’autre a besoin d’écriture DynamoDB pour une fonctionnalité, le rôle finit avec huit politiques managées attachées et en utilise trois. La fonctionnalité unused-access d’IAM Access Analyzer fait remonter cela.

Comment vérifier

L’audit à l’échelle du parc tient en trois requêtes.

D’abord, lister chaque instance EC2 et le rôle IAM attaché (ou None) :

aws ec2 describe-instances \
  --query 'Reservations[].Instances[].[InstanceId,IamInstanceProfile.Arn,Tags[?Key==`Name`].Value|[0]]' \
  --output table

Toute instance avec None dans la colonne IAM utilise soit un fichier de credentials, soit n’a aucun accès AWS du tout. Lancez find / -name credentials -path '*/.aws/*' 2>/dev/null sur un échantillon de ces instances pour confirmer.

Ensuite, vérifier la configuration IMDS sur chaque instance :

aws ec2 describe-instances \
  --query 'Reservations[].Instances[].[InstanceId,MetadataOptions.HttpTokens,MetadataOptions.HttpEndpoint]' \
  --output table

HttpTokens doit être required (IMDSv2 uniquement). S’il est à optional, l’instance accepte encore les requêtes IMDSv1. HttpEndpoint doit être enabled à moins que l’instance n’ait intentionnellement aucun besoin d’accès aux métadonnées.

Enfin, pour la passe de nettoyage, activer l’analyseur unused-access d’IAM Access Analyzer (distinct de l’analyseur external-access) :

aws accessanalyzer create-analyzer \
  --analyzer-name unused-access-prod \
  --type ACCOUNT_UNUSED_ACCESS \
  --configuration '{"unusedAccess":{"unusedAccessAge":90}}'

Une fois l’analyseur lancé (quelques heures pour un compte neuf), lister les findings :

aws accessanalyzer list-findings-v2 \
  --analyzer-arn arn:aws:access-analyzer:us-east-1:123456789012:analyzer/unused-access-prod \
  --filter '{"findingType":{"eq":["UnusedPermission","UnusedIAMRole","UnusedIAMUserAccessKey"]}}'

Les findings UnusedIAMUserAccessKey sont les clés d’accès à long terme sur disque que personne n’utilise activement — ce sont celles à retirer en premier. UnusedPermission est le nettoyage au niveau des politiques.

La correction

L’ordre de migration : profil d’instance d’abord, puis application d’IMDSv2, puis nettoyage des permissions.

  1. Créer le rôle avec une politique de trust étroitement scopée et les permissions dont l’application a réellement besoin (et non AmazonS3FullAccess). Pour une app qui lit depuis s3://app-uploads-prod/ :

    {
      "Version": "2012-10-17",
      "Statement": [{
        "Effect": "Allow",
        "Action": ["s3:GetObject", "s3:PutObject", "s3:DeleteObject"],
        "Resource": ["arn:aws:s3:::app-uploads-prod/*"]
      }]
    }

    Pas de s3:* au niveau du bucket, pas de wildcards sur les buckets, pas de iam:*.

  2. Attacher le rôle à l’instance. Pour une instance en cours d’exécution :

    aws ec2 associate-iam-instance-profile \
      --instance-id i-0123456789abcdef0 \
      --iam-instance-profile Name=app-prod-role

    Le SDK sur l’instance ramasse les identifiants du rôle en quelques secondes. Vérifier avec aws sts get-caller-identity depuis l’instance — la sortie doit être l’ARN du rôle assumé, pas l’utilisateur IAM.

  3. Retirer le fichier de credentials. rm /home/app/.aws/credentials sur l’instance, puis révoquer la clé d’accès de l’utilisateur IAM :

    aws iam update-access-key \
      --user-name app-deploy-user \
      --access-key-id AKIA... \
      --status Inactive

    Laisser la clé inactive pendant deux semaines avant de la supprimer. Si quoi que ce soit utilisait encore la clé depuis l’extérieur de l’instance, l’échec apparaîtra dans cette fenêtre.

  4. Appliquer IMDSv2 sur l’instance. Le changement est non perturbant pour tout SDK publié après 2020, mais à appliquer les yeux ouverts sur les AMI legacy :

    aws ec2 modify-instance-metadata-options \
      --instance-id i-0123456789abcdef0 \
      --http-tokens required \
      --http-endpoint enabled \
      --http-put-response-hop-limit 1

    La limite de saut à 1 empêche les conteneurs en mode bridge d’atteindre le service de métadonnées. Si la charge nécessite que des conteneurs accèdent à AWS (et utilise le SDK depuis l’intérieur du conteneur), utilisez IRSA sur EKS ou les task roles sur ECS au lieu d’augmenter la limite de saut.

  5. Une fois le profil d’instance en place depuis au moins 90 jours, lancer la passe unused-permission d’Access Analyzer et élaguer la politique du rôle pour ne garder que les actions que le rôle a réellement appelées. Le finding UnusedPermission de l’analyseur liste chaque action qui n’a jamais été invoquée — retirer celles-là de la politique. Le rôle se rétrécit avec le temps sans jamais casser l’application, parce que l’analyseur ne rapporte que les actions que l’application n’utilise prouvément pas.

Trois règles sur chaque nettoyage IAM que nous menons :

Des rôles, pas des utilisateurs, sur EC2. Un utilisateur IAM avec une clé d’accès sur disque est un patron de 2014. Il n’y a aucun scénario en 2026 où c’est la bonne réponse pour du code tournant sur une instance EC2.

IMDSv2 required, hop limit 1. Comportement par défaut sur les nouveaux comptes depuis 2024, mais les parcs legacy dérivent encore. Les deux drapeaux ensemble ne coûtent rien et retirent une classe d’attaques SSRF-vers-vol-d’identifiants.

Élaguer les permissions sur une cadence de 90 jours. Les findings unused-access deviennent rances si personne n’agit dessus. La cadence tourne avec la revue de coûts — une boucle trimestrielle attrape la dérive avant qu’elle ne devienne du matériel d’audit.

La séquence complète sur un parc de 40 instances tient dans un après-midi concentré pour la migration plus une heure par trimestre pour l’élagage. Rien de sophistiqué. Les économies ne sont pas sur la facture — elles sont sur le prochain incident qui ne se produit pas parce qu’une clé à long terme ne traînait pas dans un répertoire en attendant d’être trouvée.


Stack Harbor opère le nettoyage IAM, la baseline CloudWatch, AWS Backup et le rythme de contrôle des coûts dans le cadre de Gestion AWS et des Environnements infogérés en continu. Si des clés d’accès à long terme ressortent encore à vos audits, commencez par une consultation d’infrastructure.