Le Blog Amazon Web Services

Atyos : Vérifier la conformité de vos tâches Amazon ECS avec AWS Config et Cosign

Cet article a été co-écrit par Julien BAILLAGOU, Architecte Cloud et Damien SCHMITT, Architecte Cloud & Gérant de la société ATYOS en collaboration avec les équipes Partner Solutions Architect d’AWS France.

Nous assistons depuis ces derniers mois à l’émergence d’attaques sur la « Supply Chain » logicielle. Le principe est de compromettre vos chaînes de production d’applicatifs afin de pouvoir exécuter du code malveillant au sein de votre infrastructure. Un vecteur d’attaque de plus en plus fréquent consiste à introduire du code malveillant à différents moments du cycle de vie de vos conteneurs. La signature d’images de conteneur permet de valider la provenance d’une image de conteneur, de vérifier que l’image n’a pas été falsifiée et de définir des politiques pour déterminer les images validées que vous autorisez à extraire sur vos systèmes. Dans cet article, vous allez découvrir comment signer vos conteneurs avec Cosign et comment vérifier la conformité et l’intégrité de vos conteneurs avec AWS Config.

Architecture générale de la solution

L’illustration ci-dessous présente l’architecture de la solution mise en œuvre. Chaque étape du processus est détaillée dans les sections suivantes.

Architecture générale de la solution

Signer vos images Docker avec Cosign et AWS Key Management Service (KMS)

Pour signer numériquement une image de conteneur, nous allons utiliser Cosign, un projet de Sigstore. L’objectif du projet Sigstore est de fournir un standard open-source de signature et de vérification d’artefacts logiciels tels que des images Docker, mais aussi, des binaires, archives, etc. Cosign s’intègre nativement avec AWS Key Management Service (AWS KMS). Avant de pouvoir procéder à la création d’une signature pour une image de conteneur, nous avons besoin de créer une paire de clés asymétriques. Cette dernière pourra être utilisée pour signer et pour vérifier la signature de l’ensemble de nos images de conteneurs.

1. Prérequis

Pour implémenter ce qui est décrit dans cet article, vous aurez besoin des éléments suivants :

Il est aussi possible d’utiliser l’environnement de développement AWS : AWS CloudShell / AWS Cloud9.

2. Création de la clé AWS Customer Managed Key (CMK)

La première étape consiste à créer une clé AWS KMS gérée par le client (CMK) au sein de votre compte AWS. Pour cela, nous pouvons utiliser la ligne de commande :

$ aws kms create-key --customer-master-key-spec RSA_4096\
       --key-usage SIGN_VERIFY \ 
       --description "CoSign Signature Key pair" \ 
       --query KeyMetadata.Arn --output text
       

En retour, vous obtiendrez l’ID et l’ARN de la clé KMS qu’il convient de conserver pour la suite de cet article. Nous nous y référerons sous les variables $KMS_KEY_ID et $KMS_KEY_ARN.

3. Construction, Envoi et Signature de vos images Docker

Nous allons stocker nos images Docker au sein d’un dépôt AWS Elastic Container Registry (ECR). Pour cela, nous allons créer un dépôt à l’aide de la ligne de commande suivante :

$ aws ecr create-repository --repository-name docker-cosign

Ensuite, nous allons construire une image docker basée sur Nginx puis l’envoyer sur le dépôt ECR :

$ export AWS_REGION=<votre-region>
$ cat > Dockerfile-signed <<EOF
FROM nginxinc/nginx-unprivileged:stable-alpine
USER root
RUN echo "Cette image est signée." > /usr/share/nginx/html/index.html
USER nginx
EOF
$ aws ecr get-login-password --region $AWS_REGION | docker login --username AWS --password-stdin
$ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com
$ docker build -f Dockerfile-signed -t $ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/docker-cosign:signed .
$ docker push $ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/docker-cosign:signed

Ensuite, à l’aide de l’utilitaire Cosign, nous allons signer l’image à l’aide de la clé AWS KMS créée précédemment :

$ export AWS_REGION=<votre-region>
$ cosign sign --key awskms:///$KMS_KEY_ARN $ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/docker-cosign:signed
$ cosign verify --key awskms:///$KMS_KEY_ARN $ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/docker-cosign:signed

Pour illustrer le fonctionnement de vérification de conformité de l’image, nous allons également créer une image docker similaire non-signée dans le dépôt ECR.

