Aller au contenu

Lab 7.3 : NetworkPolicies

Durée 60 à 75 min
Niveau Guidé
Prérequis Lab 3.2 réalisé (Ingress, curl.exe, fichier hosts), cours du chapitre 7 lu
Acquis visés AA4

!!! abstract "Objectifs" - Constater que tous les Pods communiquent par défaut - Appliquer un « deny all » puis autoriser uniquement les flux nécessaires - Autoriser le trafic de l'Ingress controller (Traefik) vers le frontend - Distinguer un namespaceSelector seul d'un namespaceSelector combiné à un podSelector - Filtrer le trafic sortant et autoriser le DNS

Contexte et schéma

Vous protégez une application à trois niveaux, simulée par trois Pods nginx étiquetés tier: frontend, tier: api et tier: db. Seuls les flux du schéma doivent être autorisés.

flowchart LR
  T["Traefik<br/>(kube-system)"] --> F["frontend"]
  F --> A["api"]
  A --> D["db"]
  F -. "bloqué" .-> D
  X["autre Pod"] -. "bloqué" .-> A
  X -. "bloqué" .-> D

À chaque étape, vous mesurez les flux avec un petit script : un flux réussi affiche OK, un flux bloqué affiche BLOQUE (après 3 secondes de délai).

Étapes

Étape 1 : Déployer l'application et mesurer l'état initial

k create namespace tp-k8s
k config set-context --current --namespace=tp-k8s

cat <<'EOF' > app.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: frontend
spec:
  replicas: 1
  selector:
    matchLabels: { app: appdemo, tier: frontend }
  template:
    metadata:
      labels: { app: appdemo, tier: frontend }
    spec:
      containers:
        - name: web
          image: nginx:1.26-alpine
---
apiVersion: v1
kind: Service
metadata:
  name: frontend
spec:
  selector: { app: appdemo, tier: frontend }
  ports:
    - port: 80
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
spec:
  replicas: 1
  selector:
    matchLabels: { app: appdemo, tier: api }
  template:
    metadata:
      labels: { app: appdemo, tier: api }
    spec:
      containers:
        - name: web
          image: nginx:1.26-alpine
---
apiVersion: v1
kind: Service
metadata:
  name: api
spec:
  selector: { app: appdemo, tier: api }
  ports:
    - port: 80
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: db
spec:
  replicas: 1
  selector:
    matchLabels: { app: appdemo, tier: db }
  template:
    metadata:
      labels: { app: appdemo, tier: db }
    spec:
      containers:
        - name: web
          image: nginx:1.26-alpine
---
apiVersion: v1
kind: Service
metadata:
  name: db
spec:
  selector: { app: appdemo, tier: db }
  ports:
    - port: 80
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: app-ingress
spec:
  ingressClassName: traefik
  rules:
    - host: app.k3s.local
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: frontend
                port:
                  number: 80
EOF
k apply -f app.yaml
k rollout status deployment/frontend
k rollout status deployment/api
k rollout status deployment/db

Créez le script de mesure :

cat <<'EOF' > matrice.sh
#!/bin/bash
t() {
  printf '%-32s' "$1"
  if eval "$2" >/dev/null 2>&1; then echo "OK"; else echo "BLOQUE"; fi
}
t "frontend -> api"            "kubectl exec deploy/frontend -- wget -T 3 -qO- http://api"
t "frontend -> db"             "kubectl exec deploy/frontend -- wget -T 3 -qO- http://db"
t "api -> db"                  "kubectl exec deploy/api -- wget -T 3 -qO- http://db"
t "autre Pod -> api"           "kubectl run autre --rm -i --image=busybox:1.36 --restart=Never -- wget -T 3 -qO- http://api"
t "autre Pod -> db"            "kubectl run autre --rm -i --image=busybox:1.36 --restart=Never -- wget -T 3 -qO- http://db"
t "Pod kube-system -> frontend" "kubectl run intrus -n kube-system --rm -i --image=busybox:1.36 --restart=Never -- wget -T 3 -qO- http://frontend.tp-k8s"
EOF
chmod +x matrice.sh
./matrice.sh

Testez aussi l'accès par l'Ingress depuis le poste Windows (remplacez <IP_NOEUD> par l'adresse d'un nœud) :

curl.exe -m 5 -H "Host: app.k3s.local" http://<IP_NOEUD>

