Skip to content

Sécurité dans Kubernetes

La sécurité dans Kubernetes est un sujet vaste qui couvre plusieurs couches : l'accès à l'API, l'isolation des workloads, la gestion des secrets, et les restrictions de ressources. Ce chapitre présente les mécanismes essentiels pour sécuriser un cluster.

RBAC (Role-Based Access Control)

Le RBAC est le mécanisme de contrôle d'accès dans Kubernetes. Il permet de définir qui peut faire quoi sur quelles ressources.

Concepts clés

-> Subjects : Les entités qui effectuent des actions

  • Users : utilisateurs humains (non gérés par K8s)
  • Groups : groupes d'utilisateurs
  • ServiceAccounts : identités pour les pods

-> Resources : Les objets K8s (pods, services, deployments, etc.)

-> Verbs : Les actions possibles

  • get, list, watch : lecture
  • create, update, patch : modification
  • delete : suppression

Les 4 objets RBAC

ObjetPortéeDescription
RoleNamespaceDéfinit des permissions dans un namespace
ClusterRoleClusterDéfinit des permissions au niveau cluster
RoleBindingNamespaceLie un Role/ClusterRole à un subject dans un namespace
ClusterRoleBindingClusterLie un ClusterRole à un subject au niveau cluster

Exemple : Role et RoleBinding

Créons un Role qui permet de lire les pods dans le namespace dev :

yaml
# role-pod-reader.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: dev
  name: pod-reader
rules:
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["get", "list", "watch"]
- apiGroups: [""]
  resources: ["pods/log"]
  verbs: ["get"]

Maintenant, lions ce Role à un utilisateur :

yaml
# rolebinding-pod-reader.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: read-pods
  namespace: dev
subjects:
- kind: User
  name: alice
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: Role
  name: pod-reader
  apiGroup: rbac.authorization.k8s.io

Exemple : ClusterRole pour un ServiceAccount

Pour qu'un pod puisse lister les nodes du cluster :

yaml
# clusterrole-node-reader.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: node-reader
rules:
- apiGroups: [""]
  resources: ["nodes"]
  verbs: ["get", "list", "watch"]
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: monitoring-sa
  namespace: monitoring
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: monitoring-node-reader
subjects:
- kind: ServiceAccount
  name: monitoring-sa
  namespace: monitoring
roleRef:
  kind: ClusterRole
  name: node-reader
  apiGroup: rbac.authorization.k8s.io

Mise en pratique

-> Créez un namespace et appliquez le RBAC

sh
kubectl create ns dev
kubectl apply -f role-pod-reader.yaml
kubectl apply -f rolebinding-pod-reader.yaml

-> Vérifiez les permissions

sh
kubectl auth can-i list pods --namespace=dev --as=alice
# yes
kubectl auth can-i delete pods --namespace=dev --as=alice
# no
kubectl auth can-i list pods --namespace=default --as=alice
# no

-> Listez les roles et bindings

sh
kubectl get roles -n dev
kubectl get rolebindings -n dev
kubectl describe role pod-reader -n dev

Network Policies

Les Network Policies permettent de contrôler le trafic réseau entre les pods. Par défaut, tous les pods peuvent communiquer entre eux. Les Network Policies ajoutent des règles de firewall au niveau du cluster.

WARNING

Les Network Policies nécessitent un CNI (Container Network Interface) qui les supporte, comme Calico, Cilium, ou Weave Net. Le CNI par défaut de Minikube ne les supporte pas nativement.

Activer les Network Policies sur Minikube

sh
minikube start --cni=calico

Politique par défaut : Deny All

Une bonne pratique est de bloquer tout le trafic par défaut, puis d'ouvrir au cas par cas :

yaml
# deny-all.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: production
spec:
  podSelector: {}
  policyTypes:
  - Ingress
  - Egress

Autoriser le trafic entrant depuis certains pods

Autorisons les pods avec le label role: frontend à accéder aux pods role: backend sur le port 8080 :

yaml
# allow-frontend-to-backend.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-frontend-to-backend
  namespace: production