$ export AWS_REGION=<votre-region>
$ cat > Dockerfile-unsigned <<EOF
FROM nginxinc/nginx-unprivileged:stable-alpine
USER root
RUN echo "Cette image n'est pas signée." > /usr/share/nginx/html/index.html
USER nginx
EOF
$ aws ecr get-login-password --region $AWS_REGION | docker login --username AWS --password-stdin
$ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com
$ docker build -f Dockerfile-unsigned -t $ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/docker-cosign:unsigned .
$ docker push $ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/docker-cosign:unsigned

Après ces étapes, vous devez avoir l’aperçu suivant dans votre dépôt ECR :

Note :  La signature est stockée, dans le fichier sha256-<digest>.sig au sein même du dépôt ECR.

Vérifier la conformité des conteneurs ECS avec AWS Config

Pour notre cas d’usage, nous allons développer une règle personnalisée AWS Config dont la logique permettra d’évaluer la conformité des conteneurs.

1. Création d’une fonction Lambda

Nous allons donc créer une fonction Lambda qui sera invoquée à chaque modification d’une tâche ECS :

  • Lister l’ensemble des images Dockers utilisées dans la définition de la tâche ECS,
  • Récupérer la clé publique utilisée pour signer les images Docker dans AWS KMS,
  • Vérifier la signature de chacune des images Docker,
  • Publier le statut de conformité de la tache ECS dans AWS Config.

Le code source de la fonction Lambda est disponible ici. Le SDK de Cosign étant disponible en Go uniquement, la fonction lambda est donc développée en Go. Pour créer cette fonction lambda au sein de votre compte, suivez les étapes suivantes :

# 1. Télécharger le package de la fonction Lambda.
$ wget -O aws-config-ecs-check-image-signature.zip \
https://gitlab.com/atyos/public/aws-config-ecs-check-image-signature/-/raw/main/dist/bin/linux/amd64/aws-config-ecs-check-image-signature.zip?inline=false
# 2. Créer le rôle IAM pour cette fonction Lambda et récupérer son ARN
$ cat > trust-policy.json <<EOF
{
"Version": "2012-10-17",
"Statement": [
  {
   "Effect": "Allow",
   "Principal": {
   "Service": "lambda.amazonaws.com"
   },
   "Action": "sts:AssumeRole"
  }
 ]
}
EOF
$ aws iam create-role --role-name VerifyECSImageSignatureLambdaRole \
--assume-role-policy-document file://trust-policy.json \
--query Role.Arn --output text
# Note: L'ARN du rôle sera affiché après exécution de la commande.
# 3. Attacher une politique en ligne (inline policy) au rôle
#- Remplacer $AWS_REGION par la région dans laquelle vos ressources sont déployées
#- Remplacer $ACCOUNT_ID par la région dans laquelle vos ressources sont déployées
#- Remplacer $KMS_KEY_ID par l'ID de la clé KMS

