Skip to main content

Quatre étapes pour renforcer la sécurité d’Amazon EKS

Écrit par
Headshot of Kamil Potrec

Kamil Potrec

blog feature amazon eks security

21 juillet 2021

0 minutes de lecture

Dans la première partie de cette série d’articles, nous avons exploré le déploiement d’Amazon EKS avec Terraform et examiné comment sécuriser la mise en œuvre initiale de RBAC ainsi que le service de métadonnées d’instance. Dans ce deuxième article, nous allons découvrir d’autres bonnes pratiques pour renforcer la sécurité d’Amazon EKS, notamment l’importance des rôles IAM dédiés à la livraison continue, d’une architecture multi-comptes pour isoler les clusters Amazon EKS et du chiffrement de vos secrets dans le plan de contrôle. Enfin, nous vous montrerons comment intégrer des outils d’analyse statique à vos pipelines de livraison continue afin de détecter ces problèmes avant qu’ils n’atteignent les environnements de production.

Suivre les personnes qui ont déployé le cluster

Le service Amazon EKS s’intègre au service Identity and Access Management afin d’authentifier les utilisateurs auprès du cluster. Les décisions d’autorisation sont prises par le modèle RBAC natif de Kubernetes. La configuration RBAC est associée aux identités IAM via aws-auth ConfigMap. Toutefois, une association particulière n’apparaît dans aucun fichier ni paramètre de configuration. L’entité IAM utilisée pour créer le cluster est automatiquement associée au groupe Kubernetes intégré system:masters. Ce comportement est bien documenté dans la documentation utilisateur. Il a néanmoins des conséquences importantes sur l’auditabilité des autorisations du cluster.

Pour illustrer l’impact de cette configuration, nous allons supprimer tous les Roles et les ClusterRoles de notre cluster de démonstration.

dev@pwnbox:~$ kubectl delete clusterroles --all
clusterrole.rbac.authorization.k8s.io "admin" deleted
clusterrole.rbac.authorization.k8s.io "aws-node" deleted
clusterrole.rbac.authorization.k8s.io "cluster-admin" deleted
clusterrole.rbac.authorization.k8s.io "edit" deleted
clusterrole.rbac.authorization.k8s.io "eks:addon-manager" deleted
clusterrole.rbac.authorization.k8s.io "eks:fargate-manager" deleted
clusterrole.rbac.authorization.k8s.io "eks:node-bootstrapper" deleted
clusterrole.rbac.authorization.k8s.io "eks:node-manager" deleted
clusterrole.rbac.authorization.k8s.io "eks:podsecuritypolicy:privileged" deleted
<OMITTED>
dev@pwnbox:~$ kubectl delete roles --all-namespaces --all
role.rbac.authorization.k8s.io "system:controller:bootstrap-signer" deleted
role.rbac.authorization.k8s.io "eks:addon-manager" deleted
role.rbac.authorization.k8s.io "eks:certificate-controller" deleted
role.rbac.authorization.k8s.io "eks:fargate-manager" deleted
role.rbac.authorization.k8s.io "eks:node-manager" deleted
<OMITTED>
dev@pwnbox:~$ kubectl auth can-i --list
Resources                                       Non-Resource URLs   Resource Names   Verbs
*.*                                               []                  []               [*]
                                                [*]                 []               [*]