spec:
  podSelector:
    matchLabels:
      role: backend
  policyTypes:
  - Ingress
  ingress:
  - from:
    - podSelector:
        matchLabels:
          role: frontend
    ports:
    - protocol: TCP
      port: 8080

Autoriser le trafic sortant vers Internet

Autorisons les pods à accéder à Internet (DNS + HTTPS) :

yaml
# allow-egress-internet.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-egress-internet
  namespace: production
spec:
  podSelector:
    matchLabels:
      needs-internet: "true"
  policyTypes:
  - Egress
  egress:
  # DNS
  - to:
    - namespaceSelector: {}
    ports:
    - protocol: UDP
      port: 53
  # HTTPS
  - to:
    - ipBlock:
        cidr: 0.0.0.0/0
        except:
        - 10.0.0.0/8
        - 172.16.0.0/12
        - 192.168.0.0/16
    ports:
    - protocol: TCP
      port: 443

Mise en pratique

-> Appliquez une politique deny-all

sh
kubectl create ns production
kubectl apply -f deny-all.yaml

-> Testez la connectivité (elle devrait échouer)

sh
# Créer deux pods de test
kubectl run frontend --image=nginx -n production -l role=frontend
kubectl run backend --image=nginx -n production -l role=backend

# Tester la connexion (timeout attendu)
kubectl exec -n production frontend -- curl --max-time 5 backend

-> Appliquez la politique d'autorisation

sh
kubectl apply -f allow-frontend-to-backend.yaml

# Retester (devrait fonctionner)
kubectl exec -n production frontend -- curl --max-time 5 backend

ResourceQuota et LimitRange

Ces objets permettent de limiter les ressources consommées dans un namespace.

ResourceQuota

Limite les ressources totales d'un namespace :

yaml
# resourcequota.yaml
apiVersion: v1
kind: ResourceQuota
metadata:
  name: compute-quota
  namespace: dev
spec:
  hard:
    requests.cpu: "4"
    requests.memory: 8Gi
    limits.cpu: "8"
    limits.memory: 16Gi
    pods: "20"
    services: "10"
    secrets: "20"
    configmaps: "20"
    persistentvolumeclaims: "10"

LimitRange

Définit des valeurs par défaut et des limites pour les containers :

yaml
# limitrange.yaml
apiVersion: v1
kind: LimitRange
metadata:
  name: default-limits
  namespace: dev
spec:
  limits:
  - default:
      cpu: "500m"
      memory: "512Mi"
    defaultRequest:
      cpu: "100m"
      memory: "128Mi"
    max:
      cpu: "2"
      memory: "2Gi"
    min:
      cpu: "50m"
      memory: "64Mi"
    type: Container

Mise en pratique

-> Appliquez les quotas

sh
kubectl apply -f resourcequota.yaml
kubectl apply -f limitrange.yaml

-> Vérifiez les quotas

sh
kubectl get resourcequota -n dev
kubectl describe resourcequota compute-quota -n dev
# Used:
#   limits.cpu: 0
#   limits.memory: 0
#   ...

-> Testez le dépassement

sh
# Ce pod sera refusé si le quota est dépassé
kubectl run test --image=nginx -n dev --requests='cpu=10,memory=20Gi'
# Error: exceeded quota

Secrets

Les Secrets permettent de stocker des données sensibles (mots de passe, tokens, clés SSH).

WARNING

Par défaut, les Secrets sont stockés en base64 (encodés, pas chiffrés). Pour une vraie sécurité en production, activez le chiffrement at-rest d'etcd.

Créer un Secret

sh
# Via kubectl
kubectl create secret generic db-credentials \
  --from-literal=username=admin \
  --from-literal=password=supersecret

# Ou via un fichier
kubectl create secret generic tls-cert \
  --from-file=tls.crt=./server.crt \
  --from-file=tls.key=./server.key

Utiliser un Secret dans un Pod

yaml
# pod-with-secret.yaml
apiVersion: v1
kind: Pod
metadata:
  name: app-with-secrets
