Sécuriser un cluster AWS EKS de production
Ceci durcit un cluster EKS par défaut avec trois contrôles : IAM scopé au ServiceAccount via IRSA plutôt qu'un rôle partagé au niveau du node, NetworkPolicies default-deny plutôt que l'allow-all par défaut de Kubernetes, et contraintes OPA Gatekeeper qui bloquent les manifestes non conformes à l'admission.
IRSA : scoper l'IAM au ServiceAccount, pas au node
IAM Roles for Service Accounts permet à un ServiceAccount Kubernetes d'assumer un rôle IAM précis via fédération OIDC : sans identifiants statiques, sans permissions portées par l'ensemble du node. EKS expose déjà un fournisseur OIDC par cluster : on l'associe à IAM, puis on écrit une trust policy qui ne fait confiance qu'aux tokens émis pour une paire namespace/ServiceAccount donnée.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::123456789012:oidc-provider/oidc.eks.eu-west-1.amazonaws.com/id/ABCDEF1234567890"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"oidc.eks.eu-west-1.amazonaws.com/id/ABCDEF1234567890:sub": "system:serviceaccount:payments:invoice-worker",
"oidc.eks.eu-west-1.amazonaws.com/id/ABCDEF1234567890:aud": "sts.amazonaws.com"
}
}
}
]
}La condition sub est le contrôle à elle seule : sans elle, n'importe quel ServiceAccount du cluster annoté avec l'ARN de ce rôle pourrait l'assumer. Annotez le ServiceAccount avec l'ARN du rôle, attachez une permissions policy scopée (les actions S3 et les ARN de ressources dont le workload a besoin, jamais s3:*), et retirez entièrement les permissions correspondantes de l'instance profile du node ; c'est l'étape que les équipes sautent, et la sauter annule toute la migration.
Un rôle IAM de node trop large est un multiplicateur de mouvement latéral dans un cluster multi-tenant : il signifie que le rayon d'impact de la compromission de n'importe quel pod sur ce node équivaut au rayon d'impact de la compromission du node lui-même. Vérifiez ce point spécifiquement lors de vos audits.
NetworkPolicies default-deny, puis un allow scopé
Les NetworkPolicies sont additives et scopées au namespace : en l'absence de définition, tout est ouvert. La correction consiste à poser une policy deny-all par namespace, puis à ajouter des allows explicites pour le trafic réellement légitime.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: payments
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-invoice-worker-egress
namespace: payments
spec:
podSelector:
matchLabels:
app: invoice-worker
policyTypes:
- Egress
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
ports:
- protocol: UDP
port: 53
- to: []
ports:
- protocol: TCP
port: 443podSelector: {} sur la policy deny-all signifie « tous les pods de ce namespace » : c'est précisément ce qui en fait une politique default-deny plutôt qu'une policy qui ne couvre que les pods qu'on a pensé à labelliser. La seconde policy ouvre alors exactement deux chemins pour invoice-worker : la résolution DNS via kube-system, et l'egress HTTPS (nécessaire pour atteindre les endpoints S3 et STS avec le rôle assumé via IRSA). Tout le reste, la base de données paiements dans un autre namespace, le namespace marketing, le serveur API Kubernetes, reste bloqué. Cela nécessite un CNI qui applique réellement les NetworkPolicies : le amazon-vpc-cni par défaut demande des ajustements autour de AWS_VPC_K8S_CNI_EXTERNALSNAT, et il est couramment complété par Calico.
OPA Gatekeeper : bloquer les manifestes défaillants dès l'admission
Gatekeeper est le framework de contraintes d'OPA branché comme webhook d'admission de validation : un ConstraintTemplate définit la logique Rego, une Constraint l'applique avec des paramètres précis.
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8srequiredresources
spec:
crd:
spec:
names:
kind: K8sRequiredResources
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8srequiredresources
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
not container.resources.limits.memory
msg := sprintf("container <%v> is missing memory limits", [container.name])
}
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
not container.resources.limits.cpu
msg := sprintf("container <%v> is missing cpu limits", [container.name])
}
---
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredResources
metadata:
name: require-container-limits
spec:
enforcementAction: deny
match:
kinds:
- apiGroups: [""]
kinds: ["Pod"]
excludedNamespaces: ["kube-system", "gatekeeper-system"]Cette contrainte referme un schéma d'incident bien réel : un conteneur sans limite CPU ni mémoire atterrit sur un node et affame tous les pods co-localisés sous charge : le problème classique du noisy neighbor, mais à connotation sécurité plutôt que coût. Le même principe s'applique pour interdire les tags :latest, exiger readOnlyRootFilesystem, ou bloquer hostNetwork: true en dehors d'une courte allowlist.
Déployez toujours une nouvelle contrainte en dryrun pendant au moins un cycle de déploiement complet. Elle journalise les violations sans rien bloquer, de quoi découvrir que quatorze Deployments existants échoueraient à la policy avant qu'un passage en deny ne fasse tomber une mise en production un vendredi. Corrigez les cas en violation, puis basculez.
Pod Security Standards comme socle sous-jacent
Les contraintes Gatekeeper sont sur mesure et peuvent dériver de l'intention initiale, mais les Pod Security Standards (PSS), appliqués via le contrôleur d'admission Pod Security natif, offrent un socle qui ne dépend pas d'avoir écrit le bon Rego. Labelliser un namespace avec le profil restricted bloque les conteneurs privilégiés, le partage des namespaces hôtes et les capabilities non par défaut, sans écrire une seule policy custom :
apiVersion: v1
kind: Namespace
metadata:
name: payments
labels:
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/audit: restrictedJe considère PSS restricted comme le plancher, les pièges bien connus au niveau Kubernetes, couverts gratuitement, et Gatekeeper comme l'endroit où vivent les règles propres à l'organisation : limites de ressources obligatoires, provenance des images, conventions de labels.
Le chemin durci, de bout en bout
Chaque étape restreint un peu plus ce que la précédente a laissé passer, et retirer n'importe laquelle des trois couches laisse les deux autres continuer d'avoir un rôle réel à jouer. Mais un cluster qui les combine toutes les trois est une cible fondamentalement différente du cluster par défaut, en allow-all et rôle de node universel, sur lequel démarrent la plupart des équipes.
IRSA, les NetworkPolicies et les contraintes Gatekeeper ne sont pas difficiles à mettre en place isolément : les clusters partent en production sans eux parce qu'une échéance ne se soucie pas du rayon d'impact, et que rien ne force la conversation avant un audit ou un incident. Les rétrofitter coûte plus cher que de les construire dès le premier namespace, mais le travail reste identique, que vous l'écriviez au jour un ou après qu'un incident vous y ait forcé.
Envie de faire tourner ça en production ?
Ce tutoriel couvre les concepts et l'architecture. Si vous voulez l'implémenter dans votre propre infrastructure, ou monter en compétence pour posséder ce sujet durablement, je propose du mentorat individuel construit autour de votre environnement réel, pas une formation générique.
Ce tutoriel
- Concepts clés et architecture principale
- Extraits de code illustratifs
- Le raisonnement derrière chaque décision
Mentorat individuel
- Des sessions de travail sur votre propre environnement
- Des réponses directes aux cas particuliers que vous rencontrez
- Un retour sur votre implémentation réelle
- Un accompagnement continu pendant que vous la construisez
Tutoriels similaires
FinOps Kubernetes : réduire le compute de 40 %
Comment le provisioning just-in-time et bin-packing de Karpenter remplace les node pools statiques pour réduire encore la facture de compute.
Sécuriser les secrets CI/CD avec HashiCorp Vault
Sortir les secrets des variables CI/CD et fichiers .env pour les stocker dans HashiCorp Vault, avec des identifiants à courte durée de vie.