!!! success "Point de contrôle" - [ ] Les six flux du script affichent OK, y compris ceux qui devraient être interdits (frontend -> db, autre Pod -> db) - [ ] L'Ingress répond avec la page d'accueil de nginx

Étape 2 : Tout interdire en entrée

cat <<'EOF' > default-deny.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-ingress
spec:
  podSelector: {}
  policyTypes: ["Ingress"]
EOF
k apply -f default-deny.yaml
k get networkpolicy
./matrice.sh
curl.exe -m 5 -H "Host: app.k3s.local" http://<IP_NOEUD>

Tous les flux sont désormais bloqués, y compris l'Ingress. Un flux bloqué se manifeste par un délai dépassé, et non par un refus de connexion.

!!! success "Point de contrôle" - [ ] Les six flux affichent BLOQUE - [ ] curl.exe échoue par dépassement du délai

Étape 3 : Autoriser les flux nécessaires

Une politique par flux autorisé. Pour Traefik, on autorise le namespace kube-system.

cat <<'EOF' > allow.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-traefik-to-frontend
spec:
  podSelector:
    matchLabels:
      tier: frontend
  policyTypes: ["Ingress"]
  ingress:
    - from:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: kube-system
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-frontend-to-api
spec:
  podSelector:
    matchLabels:
      tier: api
  policyTypes: ["Ingress"]
  ingress:
    - from:
        - podSelector:
            matchLabels:
              tier: frontend
      ports:
        - protocol: TCP
          port: 80
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-api-to-db
spec:
  podSelector:
    matchLabels:
      tier: db
  policyTypes: ["Ingress"]
  ingress:
    - from:
        - podSelector:
            matchLabels:
              tier: api
      ports:
        - protocol: TCP
          port: 80
EOF
k apply -f allow.yaml
k get networkpolicy
k describe networkpolicy allow-api-to-db
./matrice.sh
curl.exe -m 5 -H "Host: app.k3s.local" http://<IP_NOEUD>

Relisez les résultats : frontend -> api et api -> db fonctionnent, frontend -> db et les flux de autre Pod sont bloqués. L'Ingress fonctionne de nouveau. Mais Pod kube-system -> frontend réussit encore : la règle pour Traefik est trop large.

!!! success "Point de contrôle" - [ ] frontend -> api et api -> db sont OK - [ ] frontend -> db, autre Pod -> api et autre Pod -> db sont BLOQUE - [ ] L'Ingress répond - [ ] Pod kube-system -> frontend est encore OK (règle trop large)

Étape 4 : Affiner avec ET (namespace et Pod)

Repérez les labels du Pod Traefik :

k get pods -n kube-system --show-labels | grep traefik

Combinez ensuite un namespaceSelector et un podSelector dans le même élément de la liste from (donc en ET) :

cat <<'EOF' > allow-traefik.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-traefik-to-frontend
spec:
  podSelector:
    matchLabels:
      tier: frontend
  policyTypes: ["Ingress"]
  ingress:
    - from:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: kube-system
          podSelector:
            matchLabels:
              app.kubernetes.io/name: traefik
EOF
k apply -f allow-traefik.yaml
./matrice.sh
curl.exe -m 5 -H "Host: app.k3s.local" http://<IP_NOEUD>

Adaptez le label app.kubernetes.io/name: traefik à celui que vous avez relevé. Le Pod de kube-system qui n'est pas Traefik est maintenant bloqué, alors que l'Ingress continue de fonctionner.

Un tiret de plus, un sens différent

Si vous écrivez - namespaceSelector: ... et - podSelector: ... comme deux éléments de la liste (deux tirets), les sources s'additionnent (OU) : tous les Pods de kube-system, ou les Pods portant ce label dans le namespace courant. Ce n'est pas ce que l'on veut ici.

!!! success "Point de contrôle" - [ ] Pod kube-system -> frontend est maintenant BLOQUE - [ ] L'Ingress fonctionne toujours - [ ] Vous pouvez expliquer la différence entre un seul élément (ET) et deux éléments (OU)

Étape 5 : Filtrer le trafic sortant

Interdisez tout le trafic sortant des Pods du frontend :

cat <<'EOF' > egress-deny.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: frontend-deny-egress
spec:
  podSelector:
    matchLabels:
      tier: frontend
  policyTypes: ["Egress"]
