L’organisation AWS de l’équipe a quatre comptes — shared-ci, dev, staging, prod. Le pipeline de build s’exécute actuellement trois fois : une par environnement, chacune dans son propre compte, chacune pullant la même image Docker de base, exécutant les mêmes tests, produisant des artefacts qui devraient être identiques mais ne le sont pas toujours. La bonne forme, c’est un seul build dans shared-ci qui produit un seul artefact, et trois jobs de déploiement qui promeuvent ce même artefact à travers les comptes. Cet article est la plomberie IAM, KMS, ECR et S3 qui le fait fonctionner.
Ce qui se passe
Le pattern « build une fois, déploie plusieurs fois » repose sur quatre pièces d’accès inter-comptes :
- Assomption de rôle IAM — CodeBuild dans
shared-ciassume un rôle de déploiement dansdev/staging/prodpour y appliquer les changements. - Partage de clé KMS — les artefacts CI chiffrés avec une CMK dans
shared-cidoivent être déchiffrables par les comptes cibles. - Accès ECR inter-comptes — l’image Docker poussée dans l’ECR de
shared-cidoit être pullable par ECS/EKS dans les comptes cibles. Soit via la réplication ECR (une image, copiée dans les registres par compte), soit via les politiques de dépôt ECR (image unique, multiples permissions de pull). - Politiques de bucket S3 — quand CodePipeline traverse les comptes, le bucket d’artefacts doit autoriser le rôle de pipeline du compte cible à le lire.
Le pattern qui passe à l’échelle : les artefacts vivent dans le compte CI, sont chiffrés avec la clé KMS du compte CI, et les comptes cibles assument un rôle qui a les permissions de déchiffrement sur cette clé et de lecture sur le bucket. La promotion, c’est « redéployer avec le rôle ciblant le compte prod », pas « rebuilder pour prod ».
La promotion ressemble à :
shared-cibuild → produitimageDetail.json, image ECR à123-ci.dkr.ecr.us-east-1.amazonaws.com/web:abc1234- déploiement
dev→ assumearn:aws:iam::111-dev:role/PipelineDeployRole, applique la mise à jour du service ECS pointant vers l’image CI - déploiement
staging→ assumearn:aws:iam::222-staging:role/PipelineDeployRole, même image - déploiement
prod→ assumearn:aws:iam::333-prod:role/PipelineDeployRole, même image
Le même SHA d’image circule du build à la prod. L’auditabilité est simple — aws ecr describe-images dans shared-ci montre quel SHA a été déployé où, avec le pushedAt de l’image et l’heure de déploiement comme les deux seules dates qui comptent.
Comment vérifier
La plomberie IAM minimum. Dans le compte cible (prod montré ; même pattern pour dev/staging), créez le rôle de déploiement :
aws iam create-role \
--role-name PipelineDeployRole \
--assume-role-policy-document '{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": { "AWS": "arn:aws:iam::CI-ACCOUNT-ID:role/CodeBuildServiceRole" },
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": { "sts:ExternalId": "prod-deploy-2026" }
}
}]
}'
L’ExternalId est la mitigation canonique du confused deputy — même si le rôle du compte CI est compromis, l’attaquant ne peut pas assumer le rôle cible sans connaître l’ID externe.
Attachez les permissions du rôle de déploiement — pour un service basé sur ECS :
aws iam attach-role-policy \
--role-name PipelineDeployRole \
--policy-arn arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy
aws iam put-role-policy \
--role-name PipelineDeployRole \
--policy-name DeployServices \
--policy-document '{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ecs:UpdateService",
"ecs:DescribeServices",
"ecs:RegisterTaskDefinition"
],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": "iam:PassRole",
"Resource": "arn:aws:iam::PROD-ACCOUNT-ID:role/ecsTaskExecutionRole"
}
]
}'
Dans le compte CI, le rôle CodeBuild doit être autorisé à assumer chaque rôle cible :
aws iam put-role-policy \
--role-name CodeBuildServiceRole \
--policy-name AssumeDeployRoles \
--policy-document '{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": "sts:AssumeRole",
"Resource": [
"arn:aws:iam::DEV-ACCOUNT-ID:role/PipelineDeployRole",
"arn:aws:iam::STAGING-ACCOUNT-ID:role/PipelineDeployRole",
"arn:aws:iam::PROD-ACCOUNT-ID:role/PipelineDeployRole"
]
}]
}'
Partage KMS — la CMK qui chiffre le bucket d’artefacts dans shared-ci doit laisser les rôles des comptes cibles déchiffrer :
aws kms put-key-policy \
--key-id arn:aws:kms:us-east-1:CI-ACCOUNT-ID:key/abc12345-... \
--policy-name default \
--policy '{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "EnableIAMUserPermissions",
"Effect": "Allow",
"Principal": { "AWS": "arn:aws:iam::CI-ACCOUNT-ID:root" },
"Action": "kms:*",
"Resource": "*"
},
{
"Sid": "AllowCrossAccountDecrypt",
"Effect": "Allow",
"Principal": {
"AWS": [
"arn:aws:iam::DEV-ACCOUNT-ID:role/PipelineDeployRole",
"arn:aws:iam::STAGING-ACCOUNT-ID:role/PipelineDeployRole",
"arn:aws:iam::PROD-ACCOUNT-ID:role/PipelineDeployRole"
]
},
"Action": ["kms:Decrypt", "kms:DescribeKey"],
"Resource": "*"
}
]
}'
Le bucket S3 d’artefacts dans shared-ci a besoin d’une politique accordant la lecture aux principaux des comptes cibles :
aws s3api put-bucket-policy \
--bucket shared-ci-pipeline-artifacts \
--policy '{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {
"AWS": [
"arn:aws:iam::DEV-ACCOUNT-ID:role/PipelineDeployRole",
"arn:aws:iam::STAGING-ACCOUNT-ID:role/PipelineDeployRole",
"arn:aws:iam::PROD-ACCOUNT-ID:role/PipelineDeployRole"
]
},
"Action": ["s3:GetObject", "s3:GetObjectVersion", "s3:ListBucket"],
"Resource": [
"arn:aws:s3:::shared-ci-pipeline-artifacts",
"arn:aws:s3:::shared-ci-pipeline-artifacts/*"
]
}]
}'
ECR — l’image vit dans le compte CI. Les comptes cibles la pullent. Soit on la réplique (une image, des copies dans chaque ECR cible), soit on partage le dépôt source :
aws ecr set-repository-policy \
--repository-name web \
--policy-text '{
"Version": "2012-10-17",
"Statement": [{
"Sid": "CrossAccountPull",
"Effect": "Allow",
"Principal": {
"AWS": [
"arn:aws:iam::DEV-ACCOUNT-ID:root",
"arn:aws:iam::STAGING-ACCOUNT-ID:root",
"arn:aws:iam::PROD-ACCOUNT-ID:root"
]
},
"Action": [
"ecr:GetDownloadUrlForLayer",
"ecr:BatchGetImage",
"ecr:BatchCheckLayerAvailability"
]
}]
}'
Dans la définition de tâche ECS du compte cible, référencez directement l’URL ECR du compte CI : 123-ci.dkr.ecr.us-east-1.amazonaws.com/web:abc1234. Le rôle d’exécution de tâche ECS dans le compte cible a besoin de ecr:GetAuthorizationToken (au niveau du compte, pas d’ARN de ressource possible) et ecr:BatchGetImage sur le dépôt source. La politique du dépôt source ci-dessus accorde la seconde.
Dans le buildspec, l’étape de déploiement assume le rôle cible et agit comme lui :
post_build:
commands:
- |
ROLE_OUTPUT=$(aws sts assume-role \
--role-arn arn:aws:iam::PROD-ACCOUNT-ID:role/PipelineDeployRole \
--role-session-name codebuild-deploy-${CODEBUILD_BUILD_NUMBER} \
--external-id prod-deploy-2026)
export AWS_ACCESS_KEY_ID=$(echo $ROLE_OUTPUT | jq -r .Credentials.AccessKeyId)
export AWS_SECRET_ACCESS_KEY=$(echo $ROLE_OUTPUT | jq -r .Credentials.SecretAccessKey)
export AWS_SESSION_TOKEN=$(echo $ROLE_OUTPUT | jq -r .Credentials.SessionToken)
- aws ecs update-service
--cluster prod-web
--service api
--task-definition api:$NEW_REVISION
--force-new-deployment
Le unset des credentials de session entre comptes compte — plusieurs déploiements dans un seul buildspec doivent ré-assumer par cible pour éviter la contamination croisée :
unset AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN
La correction
Le pattern qui passe à l’échelle d’une organisation à 10 comptes :
-
Un compte de build, N comptes de déploiement. N’exécutez pas CodeBuild dans chaque compte cible. Le code source est buildé une fois. La promotion, c’est un redéploiement du même artefact, pas une reconstruction.
-
Rôle par cible avec ID externe. Chaque compte cible a un
PipelineDeployRolequi fait confiance au rôle de service CodeBuild du compte CI, gardé par unExternalIdunique par cible. Les IDs vont dans SSM Parameter Store dans le compte CI, pas dans le buildspec. -
KMS sur le compte CI. La CMK du bucket d’artefacts vit dans
shared-ci. Les rôles des comptes cibles obtiennentkms:Decryptetkms:DescribeKeysur cette unique clé, pas la leur. Centraliser la clé simplifie la rotation. -
Pull ECR inter-comptes, pas la réplication, par défaut. La réplication double le coût de stockage et ajoute de la complexité de rotation. La politique de dépôt accordant le pull aux comptes cibles est plus simple et le SHA d’image est vérifiablement le même. Utilisez la réplication seulement quand la latence ou le coût du pull inter-régions fait mal (la réplication inter-régions est un cas valide séparé).
-
unsetdes credentials entre les assumes de cibles. Un buildspec qui déploie aux trois environnements en séquence déploiera silencieusementstagingen prod si vous oubliez leunsetentre les rôles. Faites duunsetune discipline. -
Limites de permissions sur le rôle de déploiement. Le
PipelineDeployRoleenprodest puissant (il peut mettre à jour les services de production). Attachez une limite de permissions qui plafonne ce qu’il peut faire — pas d’écritures IAM, pas de règles de groupes de sécurité, pas de Route 53. Le buildspec a rarement besoin d’autre chose que des mises à jour ECS/EKS/Lambda. -
CloudTrail dans tous les comptes, agrégé. Les appels assume-role inter-comptes atterrissent dans le CloudTrail du compte source. Le CloudTrail du compte cible montre l’identité AssumedRole agissant localement. Une piste agrégée dans un compte de sécurité, c’est l’enregistrement auditable de chaque action inter-comptes.
Mode d’échec courant : le déploiement réussit contre une image intra-compte mais échoue quand l’image est dans shared-ci. La cause, c’est presque toujours le rôle d’exécution de tâche ECS à qui il manque ecr:BatchGetImage pour le dépôt inter-comptes, même si la politique du dépôt l’accorde. Les deux côtés de la chaîne de politiques doivent autoriser le pull — la politique de ressource sur le dépôt ECR et la politique d’identité sur le rôle d’exécution.
Stack Harbor opère CodeBuild + CodePipeline à l’échelle production dans le cadre de l’onboarding Gestion AWS et des Environnements infogérés en continu. Si votre build s’exécute trois fois quand il devrait s’exécuter une seule fois et que vos auditeurs n’arrêtent pas de demander quel SHA est arrivé en prod, commencez par une consultation d’infrastructure.