Une Service Control Policy appliquée à la racine de l’Organisation est le seul garde-fou AWS qu’un propriétaire de compte ne peut pas désactiver. Les administrateurs IAM dans le compte membre peuvent s’octroyer ce qu’ils veulent, et la SCP gagne quand même. Cela fait de la SCP de base l’une des choses à plus haut levier que vous livrez — et l’une des plus faciles à rater en se verrouillant les outils de sécurité dont vous dépendez. Cet article est la base que nous attachons quand nous intégrons un nouveau compte payeur, plus la boucle de tests qui attrape les cas de verrouillage avant la mise en production.
Comment vérifier
Commencez par demander à l’API ce qui est réellement attaché à chaque niveau — les clics dans l’interface Organizations cachent plus de choses qu’ils n’en montrent :
aws organizations list-policies --filter SERVICE_CONTROL_POLICY
aws organizations list-roots --query 'Roots[].Id' --output text
aws organizations list-policies-for-target --target-id r-xxxx --filter SERVICE_CONTROL_POLICY
aws organizations describe-effective-policy --policy-type SERVICE_CONTROL_POLICY --target-id <account-id>
describe-effective-policy est la seule commande qui résout la chaîne d’héritage (racine → OU → compte) en la politique unique qui s’applique réellement. Si vous ne regardez que les politiques attachées, vous ratez le fait que l’OU dans laquelle vit votre compte sandbox manque une règle de refus que la racine a déjà déclarée.
Ce qui se passe
Les SCP ne sont pas des politiques IAM. Elles n’accordent jamais de permissions — elles fixent seulement le maximum de permissions qu’un principal dans un compte cible peut se voir accorder par IAM. La valeur par défaut implicite à la racine est FullAWSAccess. Enlever cette valeur par défaut signifie qu’aucun principal dans aucun compte membre ne peut rien faire, ce qui est rarement ce que vous voulez. Le patron est : garder FullAWSAccess attaché à la racine, puis attacher des politiques Deny explicites pour les choses que vous ne voulez jamais voir quiconque faire.
Il y a cinq refus qui ont leur place dans presque toutes les bases :
- Refuser les régions non approuvées. Une charge de travail qui tourne dans us-east-1 et eu-west-1 ne devrait pas pouvoir démarrer une instance EC2 dans ap-southeast-3 parce que quelqu’un a cliqué la mauvaise région dans la console. Les limites de rayon d’impact par région réduisent aussi la surface d’attaques d’exfiltration vers une région non surveillée.
- Refuser les actions root. L’utilisateur root existe ; il ne devrait jamais être utilisé. Une SCP qui refuse toute action quand
aws:PrincipalArncorrespond au patron root prévient l’activité root accidentelle même si le MFA est contourné d’une façon ou d’une autre. - Refuser les lancements IMDSv1. Tout appel
ec2:RunInstancesqui ne fixe pasMetadataOptions.HttpTokens=requireddevrait échouer. Les nouvelles AMI ont IMDSv2-requis par défaut depuis 2024, mais les modules Terraform de projets plus anciens lancent encore des instances IMDSv1-permis. - Refuser la désactivation de CloudTrail / GuardDuty / Config. Les administrateurs de comptes membres ne devraient pas pouvoir arrêter la piste d’audit. La liste de refus couvre
cloudtrail:StopLogging,cloudtrail:DeleteTrail,guardduty:DeleteDetector,config:StopConfigurationRecorder, et compagnie. - Refuser la création d’utilisateurs IAM dans les comptes de charge de travail. Identity Center (anciennement SSO) est le chemin ; les utilisateurs IAM persistants avec des clés d’accès sont la régression. Le refus couvre
iam:CreateUseretiam:CreateAccessKeyhors d’un petit ensemble de comptes break-glass.
Le piège : une SCP qui refuse une action large avec une exception étroite peut verrouiller le rôle même qui opère l’exception. L’exemple classique est une SCP de protection de CloudTrail qui refuse cloudtrail:* sauf pour le OrganizationAccountAccessRole — et puis quelqu’un utilise un rôle différent pour le travail quotidien, frappe le refus, et suppose que la politique est cassée. Le remède est d’élargir la condition de l’exception (une étiquette, un préfixe de chemin) ou d’attacher la politique à une OU qui exclut les comptes d’opérations.
La procédure
-
Créez une OU
Sandboxdédiée avant de commencer. Déplacez un compte de test dedans. Votre boucle d’itération est « attacher à l’OU Sandbox, tester dans le compte de test, puis promouvoir à la racine ». Ne testez jamais les SCP en les attachant directement à la racine.aws organizations create-organizational-unit \ --parent-id r-xxxx --name Sandbox aws organizations move-account \ --account-id 111122223333 \ --source-parent-id r-xxxx \ --destination-parent-id ou-xxxx-yyyy -
Écrivez la SCP de base. Sauvegardez sous
baseline-deny.json:{ "Version": "2012-10-17", "Statement": [ { "Sid": "DenyUnapprovedRegions", "Effect": "Deny", "NotAction": ["iam:*","organizations:*","route53:*","cloudfront:*","support:*","budgets:*","ce:*","sts:GetCallerIdentity","sts:AssumeRoleWithSAML","sts:AssumeRoleWithWebIdentity"], "Resource": "*", "Condition": { "StringNotEquals": { "aws:RequestedRegion": ["us-east-1","us-east-2","ca-central-1"] } } }, { "Sid": "DenyRootUser", "Effect": "Deny", "Action": "*", "Resource": "*", "Condition": { "StringLike": {"aws:PrincipalArn": "arn:aws:iam::*:root"} } }, { "Sid": "DenyIMDSv1Launches", "Effect": "Deny", "Action": "ec2:RunInstances", "Resource": "arn:aws:ec2:*:*:instance/*", "Condition": { "StringNotEquals": {"ec2:MetadataHttpTokens": "required"} } }, { "Sid": "ProtectAuditTrails", "Effect": "Deny", "Action": [ "cloudtrail:StopLogging", "cloudtrail:DeleteTrail", "cloudtrail:UpdateTrail", "guardduty:DeleteDetector", "guardduty:DisassociateFromMasterAccount", "config:DeleteConfigurationRecorder", "config:StopConfigurationRecorder" ], "Resource": "*" } ] }Le découpage
NotActiondans le refus de régions est critique — IAM, Organizations, Route 53, CloudFront, Support, Budgets et Cost Explorer sont tous des services globaux routés via us-east-1. Si vous les refusez, vous vous verrouillez l’accès à la console. -
Attachez d’abord à l’OU Sandbox.
aws organizations create-policy \ --type SERVICE_CONTROL_POLICY \ --name baseline-deny \ --description "Base région/root/IMDSv1/audit" \ --content file://baseline-deny.json aws organizations attach-policy \ --policy-id p-xxxxxxxx \ --target-id ou-xxxx-yyyy -
Testez depuis le compte sandbox. Dans un terminal accrédité pour le compte sandbox, lancez chaque action refusée et confirmez qu’elle échoue :
aws ec2 run-instances --region ap-southeast-3 \ --image-id ami-xxxx --instance-type t3.micro # Attendu : AccessDenied — région refusée par SCP aws ec2 run-instances --region us-east-1 \ --image-id ami-xxxx --instance-type t3.micro \ --metadata-options HttpTokens=optional # Attendu : AccessDenied — IMDSv1 refusé par SCP aws cloudtrail stop-logging --name org-trail # Attendu : AccessDenied -
Promouvez à la racine une fois la sandbox propre. Détachez de l’OU Sandbox, attachez à la racine, et ne détachez
FullAWSAccessde l’OU Sandbox que si vous avez en tête une politique sandbox plus stricte. La base au niveau racine s’applique à chaque compte de l’organisation à partir de ce moment.
Pièges courants
- La liste des régions refusées doit inclure chaque région que vos charges de travail utilisent réellement, y compris les régions de relève que vous touchez rarement. Oublier votre région de relève signifie qu’une vraie bascule est bloquée.
aws:RequestedRegionn’est pas fixé pour les appels de services globaux — IAM, Organizations, Route 53. Le découpageNotActionci-dessus gère cela ; n’inversez pas la logique et n’écrivez pasStringEquals: us-east-1ou vous bloquerez les services globaux depuis n’importe où ailleurs.- Une SCP appliquée à la racine affecte aussi le compte de gestion (payeur). Si vous refusez la création d’utilisateurs IAM à l’échelle de l’organisation, vous ne pouvez pas créer un utilisateur break-glass d’urgence dans le payeur. Le patron standard est d’attacher le refus à un niveau d’OU qui exclut le payeur.
- Les résultats de
describe-effective-policysont mis en cache jusqu’à 24 heures. Après un attach/detach, l’API peut retourner l’ancienne politique effective pendant un certain temps — l’application réelle se met à jour en quelques minutes, mais l’introspection est en retard. - Une OU avec
FullAWSAccessdétaché et aucun autre allow attaché bloque effectivement tout accès. Les SCP requièrent un allow quelque part en amont de la chaîne.
Stack Harbor opère AWS Organizations pour des clients dont le compte payeur, la disposition d’OU et la base de SCP sont gérés comme du code, avec la boucle de tests ci-dessus branchée dans une vérification de pull-request qui tourne contre une OU sandbox dédiée avant qu’un changement n’atteigne la racine. Si vos garde-fous aujourd’hui sont « on fait confiance aux gens pour ne pas lancer dans la mauvaise région », cet audit est le premier livrable de la gestion de comptes AWS.