EOF
k apply -f egress-deny.yaml
k exec deploy/frontend -- wget -T 3 -qO- http://api

L'appel échoue, avec une erreur de résolution de nom (bad address) : le DNS lui-même est bloqué. Autorisez-le, puis le flux vers l'API :

cat <<'EOF' > egress-allow.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: frontend-allow-egress
spec:
  podSelector:
    matchLabels:
      tier: frontend
  policyTypes: ["Egress"]
  egress:
    - to:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: kube-system
      ports:
        - protocol: UDP
          port: 53
        - protocol: TCP
          port: 53
    - to:
        - podSelector:
            matchLabels:
              tier: api
      ports:
        - protocol: TCP
          port: 80
EOF
k apply -f egress-allow.yaml
./matrice.sh
curl.exe -m 5 -H "Host: app.k3s.local" http://<IP_NOEUD>

!!! success "Point de contrôle" - [ ] Avec frontend-deny-egress seule, frontend -> api échoue sur la résolution DNS - [ ] Avec frontend-allow-egress, frontend -> api fonctionne et frontend -> db reste BLOQUE - [ ] L'Ingress fonctionne toujours

Résultats attendus

Flux Étape 1 Étape 2 Étape 3 Étape 4 Étape 5
frontend → api OK BLOQUE OK OK OK
frontend → db OK BLOQUE BLOQUE BLOQUE BLOQUE
api → db OK BLOQUE OK OK OK
autre Pod → api OK BLOQUE BLOQUE BLOQUE BLOQUE
autre Pod → db OK BLOQUE BLOQUE BLOQUE BLOQUE
Pod kube-system → frontend OK BLOQUE OK BLOQUE BLOQUE
Ingress (Traefik → frontend) OK BLOQUE OK OK OK

Questions de réflexion

Pourquoi poser d'abord un « deny all » avant d'ajouter des autorisations ?

Dès qu'un Pod est sélectionné par une politique, ce qui n'est pas explicitement autorisé est refusé. En partant de « tout interdit », on obtient une liste de flux autorisés explicite et vérifiable.

Pourquoi l'Ingress a-t-il cessé de fonctionner après le « deny all » ?

Traefik est un Pod du namespace kube-system : son trafic vers le frontend est du trafic entrant pour le namespace tp-k8s, bloqué comme les autres. Il faut l'autoriser explicitement.

Quelle différence entre un namespaceSelector seul et un namespaceSelector combiné à un podSelector dans le même élément ?

Seul, il autorise tous les Pods du namespace. Combiné dans le même élément, il n'autorise que les Pods du namespace qui portent aussi le label : c'est un ET.

Comment autoriser en plus un Job de sauvegarde, étiqueté tier: backup, à joindre la base ?

Ajouter un second élément à la liste from de la politique de la base (un nouveau tiret avec podSelector: tier: backup), avec le même port.

Pourquoi faut-il autoriser le DNS quand on filtre le trafic sortant ?

Les Pods résolvent les noms de Services avec CoreDNS, situé dans kube-system. Sans autorisation vers le port 53, aucun nom n'est résolu et les appels échouent, même vers des destinations autorisées.

Dépannage

Symptôme Pistes
Tout reste OK après un « deny all » Vérifier que la politique existe dans le bon namespace (k get networkpolicy)
Tout est bloqué, Ingress compris Flux oublié : Traefik vers le frontend, DNS (si egress filtré)
curl.exe : délai dépassé après les autorisations Label de Traefik incorrect dans la politique : k get pods -n kube-system --show-labels
wget: bad address Résolution DNS bloquée : autoriser le port 53 vers kube-system
La politique d'Ingress ne bloque rien Mauvais podSelector ou labels des Pods : k get pods --show-labels
Le script affiche BLOQUE pour tout dès l'étape 1 Pods non prêts ou accès Internet des VMs (image busybox) : k get pods, relancer
Pod autre ou intrus déjà existant k delete pod autre, k delete pod intrus -n kube-system
Résultat différent de celui attendu Comparer avec le tableau des résultats attendus et relire les sélecteurs de chaque politique

Nettoyage

k delete namespace tp-k8s
k config set-context --current --namespace=default

La suppression du namespace supprime aussi ses NetworkPolicies et son Ingress. Supprimez de votre fichier hosts Windows la ligne app.k3s.local, si vous l'aviez ajoutée.