preloader

Vérifier la conformité de vos tâches ECS avec AWS Config et CosignBlog

post-thumb

Auteur Atyos / publié le 14 Nov 2022

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

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 oeuvre. Chaque étape du processus est détaillée dans les sections suivantes.

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 CMK

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

 $ 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 cette article. Nous nous y référerons sous les variables $KMS_KEY_ID et $KMS_KEY_ARN.

3. Construction, Envoi & Signature de vos images Docker

Nous allons stocker nos images Docker au sein d’un dépôt AWS 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é 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 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 Docker 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 imagess Docker
  • Publier le status de conformité de la tâche ECS dans AWS Config

Le code source de la Lambda est disponible ici. Le SDK de Cosign étant disponible en Go uniquement, la Lambda est donc développée en Go.

Pour créer cette 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 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 ECSTaskUnsignedImageutilisera 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é 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 implémentation, vous serez facturé par AWS pour les services suivants (les tarifs indiqués sont pour la région eu-west-3) :

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 cette 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 SNS ou appliquer vos propres processus de rémédiationn en terminant la tâche ECS avec une autre fonction Lambda, par exemple.

Partager