Aller au contenu

Chiffrement du magasin d'artéfacts CodePipeline — le bloc de confiance KMS qui casse sans kms:ViaService

Clés KMS gérées par le client pour le bucket d'artéfacts, partage de clé multi-compte pour les pipelines partagés, politiques de bucket bloquant les uploads non chiffrés, rotation de clé. La condition kms:ViaService que vous ne pouvez pas omettre.

Un auditeur signale le bucket d’artéfacts CodePipeline : chiffré au repos, mais avec la clé aws/s3 gérée par AWS par défaut. Le bucket contient 18 mois d’artéfacts de build — chaque commit, chaque exécution de test, chaque artéfact de déploiement — et l’audit veut soit la suppression du contenu plus ancien, des clés gérées par le client, ou une exception documentée. L’équipe veut des clés gérées par le client, mais la première tentative de bascule casse le pipeline parce que le bloc de confiance IAM sur la nouvelle clé manque de la condition kms:ViaService qui permet au rôle de service de CodePipeline d’utiliser la clé. Voici la configuration qui passe l’audit et garde le pipeline fonctionnel.

Ce qui se passe

Le magasin d’artéfacts CodePipeline a trois décisions de chiffrement indépendantes :

  • Le chiffrement côté serveur du bucket S3. C’est défini au niveau du bucket — soit SSE-S3 (AES-256, gérée par AWS) soit SSE-KMS (chiffré KMS avec une clé gérée par AWS ou par le client). Pour les besoins d’audit, géré par le client est ce qui obtient la coche verte.
  • Le paramètre EncryptionKey du pipeline. C’est un champ séparé sur la définition du pipeline — ArtifactStore.EncryptionKey référence la clé KMS que CodePipeline utilisera pour chiffrer les métadonnées d’artéfact qu’il écrit dans S3. Par défaut, c’est la clé aws/s3 gérée par AWS.
  • Accès multi-compte. Si le pipeline vit dans un compte AWS séparé de la cible de build ou de déploiement, la politique de clé KMS doit permettre au rôle multi-compte d’utiliser la clé — et la politique de bucket doit permettre au rôle multi-compte de lire/écrire.

Le mode d’échec d’audit que tout le monde rencontre est le troisième : le pipeline chiffre les artéfacts avec une clé gérée par le client dans le compte pipeline, mais l’étape de déploiement s’exécute dans un compte différent et le rôle de déploiement n’a pas kms:Decrypt sur la clé du compte pipeline. L’action de déploiement échoue avec AccessDenied, et la tentation est d’élargir la politique de clé avec Principal: '*' et kms:*, ce qui fait échouer l’audit suivant plus durement.

La bonne correction est une politique de clé spécifique avec une condition kms:ViaService restreignant l’accès multi-compte spécifiquement au service CodePipeline, plus une politique de bucket qui bloque tout upload non chiffré avec cette clé.

Comment vérifier

La clé gérée par le client, avec le bloc de confiance qui fonctionne et qui ne fonctionne pas :

Cassé (sur-privilégié) :

{
  "Statement": [{
    "Effect": "Allow",
    "Principal": { "AWS": "arn:aws:iam::222222222222:root" },
    "Action": "kms:*",
    "Resource": "*"
  }]
}

Ceci passe le smoke test (le multi-compte fonctionne) et échoue à l’audit (tout rôle dans le compte 222222222222 peut utiliser la clé pour n’importe quoi, y compris les déchiffrements S3 directs qui contournent CodePipeline).

Correct (moindre privilège avec kms:ViaService) :

{
  "Statement": [
    {
      "Sid": "PipelineAccountRoot",
      "Effect": "Allow",
      "Principal": { "AWS": "arn:aws:iam::111111111111:root" },
      "Action": "kms:*",
      "Resource": "*"
    },
    {
      "Sid": "DeployAccountCodePipelineOnly",
      "Effect": "Allow",
      "Principal": { "AWS": "arn:aws:iam::222222222222:role/CrossAccountDeployRole" },
      "Action": [
        "kms:Decrypt",
        "kms:DescribeKey",
        "kms:GenerateDataKey"
      ],
      "Resource": "*",
      "Condition": {
        "StringEquals": {
          "kms:ViaService": "s3.ca-central-1.amazonaws.com"
        },
        "StringLike": {
          "kms:EncryptionContext:aws:s3:arn": "arn:aws:s3:::pipeline-artifacts-ca-central-1/*"
        }
      }
    }
  ]
}

La condition kms:ViaService force le principal multi-compte à utiliser la clé uniquement via S3 — les appels kms:Decrypt directs (comme une authentification de rôle fuitée essayant de lire le texte chiffré d’un artéfact téléchargé) échouent. La condition kms:EncryptionContext:aws:s3:arn restreint en outre spécifiquement au bucket d’artéfacts du pipeline, pas à n’importe quel bucket que le rôle de déploiement peut toucher.

La politique de bucket fait appliquer le chiffrement du côté stockage :

{
  "Statement": [
    {
      "Sid": "DenyUnencryptedUploads",
      "Effect": "Deny",
      "Principal": "*",
      "Action": "s3:PutObject",
      "Resource": "arn:aws:s3:::pipeline-artifacts-ca-central-1/*",
      "Condition": {
        "StringNotEquals": {
          "s3:x-amz-server-side-encryption": "aws:kms"
        }
      }
    },
    {
      "Sid": "DenyWrongKey",
      "Effect": "Deny",
      "Principal": "*",
      "Action": "s3:PutObject",
      "Resource": "arn:aws:s3:::pipeline-artifacts-ca-central-1/*",
      "Condition": {
        "StringNotLikeIfExists": {
          "s3:x-amz-server-side-encryption-aws-kms-key-id": "arn:aws:kms:ca-central-1:111111111111:key/abc-123-def-456"
        }
      }
    }
  ]
}