selfsubjectaccessreviews.authorization.k8s.io   []                  []               [create]
selfsubjectrulesreviews.authorization.k8s.io    []                  []               [create]
                                                [/api/*]            []               [get]
                                                [/api]              []               [get]
                                                [/apis/*]           []               [get]
                                                [/apis]             []               [get]
                                                [/healthz]          []               [get]
                                                [/healthz]          []               [get]
                                                [/livez]            []               [get]
                                                [/livez]            []               [get]
                                                [/openapi/*]        []               [get]
                                                [/openapi]          []               [get]
                                                [/readyz]           []               [get]
                                                [/readyz]           []               [get]
                                                [/version/]         []               [get]
                                                [/version/]         []               [get]
                                                [/version]          []               [get]
                                                [/version]          []               [get]
dev@pwnbox:~$ kubectl get configmap --all-namespaces
NAMESPACE     NAME                                 DATA   AGE
kube-system   coredns                              1      7h59m
kube-system   cp-vpc-resource-controller           0      7h58m
kube-system   eks-certificates-controller          0      7h59m
kube-system   extension-apiserver-authentication   6      7h59m
kube-system   kube-proxy                           1      7h59m
kube-system   kube-proxy-config                    1      7h59m

Amazon EKS utilise l’authentification par jeton via webhook pour s’intégrer à IAM. Nous pouvons consulter la configuration dans les arguments du serveur d’API.

FLAG: --authentication-token-webhook-cache-ttl="7m0s"
FLAG: --authentication-token-webhook-config-file="/etc/kubernetes/authenticator/apiserver-webhook-kubeconfig.yaml"
FLAG: --authentication-token-webhook-version="v1beta1"

AWS IAM dispose d’identifiants uniques qui peuvent être utilisés dans certains services afin d’éviter les erreurs de configuration liées à la réutilisation des noms. La documentation d’Amazon EKS ne précise pas clairement si l’autorisation system:masters est liée à l’identifiant unique de l’entité ou à son nom convivial. Pour vérifier si la suppression de l’entité d’origine révoque toutes les autorisations sur le cluster, nous avons supprimé le rôle temporaire, puis nous l’avons recréé.

dev@pwnbox:~$ aws iam get-role --role-name ci-eks-test
{
    "Role": {
        "Path": "/",
        "RoleName": "ci-eks-test",
        "RoleId": "AROAYREY3WYOOSHLPO3W6",
        "Arn": "arn:aws:iam::123456789012:role/ci-eks-test",
        "CreateDate": "2021-06-20T21:09:01+00:00",
        "AssumeRolePolicyDocument": {
            "Version": "2012-10-17",
            "Statement": [
                {
                    "Effect": "Allow",
                    "Principal": {
                        "AWS": "arn:aws:iam::123456789012:user/ci"
                    },
                    "Action": "sts:AssumeRole",
                    "Condition": {}
                }
            ]
        },
        "MaxSessionDuration": 3600,
        "RoleLastUsed": {}
    }
}
dev@pwnbox:~$ kubectl auth can-i --list
Resources                                       Non-Resource URLs   Resource Names     Verbs
*.*                                             []                  []                 [*]
                                                [*]                 []                 [*]
selfsubjectaccessreviews.authorization.k8s.io   []                  []                 [create]
selfsubjectrulesreviews.authorization.k8s.io    []                  []                 [create]
                                                [/api/*]            []                 [get]
                                                [/api]              []                 [get]
                                                [/apis/*]           []                 [get]
                                                [/apis]             []                 [get]
                                                [/healthz]          []                 [get]
                                                [/healthz]          []                 [get]
                                                [/livez]            []                 [get]
                                                [/livez]            []                 [get]
                                                [/openapi/*]        []                 [get]
                                                [/openapi]          []                 [get]
                                                [/readyz]           []                 [get]
                                                [/readyz]           []                 [get]
                                                [/version/]         []                 [get]
                                                [/version/]         []                 [get]
                                                [/version]          []                 [get]
                                                [/version]          []                 [get]
podsecuritypolicies.policy                      []                  [eks.privileged]   [use]

dev@pwnbox:~$ aws iam get-role --role-name ci-eks-test
{
    "Role": {
        "Path": "/",
        "RoleName": "ci-eks-test",
        "RoleId": "AROAYREY3WYOAK6QMGSUT",
        "Arn": "arn:aws:iam::123456789012:role/ci-eks-test",
        "CreateDate": "2021-06-20T21:14:28+00:00",
        "AssumeRolePolicyDocument": {
            "Version": "2012-10-17",
            "Statement": [
                {
                    "Effect": "Allow",
                    "Principal": {
                        "AWS": "arn:aws:iam::123456789012:user/ci"
                    },
                    "Action": "sts:AssumeRole",
                    "Condition": {}
                }
            ]
        },
        "MaxSessionDuration": 3600,
        "RoleLastUsed": {}
    }
}
dev@pwnbox:~$ kubectl auth can-i --list
Resources                                       Non-Resource URLs   Resource Names     Verbs
*.*                                             []                  []                 [*]
                                                [*]                 []                 [*]
selfsubjectaccessreviews.authorization.k8s.io   []                  []                 [create]
selfsubjectrulesreviews.authorization.k8s.io    []                  []                 [create]
                                                [/api/*]            []                 [get]
                                                [/api]              []                 [get]
                                                [/apis/*]           []                 [get]
                                                [/apis]             []                 [get]
                                                [/healthz]          []                 [get]
                                                [/healthz]          []                 [get]
                                                [/livez]            []                 [get]
                                                [/livez]            []                 [get]
                                                [/openapi/*]        []                 [get]
                                                [/openapi]          []                 [get]
                                                [/readyz]           []                 [get]
                                                [/readyz]           []                 [get]
                                                [/version/]         []                 [get]
                                                [/version/]         []                 [get]
                                                [/version]          []                 [get]
                                                [/version]          []                 [get]
podsecuritypolicies.policy                      []                  [eks.privileged]   [use]

Comme vous pouvez le constater, le nouveau rôle est complètement différent, comme l’indique le RoleId. Pourtant, le service d’authentification Amazon EKS utilise le nom convivial du rôle pour nous accorder les mêmes autorisations system:masters.

Pour atténuer efficacement ce problème, utilisez un système de déploiement centralisé, attribuez un rôle dédié aux déploiements et évitez à tout prix les identifiants personnels. Des outils comme aws-vault peuvent vous aider à gérer de manière sécurisée le passage d’un rôle à l’autre.

Voyons maintenant comment les nœuds s’authentifient auprès du plan de contrôle.

Séparer les clusters dans différents comptes

Amazon EKS exige que chaque nœud soit associé à un profil d’instance incluant les deux politiques suivantes : AmazonEKSWorkerNodePolicy et AmazonEC2ContainerRegistryReadOnly.

dev@pwnbox:$ aws iam get-policy-version --policy-arn arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy --version-id v1
{
    "PolicyVersion": {
        "Document": {
            "Version": "2012-10-17",
            "Statement": [
                {
                    "Action": [
                        "ec2:DescribeInstances",
                        "ec2:DescribeRouteTables",
                        "ec2:DescribeSecurityGroups",
                        "ec2:DescribeSubnets",
                        "ec2:DescribeVolumes",
                        "ec2:DescribeVolumesModifications",
                        "ec2:DescribeVpcs",
                        "eks:DescribeCluster"
                    ],
                    "Resource": "*",
                    "Effect": "Allow"
                }
            ]
        },
        "VersionId": "v1",
        "IsDefaultVersion": true,
        "CreateDate": "2018-05-27T21:09:01+00:00"
    }
}

dev@pwnbox:$ aws iam get-policy-version --policy-arn arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly --version-id v3
{
    "PolicyVersion": {
        "Document": {
            "Version": "2012-10-17",
            "Statement": [
                {
                    "Effect": "Allow",
                    "Action": [
                        "ecr:GetAuthorizationToken",
                        "ecr:BatchCheckLayerAvailability",
                        "ecr:GetDownloadUrlForLayer",
                        "ecr:GetRepositoryPolicy",
                        "ecr:DescribeRepositories",
                        "ecr:ListImages",
                        "ecr:DescribeImages",
                        "ecr:BatchGetImage",
                        "ecr:GetLifecyclePolicy",
                        "ecr:GetLifecyclePolicyPreview",
                        "ecr:ListTagsForResource",
                        "ecr:DescribeImageScanFindings"
                    ],
                    "Resource": "*"
                }
            ]
        },
        "VersionId": "v3",
        "IsDefaultVersion": true,
        "CreateDate": "2019-12-10T20:56:32+00:00"
    }
}

Par défaut, ces politiques accordent des privilèges sur toutes les ressources du compte AWS. Cela signifie qu’un pod compromis sur ce cluster pourra répertorier des informations sur toutes les ressources, tous les VPC, sous-réseaux et toutes les images du compte.

Le nœud Amazon EKS a également besoin d’une autre politique gérée, nommée AmazonEKS_CNI_Policy. Cette politique permet au nœud d’allouer des adresses IP et d’effectuer les opérations liées au réseau.

dev@pwnbox:$ aws iam get-policy-version --policy-arn arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy --version-id v4
{
    "PolicyVersion": {
        "Document": {
            "Version": "2012-10-17",
            "Statement": [
                {
                    "Effect": "Allow",
                    "Action": [
                        "ec2:AssignPrivateIpAddresses",
                        "ec2:AttachNetworkInterface",
                        "ec2:CreateNetworkInterface",
                        "ec2:DeleteNetworkInterface",
                        "ec2:DescribeInstances",
                        "ec2:DescribeTags",
                        "ec2:DescribeNetworkInterfaces",
                        "ec2:DescribeInstanceTypes",
                        "ec2:DetachNetworkInterface",
                        "ec2:ModifyNetworkInterfaceAttribute",
                        "ec2:UnassignPrivateIpAddresses"
                    ],
                    "Resource": "*"
                },
                {
                    "Effect": "Allow",
                    "Action": [
                        "ec2:CreateTags"
                    ],
                    "Resource": [
                        "arn:aws:ec2:*:*:network-interface/*"
                    ]
                }
            ]
        },
        "VersionId": "v4",
        "IsDefaultVersion": true,
        "CreateDate": "2020-04-20T20:52:01+00:00"
    }
}

Cette politique permet à l’appelant de modifier la configuration réseau de n’importe quelle instance du compte. AWS recommande de l’associer directement au compte de service Kubernetes aws-node. Par défaut, le module Terraform associe cette politique au profil IAM. Vous pouvez modifier ce comportement en définissant l’attribut attach_worker_cni_policy sur false.

Chiffrez vos secrets

L’un des composants du plan de contrôle gérés par le service Amazon EKS est l’espace de stockage clé-valeur etcd. Le serveur d’API Kubernetes s’en sert pour stocker tous les objets et toutes les configurations. Par défaut, Kubernetes ne chiffre pas les données stockées dans cet espace. AWS est responsable de la sécurité des composants du plan de contrôle, mais nous pouvons également appliquer une stratégie de défense en profondeur pour mieux protéger nos données.

Le service AWS KMS est l’un des fournisseurs de chiffrement capables d’assurer le chiffrement par enveloppe des objets secrets stockés dans notre cluster. En définissant l’option `cluster_encryption_config`, nous pouvons spécifier une clé KMS qui servira à chiffrer des clés de chiffrement intermédiaires, elles-mêmes utilisées pour chiffrer des objets spécifiques. Cet article de blog d’AWS sur Amazon EKS présente clairement le fonctionnement sous-jacent. Dès lors, toute personne ayant accès à un cluster etcd ne pourra pas lire nos secrets sans également avoir accès à notre clé KMS. L’ensemble du processus est entièrement transparent pour l’utilisateur final.

Détecter les problèmes dans le pipeline de déploiement

L’infrastructure en tant que code nous permet de décrire de manière déclarative l’état souhaité du cluster Amazon EKS. Nous pouvons ainsi détecter statiquement certains de ces problèmes avant tout déploiement.

Dans Terraform, nous pouvons générer un plan de la configuration à déployer.

dev@pwnbox:$ terraform plan

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create
 <= read (data resources)

Terraform will perform the following actions:

  # data.aws_eks_cluster.cluster will be read during apply
  # (config refers to values not yet known)
 <= data "aws_eks_cluster" "cluster"  {
      + arn                       = (known after apply)
      + certificate_authority     = (known after apply)
      + created_at                = (known after apply)
      + enabled_cluster_log_types = (known after apply)
      + endpoint                  = (known after apply)
      + id                        = (known after apply)
      + identity                  = (known after apply)
      + kubernetes_network_config = (known after apply)
      + name                      = (known after apply)
      + platform_version          = (known after apply)
      + role_arn                  = (known after apply)
      + status                    = (known after apply)
      + tags                      = (known after apply)
      + version                   = (known after apply)
      + vpc_config                = (known after apply)
    }

<OMITTED>

# module.vpc.aws_vpc.this[0] will be created
  + resource "aws_vpc" "this" {
      + arn                              = (known after apply)
      + assign_generated_ipv6_cidr_block = false
      + cidr_block                       = "10.0.0.0/16"
      + default_network_acl_id           = (known after apply)
      + default_route_table_id           = (known after apply)
      + default_security_group_id        = (known after apply)
      + dhcp_options_id                  = (known after apply)
      + enable_classiclink               = (known after apply)
      + enable_classiclink_dns_support   = (known after apply)
      + enable_dns_hostnames             = false
      + enable_dns_support               = true
      + id                               = (known after apply)
      + instance_tenancy                 = "default"
      + ipv6_association_id              = (known after apply)
      + ipv6_cidr_block                  = (known after apply)
      + main_route_table_id              = (known after apply)
      + owner_id                         = (known after apply)
      + tags                             = {
          + "Name" = "eks-threat-modelling-ddddaaaa"
        }
      + tags_all                         = {
          + "Name" = "eks-threat-modelling-ddddaaaa"
        }
    }

Plan: 44 to add, 0 to change, 0 to destroy.

Changes to Outputs:
  + cluster_endpoint          = (known after apply)
  + cluster_name              = (known after apply)
  + cluster_security_group_id = (known after apply)
  + config_map_aws_auth       = []
  + kubectl_config            = (known after apply)
  + region                    = "eu-west-1

Ce plan peut être converti au format JSON afin de permettre l’analyse statique de tous les attributs.

dev@pwnbox:$ terraform show -json tfplan > tfplan.json
dev@pwnbox:$ jq '.resource_changes[] | select((.type == "aws_eks_cluster") and .mode == "managed").change.after' tfplan.json
{
  "enabled_cluster_log_types": [
    "audit",
    "authenticator"
  ],
  "encryption_config": [
    {
      "provider": [
        {}
      ],
      "resources": [
        "secrets"
      ]
    }
  ],
  "kubernetes_network_config": [
    {}
  ],
  "name": "eks-threat-modelling-ddddaaaa",
  "tags": null,
  "timeouts": {
    "create": "30m",
    "delete": "15m",
    "update": null
  },
  "version": "1.19",
  "vpc_config": [
    {
      "endpoint_private_access": true,
      "endpoint_public_access": false,
      "public_access_cidrs": [
        "0.0.0.0/0"
      ]
    }
  ]
}

Une fois la structure du plan Terraform comprise, nous pouvons utiliser Open Policy Agent et son langage déclaratif pour écrire des tests simples.

package play

deny[msg]{
    input.vpc_config[0].endpoint_public_access == true
    msg := sprintf("The public endpoint is enabled on the cluster", [])
}

Vous pouvez essayer cet exemple simple dans un Rego Playground en ligne.

La rédaction de ces règles peut vite devenir fastidieuse. Elle nécessite d’analyser les structures de sortie de Terraform, d’analyser les fichiers, d’assurer la maintenance des bibliothèques et de présenter les résultats de manière pertinente. C’est là que des produits comme Snyk IaC peuvent vous aider. Voici un exemple d’analyse de sécurité réalisée sur notre déploiement par défaut.

dev@pwnbox:$ snyk iac test tfplan.json

Testing tfplan.json...

Infrastructure as code issues:
  ✗ EKS cluster allows public access [High Severity] [SNYK-CC-TF-94] in EKS
    introduced by aws_eks_cluster[this] > vpc_config

  ✗ Non-Encrypted root block device [Medium Severity] [SNYK-CC-TF-53] in EC2
    introduced by aws_launch_configuration[workers] > root_block_device > encrypted

  ✗ Public IPs are automatically mapped to instances [Low Severity] [SNYK-CC-AWS-427] in VPC
    introduced by aws_subnet[public_0] > map_public_ip_on_launch

  ✗ EKS control plane logging insufficient [Low Severity] [SNYK-CC-TF-131] in EKS
    introduced by aws_eks_cluster[this] > enabled_cluster_log_types

  ✗ Public IPs are automatically mapped to instances [Low Severity] [SNYK-CC-AWS-427] in VPC
    introduced by aws_subnet[public_2] > map_public_ip_on_launch

  ✗ Public IPs are automatically mapped to instances [Low Severity] [SNYK-CC-AWS-427] in VPC
    introduced by aws_subnet[public_1] > map_public_ip_on_launch

Organization:      p0tr3c
Type:              Terraform
Target file:       tfplan.json
Project name:      hardened
Open source:       no
Project path:      tfplan.json

Tested tfplan.json for known issues, found 6 issues

dev@pwnbox:/$

Grâce à l’analyse IaC de Snyk, vous pouvez détecter les problèmes dans votre configuration Terraform avant le déploiement en production et garantir la sécurité de vos environnements de production. Créez gratuitement un compte sur https://app.snyk.io ! Vous pouvez également essayer l’outil de vérification du code gratuit de Snyk pour évaluer rapidement la sécurité de votre code.

Sécurisez votre infrastructure dès la source

Snyk automatise la sécurité et la conformité de l’IaC dans vos workflows, et détecte les ressources dont la configuration a dérivé ou qui sont manquantes.

Publié dans:

Lire la suite

blog feature pypi spoof
Article

La Snyk AI Security Platform complète, gratuite pour les responsables de projets open source

Les responsables de projets open source croulent sous les signalements de vulnérabilités avérées et ont besoin d’aide pour les prioriser, les corriger et déployer les correctifs plus rapidement. Le Secure Developer Program de Snyk donne aux projets admissibles un accès gratuit à la Snyk AI Security Platform.

Article

Publication de versions malveillantes de node-ipc sur npm à la suite d’une compromission présumée du compte d’un mainteneur

Le 14 mai 2026, plusieurs versions malveillantes du célèbre package npm node-ipc ont été publiées sur le registre npm. Les informations publiques disponibles identifient actuellement node...

blog feature toolkit
Blog

Publication malveillante du paquet elementary-data sur PyPI : des identifiants cloud dérobés à des ingénieurs data

Des attaquants ont exploité une vulnérabilité d’injection de script dans GitHub Actions pour publier une version malveillante de l’interface de ligne de commande Python elementary-data (v0.23.3). Elle intégrait une porte dérobée volant des identifiants, qui ciblait les profils dbt, les clés des fournisseurs cloud et les secrets SSH dans les environnements d’ingénierie des données.