spec:
  containers:
  - name: app
    image: nginx
    env:
    # En variable d'environnement
    - name: DB_USERNAME
      valueFrom:
        secretKeyRef:
          name: db-credentials
          key: username
    - name: DB_PASSWORD
      valueFrom:
        secretKeyRef:
          name: db-credentials
          key: password
    # Ou en fichier monté
    volumeMounts:
    - name: secret-volume
      mountPath: /etc/secrets
      readOnly: true
  volumes:
  - name: secret-volume
    secret:
      secretName: db-credentials

Bonnes pratiques pour les Secrets

  1. Ne jamais commiter de secrets dans Git
  2. Utiliser des outils comme Sealed Secrets, Vault, ou External Secrets Operator
  3. Activer le chiffrement at-rest pour etcd
  4. Limiter l'accès aux secrets via RBAC

Sécurisation de l'accès à etcd

etcd contient toutes les données du cluster, y compris les secrets. Sa sécurisation est critique.

Bonnes pratiques

-> Chiffrement at-rest

Configurer Kubernetes pour chiffrer les données dans etcd :

yaml
# encryption-config.yaml
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
  - secrets
  providers:
  - aescbc:
      keys:
      - name: key1
        secret: <base64-encoded-32-byte-key>
  - identity: {}

-> Accès réseau restreint

  • etcd ne doit être accessible que par l'API Server
  • Utiliser TLS pour toutes les communications
  • Firewall entre etcd et le reste du réseau

-> Authentification TLS mutuelle

L'API Server et etcd doivent s'authentifier mutuellement avec des certificats.

Security Context

Le Security Context définit les privilèges et les restrictions d'un pod ou d'un container.

Exemple : Pod non-root

yaml
# secure-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: secure-pod
spec:
  securityContext:
    runAsNonRoot: true
    runAsUser: 1000
    runAsGroup: 3000
    fsGroup: 2000
  containers:
  - name: app
    image: nginx
    securityContext:
      allowPrivilegeEscalation: false
      readOnlyRootFilesystem: true
      capabilities:
        drop:
        - ALL
    volumeMounts:
    - name: tmp
      mountPath: /tmp
    - name: cache
      mountPath: /var/cache/nginx
    - name: run
      mountPath: /var/run
  volumes:
  - name: tmp
    emptyDir: {}
  - name: cache
    emptyDir: {}
  - name: run
    emptyDir: {}

Options importantes

OptionDescription
runAsNonRootEmpêche l'exécution en root
runAsUserUID de l'utilisateur
readOnlyRootFilesystemFilesystem en lecture seule
allowPrivilegeEscalationEmpêche l'escalade de privilèges
capabilities.dropSupprime des capabilities Linux

Checklist sécurité

Voici une checklist pour sécuriser un cluster Kubernetes :

Accès et authentification

  • [ ] RBAC activé et configuré (principe du moindre privilège)
  • [ ] ServiceAccounts dédiés par application
  • [ ] Pas de token ServiceAccount monté par défaut (automountServiceAccountToken: false)

Réseau

  • [ ] Network Policies en place (deny by default)
  • [ ] CNI supportant les Network Policies (Calico, Cilium)
  • [ ] API Server accessible uniquement depuis des IPs autorisées

Workloads

  • [ ] Pods exécutés en non-root
  • [ ] Images de base minimales et à jour
  • [ ] Scan de vulnérabilités des images (Trivy, Clair)
  • [ ] Pas de containers privilégiés

Secrets

  • [ ] Secrets chiffrés at-rest dans etcd
  • [ ] Pas de secrets dans les variables d'environnement (préférer les volumes)
  • [ ] Rotation régulière des secrets

Ressources

  • [ ] ResourceQuota par namespace
  • [ ] LimitRange avec des valeurs par défaut
  • [ ] Requests et limits définis sur tous les pods

Monitoring

  • [ ] Audit logs activés
  • [ ] Alertes sur les événements de sécurité
  • [ ] Monitoring des accès à l'API Server