Les deux déclarations Deny ensemble signifient : chaque objet dans le bucket doit être chiffré KMS, et spécifiquement avec la bonne clé. Une tentative d’upload avec la clé aws/s3 gérée par AWS ou sans chiffrement échoue avant que l’objet n’atterrisse.

Vérifiez que le chiffrement au niveau du bucket est appliqué :

aws s3api get-bucket-encryption \
  --bucket pipeline-artifacts-ca-central-1

La sortie attendue référence l’ARN de la clé gérée par le client, pas AES256.

Vérifiez que le pipeline référence la même clé :

aws codepipeline get-pipeline \
  --name app-pipeline \
  --query 'pipeline.artifactStore.encryptionKey'

Sortie attendue : {"id": "arn:aws:kms:ca-central-1:111111111111:key/abc-123-def-456", "type": "KMS"}.

Si l’encryptionKey du pipeline est null (ce qui est le défaut CloudFormation), CodePipeline utilise la clé gérée par AWS — et le Deny « mauvaise clé » de la politique de bucket bloquera la prochaine exécution du pipeline. Corrigez la définition du pipeline avant d’ajouter la politique de bucket.

La correction

La configuration du chiffrement des artéfacts, dans l’ordre :

  1. Créez une clé KMS gérée par le client dans la région du pipeline. Étiquetez-la purpose=codepipeline-artifacts. Réglez EnableKeyRotation: true — la rotation automatique annuelle est supportée pour les clés symétriques gérées par le client et est l’attente par défaut de l’audit.

  2. Écrivez la politique de clé avec kms:ViaService pour tout principal multi-compte. Le root du compte pipeline obtient kms:* (pour que les administrateurs puissent la gérer). Tout autre principal obtient un kms:Decrypt, kms:DescribeKey, kms:GenerateDataKey restreint avec la condition kms:ViaService: s3.<region>.amazonaws.com. Sans kms:ViaService, la clé peut être utilisée directement pour le déchiffrement par quiconque a kms:Decrypt — c’est la vulnérabilité que l’audit trouvera à la prochaine passe.

  3. Mettez à jour l’ArtifactStore.EncryptionKey du pipeline. Dans le CloudFormation, réglez EncryptionKey: { Id: !GetAtt ArtifactKey.Arn, Type: KMS }. Sans cela, CodePipeline écrit avec la clé par défaut et la nouvelle politique de bucket bloquera la prochaine exécution.

  4. Appliquez la politique de bucket. Les deux déclarations Deny font appliquer à la fois « doit être chiffré KMS » et « doit être la bonne clé KMS ». Appliquez dans cet ordre — si vous appliquez le Deny « mauvaise clé » en premier alors que le bucket a encore des objets chiffrés avec la mauvaise clé, les opérations de listing cassent.

  5. Pour les déploiements multi-compte, accordez kms:Decrypt au rôle du compte de déploiement avec kms:ViaService. Le rôle du compte de déploiement assume depuis le rôle du compte pipeline, donc la chaîne de confiance est : rôle de service pipeline → rôle de déploiement multi-compte → S3 GetObject (qui appelle KMS Decrypt sous le capot). Chaque maillon a besoin de la bonne permission ; le kms:ViaService empêche le rôle de déploiement de pouvoir déchiffrer autre chose que via S3.

  6. Vérifiez les artéfacts existants. Les anciens artéfacts dans le bucket peuvent avoir été écrits avec la clé gérée par AWS. La politique de bucket refuse les nouveaux uploads avec la mauvaise clé, mais ne re-chiffre pas rétroactivement. Pour les besoins d’audit, soit supprimez les artéfacts plus anciens que la fenêtre de rétention (politique de cycle de vie S3) soit re-chiffrez-les avec aws s3 cp s3://bucket/key s3://bucket/key --sse aws:kms --sse-kms-key-id <new-key>.

Trois règles qui tiennent à chaque engagement de chiffrement d’artéfact :

Clé gérée par le client, toujours, pour les pipelines de production. La clé aws/s3 gérée par AWS est bien pour les projets personnels, pas pour un SaaS qui fera face à une revue SOC 2 ou HIPAA.

kms:ViaService sur chaque accord multi-compte. Sans elle, une authentification de compte de déploiement fuitée devient un problème « lire tous les artéfacts de pipeline jamais ». Avec elle, l’authentification ne fonctionne que via S3, ce qui est ce que la conception vise.

La rotation de clé est automatique, pas manuelle. Activez la rotation au moment de la création de la clé. CodePipeline gère le re-chiffrement de manière transparente quand la clé tourne ; la rotation manuelle en production tend à trouver chaque endroit où l’ID de clé a été codé en dur que l’équipe avait oublié.

La passe d’audit est la partie facile une fois que la configuration est correcte. La partie difficile est d’obtenir le bloc de confiance correct la première fois.


Stack Harbor bâtit et opère les workflows CodePipeline en production dans le cadre de l’onboarding Gestion AWS et de la couche Pipeline de déploiement en continu. Si votre chemin de release passe encore par SSH et un message Slack, commencez par une consultation d’infrastructure.