CKS - System Hardening
🎯 Objectifs
Tu vas sécuriser les nœuds Kubernetes et les workloads :
- ✅ Configurer AppArmor pour les pods
- ✅ Configurer SELinux sur les nœuds
- ✅ Implémenter Pod Security Standards (PSS)
- ✅ Utiliser les Security Contexts
- ✅ Appliquer le kernel hardening
- ✅ Sécuriser l'accès SSH aux nœuds
🛡️ Pod Security Standards (PSS)
Pourquoi ?
Un pod mal configuré peut être une faille de sécurité. PSS te force à suivre des bonnes pratiques comme :
- Ne pas exécuter en tant que
root - Ne pas autoriser les privileges escalations
- Utiliser un filesystem read-only
Les 3 niveaux PSS
| Niveau | Permissif? | Use case |
|---|---|---|
| Restricted | Non | Production, haute sécurité |
| Baseline | Oui | Compat pour apps existantes |
| Privileged | Très permissif | Systèmes, outils infra |
Exemple : Enforcer le Restricted au niveau du cluster
apiVersion: v1
kind: Namespace
metadata:
name: production
labels:
# Enforcer = refuser les pods qui ne sont pas restricted
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/enforce-version: latest
# Audit = logger les violations (mais autoriser)
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/audit-version: latest
# Warn = avertir l'utilisateur (mais autoriser)
pod-security.kubernetes.io/warn: restricted
pod-security.kubernetes.io/warn-version: latestÀ faire :
- Crée deux namespaces :
prod-restrictedetdev-baseline - Applique le PSS restricted à prod
- Applique le PSS baseline à dev
- Essaie de déployer un pod avec
runAsUser: 0dans chaque (devrait échouer/accepter)
🔐 Security Contexts
Pourquoi ?
Un Security Context dit au pod : "Tu dois exécuter en tant que l'utilisateur 1000, tu ne peux pas escalader tes privilèges, ton filesystem est read-only sauf /tmp."
Exemple complet
apiVersion: v1
kind: Pod
metadata:
name: secure-app
spec:
# Au niveau du pod
securityContext:
runAsNonRoot: true # Obligatoire : ne pas être root
runAsUser: 1000 # Utilisateur non-root
fsGroup: 2000 # Groupe pour les volumes
seccompProfile:
type: RuntimeDefault # Utiliser seccomp par défaut
containers:
- name: app
image: myapp:latest
# Au niveau du conteneur (override du pod)
securityContext:
allowPrivilegeEscalation: false # Ne JAMAIS escalader
capabilities:
drop:
- ALL # Retire TOUS les Linux caps
add:
- NET_BIND_SERVICE # Ajoute uniquement ce qui est nécessaire
readOnlyRootFilesystem: true # Filesystem read-only
volumeMounts:
- name: tmp
mountPath: /tmp # /tmp en read-write
- name: cache
mountPath: /app/cache
volumes:
- name: tmp
emptyDir: {}
- name: cache
emptyDir: {}À faire :
- Déploie ce pod secure-app
- Entre dans le pod :
kubectl exec -it secure-app -- sh - Essaie d'écrire dans
/app(échouera) - Écris dans
/tmp(succès)
🚨 AppArmor
Pourquoi ?
AppArmor est un module de sécurité Linux qui restreint chaque processus aux ressources dont il a besoin. Plus granulaire que Pod Security Standards.
Concepts clés
AppArmor profile = liste de permissions pour un processus
- Quels fichiers peut-il accéder ?
- Quels capabilities Linux a-t-il besoin ?
- Peut-il écouter sur des ports réseau ?
Exemple : Profile restrictif
#include <tunables/global>
profile my-app flags=(attach_disconnected,mediate_deleted) {
#include <abstractions/base>
# Lecture seule des fichiers système
/etc/config/app.conf r,
/app/bin/** rix,
# Écriture dans /tmp et /var/log
/tmp/** rw,
/var/log/app.log w,
# Capacités Linux nécessaires
capability setuid,
capability dac_override,
# Réseau
network inet stream,
network inet dgram,
}Utiliser AppArmor avec Kubernetes
apiVersion: v1
kind: Pod
metadata:
name: app-with-apparmor
annotations:
container.apparmor.security.beta.kubernetes.io/app: localhost/my-app
spec:
containers:
- name: app
image: myapp:latestÀ faire :
- Crée un AppArmor profile simple
- Applique-le à un pod
- Teste que le pod ne peut faire que ce qui est autorisé
🔒 SELinux
Pourquoi ?
SELinux (Security Enhanced Linux) est un système de contrôle d'accès mandatory très puissant mais complexe. RHEL/Fedora l'utilisent par défaut.
Modes SELinux
- Enforcing : bloque les actions non-autorisées
- Permissive : log les violations mais autorise
- Disabled : complètement désactivé
Dans Kubernetes
apiVersion: v1
kind: Pod
metadata:
name: app-with-selinux
spec:
securityContext:
seLinuxOptions:
level: "s0:c123,c456" # MCS levels (compartments)
containers:
- name: app
image: myapp:latestÀ faire :
- Vérifie le mode SELinux :
getenforce - Crée un pod avec SELinux levels
- Observe les logs SELinux :
ausearch -m avc
🖥️ Kernel Hardening
Paramètres clés
# Désactiver l'IP forwarding non-nécessaire
sysctl -w net.ipv4.ip_forward=0
# Ignorer les ICMP redirects (anti-MITM)
sysctl -w net.ipv4.conf.all.accept_redirects=0
# Ignorer les broadcast pings
sysctl -w net.ipv4.icmp_echo_ignore_broadcasts=1
# Activer reverse path filtering (anti-spoofing)
sysctl -w net.ipv4.conf.all.rp_filter=1
# Désactiver magic SysRq (prévient les DoS)
sysctl -w kernel.sysrq=0
# Augmenter les inotify watches (pour les tools de monitoring)
sysctl -w fs.inotify.max_user_watches=524288À faire :
- Applique ces
sysctlvia une DaemonSet - Persiste-les dans
/etc/sysctl.d/99-hardening.conf - Vérifie avec
sysctl -a | grep net.ipv4
🔑 SSH Security
Bonnes pratiques
# /etc/ssh/sshd_config
# Désactiver root login
PermitRootLogin no
# Désactiver password auth (key-based only)
PasswordAuthentication no
PubkeyAuthentication yes
# Désactiver X11 forwarding
X11Forwarding no
# Désactiver empty passwords
PermitEmptyPasswords no
# Limiter les tentatives
MaxAuthTries 3
# Timeouts
ClientAliveInterval 300
ClientAliveCountMax 2À faire :
- Configure sshd.config avec ces paramètres
- Redémarre SSH :
systemctl restart sshd - Teste avec une clé publique (devrait marcher)
- Teste sans clé (devrait échouer)
📝 Checklist System Hardening
- Pod Security Standards : restricted en production
- Security Contexts : tous les pods ont runAsNonRoot: true
- Capabilities : DROP ALL, ADD seulement le nécessaire
- AppArmor : profiles définis et appliqués
- SELinux : en permissive ou enforcing (jamais disabled)
- Kernel : sysctl hardening appliqué via DaemonSet
- SSH : password auth désactivée
- Filesystem : /etc/passwd immuable sur les nœuds