$ cat > inline-policy.json <<EOF
{

  "Version": "2012-10-17",
 "Statement": [
{
  "Sid": "CreateLogGroup",
  "Effect": "Allow",
  "Action": "logs:CreateLogGroup",
  "Resource": "arn:aws:logs:$AWS_REGION:$ACCOUNT_ID:*"
},
{
"Sid": "PutLogEvents",
"Effect": "Allow",
"Action": [
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "arn:aws:logs:$AWS_REGION:$ACCOUNT_ID:log-group:/aws/lambda/VerifyECSImageSignature:*"
},
{
"Sid": "PutEvaluationsToConfig",
"Effect": "Allow",
"Action": "config:PutEvaluations",
"Resource": "*"
},
{
"Sid": "GetKMSPublicKey",
"Effect": "Allow",
"Action": [
"kms:GetPublicKey",
"kms:DescribeKey"
],
"Resource": "arn:aws:kms:$AWS_REGION:$ACCOUNT_ID:key/$KMS_KEY_ID"
},
{
"Sid": "GetTokenForECR",
"Effect": "Allow",
"Action": [
"ecr:GetAuthorizationToken"
],
"Resource": "*"
},
{
"Sid": "GetImageFromECR",
"Effect": "Allow",
"Action": [
"ecr:BatchGetImage",
"ecr:GetDownloadUrlForLayer"
],
"Resource": "arn:aws:ecr:$AWS_REGION:$ACCOUNT_ID:repository/docker-cosign"
}
]
}
EOF

$ aws iam put-role-policy --role-name VerifyECSImageSignatureLambdaRole \
--policy-name VerifyECSImageSignatureLambdaPolicy \
--policy-document file://inline-policy.json
# 4. Créer la fonction Lambda
#- Remplacer KMS_KEY_ARN par l'ARN de la clé KMS
#- Remplacer LAMBDA_ROLE_ARN par l'ARN du rôle créé dans l'étape 2.
$ aws lambda create-function --function-name VerifyECSImageSignature \
        --zip-file fileb://aws-config-ecs-check-image-signature.zip \
        --handler main \
        --runtime go1.x \
        --architectures x86_64 \
        --environment Variables={AWS_CONFIG_TEST_MODE=true|false,COSIGN_AWS_KMS_KEY_ARN=$KMS_KEY_ARN} \
        --role $LAMBDA_ROLE_ARN

2. Configuration de la règle personnalisée dans AWS Config

Une fois que la fonction Lambda VerifyECSImageSignature est déployée, la règle AWS Config peut être configurée comme suit :

Afin de vérifier si la règle fonctionne, nous allons créer deux tâches ECS :

  • La première ECSTaskSignedImage utilisera l’image signée $ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/docker-cosign:signed.
  • La seconde ECSTaskUnsignedImage utilisera l’image non signée $ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/docker-cosign:unsigned.

Après quelques secondes, le résultat de la vérification de conformité apparaît dans la console AWS Config :

Nous obtenons donc le résultat attendu :

  • La tâche ECS ECSTaskSignedImage utilisant l’image docker signée est considérée comme COMPLIANT, c’est à dire conforme aux règles de sécurité définies dans le compte.
  • La tâche ECS ECSTaskUnsignedImage utilisant l’image docker non signée est considérée comme NON_COMPLIANT, c’est à dire non conforme aux règles de sécurité définies dans le compte.

Estimation des coûts

Pour mettre en place cette solutions, vous serez facturé par AWS pour les services suivants (les tarifs indiqués sont pour la région eu-west-3) :

  • AWS Key Management Service (AWS KMS) : 0.15$ pour 10.000 requêtes sur l’API KMS.
  • AWS Elastic Container Registry (AWS ECR): 0.10$ par GB et par mois pour le stockage, 0.00$ de frais de transfert de données en restant dans la même région AWS.
  • AWS Lambda : 0.0000000021$ pour 1ms d’exécution/128MB de mémoire et 0.20$ pour 1 million de requêtes.
  • AWS Config : 0.001$ par évaluation de la règle et par région
  • AWS Elastic Container Service (via AWS Fargate) : 0.01458$ par vCPU par heure et 0.00159$ par GB par heure.

Suppression de l’ensemble des ressources

Si vous souhaitez supprimer l’ensemble des ressources provisionnées dans le cadre de cet article, suivez les étapes suivantes :

  • Désactiver les tâches AWS ECS
  • Supprimer la règle AWS Config
  • Supprimer la clé KMS
    $ aws kms schedule-key-deletion --key-id $KMS_KEY_ID --pending-window-in-days 7
  • Supprimer le dépôt ECR avec les images associées
    $ aws ecr delete-repository --force --repository-name docker-cosign
  • Supprimer la fonction Lambda avec le rôle associé
    $ aws lambda delete-function --function-name VerifyECSImageSignature
    $ aws iam delete-role --role VerifyECSImageSignatureLambdaRole

Conclusion

Dans cet article, nous avons présenté et détaillé la manière d’intégrer les processus de signature et de vérification d’images Docker à l’aide de Cosign et d’AWS Config. Cette solution permet de s’assurer que les tâches ECS s’exécutant dans votre compte AWS utilisent exclusivement des images signées avec une clé KMS de votre choix. Si la signature de l’image utilisée dans la tâche ECS est incorrecte, une non-conformité sera remontée et tracée dans la console AWS Config. Vous pourrez alors bénéficier de l’intégration native d’AWS EventBridge avec AWS Config pour notifier votre équipe de sécurité via Amazon Simple Notification Service ou appliquer vos propres processus de remédiation en terminant la tâche ECS avec une autre fonction Lambda, par exemple.

A propos des Auteurs

Julien BAILLAGOU est Architecte Cloud & DevOps chez ATYOS. Passionné par les enjeux de cybersécurité, il intervient régulièrement auprès d’entreprises afin d’auditer leur infrastructure Cloud et leur apporter du conseil et de l’expertise autour d’AWS.

Damien SCHMITT est Architecte Cloud et fondateur de la société ATYOS. Avec plus de 20 ans d’expérience dans l’IT, il accompagne les entreprises dans leur transformation vers le Cloud AWS.