Introduction
Ingress expose vos applications au monde extérieur. Une petite erreur de configuration peut divulguer des en-têtes, accepter des chiffrements faibles, contourner une authentification, ou surexposer des chemins réseau. Ce guide explique comment durcir la sécurité de Kubernetes Ingress avec des étapes pratiques, vérifiables, et des procédures de retour arrière claires. Vous allez :
- Réaliser un inventaire des versions et des points d'exposition pour des changements compatibles et testables.
- Appliquer un chemin de configuration sûr et limité pour TLS, mTLS, listes d'IP autorisées, limitation de débit, secrets, RBAC, NetworkPolicy et contrôles du load balancer.
- Vérifier les protections avec des commandes concrètes et des résultats attendus.
- Reconnaître les modes de panne et rétablir rapidement.
- Maintenir une checklist opérationnelle exécutable régulièrement.
Les exemples sont signalés comme « exemple construit ». Adaptez noms, espaces de noms et limites à votre environnement.
Inventaire des versions et de l'environnement
Avant toute modification, consignez ce que vous allez durcir. Des versions dépareillées ou une topologie ambiguë causent l'essentiel des mauvaises surprises.
Prérequis :
- Vous disposez de droits d'admin cluster ou délégués pour créer/modifier Ingress, Secrets, RBAC et NetworkPolicy dans les espaces de noms cibles.
- Vous connaissez le contrôleur Ingress utilisé (par exemple NGINX Ingress Controller) et son namespace.
- Vous contrôlez ou pouvez mettre à jour le DNS des noms d'hôtes.
- Vous avez ou pouvez émettre des certificats TLS valides pour vos domaines.
Inventaire (exemple construit) :
| Élément | Valeur d'exemple | Pourquoi c'est important |
|---|---|---|
| Version Kubernetes | 1.28 (exemple construit) | Détermine les API disponibles (ex. ValidatingAdmissionPolicy). |
| Contrôleur Ingress | ingress-nginx 1.9.x (exemple construit) | Définit les annotations et clés de ConfigMap supportées. |
| IngressClass | public-nginx (exemple construit) | Un lien explicite évite un routage par défaut accidentel. |
| Zone DNS | example.test (exemple construit) | Contrôle le routage host et les SAN TLS. |
| Load balancer | L4 cloud avec source ranges (exemple construit) | Permet de restreindre les IP entrantes en bordure. |
| Source TLS | AC interne et AC publique (exemple construit) | Choisit les racines mTLS et les certifs serveur. |
Commandes pour capturer l'état courant :
kubectl get ns
kubectl get deploy, ds, svc, cm -n ingress-nginx
kubectl get ingressclass
kubectl get ingress -A -o wide
kubectl version --short
Chemin de configuration sûr
Commencez par une cible étroite et à forte valeur que vous pouvez tester et rétablir facilement (par exemple un seul hostname). Puis étendez progressivement.
1) Lier à un IngressClass explicite
Assurez-vous que chaque ressource Ingress cible un contrôleur connu. Cela empêche des valeurs par défaut inconnues de capter le trafic.
IngressClass (exemple construit) :
apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
name: public-nginx
spec:
controller: k8s.io/ingress-nginx
2) Imposer TLS et des valeurs sûres par défaut
Utilisez un Secret TLS Kubernetes, redirigez HTTP vers HTTPS et activez HSTS. L'exemple suivant montre des annotations sûres pour NGINX Ingress Controller.
Création d'un secret TLS (exemple construit) :
kubectl create secret tls web-tls \
--namespace app \
--key server.key \
--cert server.crt
Ingress avec annotations sécurisées (exemple construit) :
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: web
namespace: app
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/ssl-protocols: "TLSv1.2 TLSv1.3"
nginx.ingress.kubernetes.io/hsts: "true"
nginx.ingress.kubernetes.io/hsts-max-age: "63072000"
nginx.ingress.kubernetes.io/hsts-include-subdomains: "true"
nginx.ingress.kubernetes.io/hsts-preload: "true"
nginx.ingress.kubernetes.io/server-tokens: "false"
nginx.ingress.kubernetes.io/proxy-body-size: "1m"
# Optionnel : affinez les chiffrements si votre parc client est maîtrisé
# nginx.ingress.kubernetes.io/ssl-ciphers: "ECDHE-ECDSA-AES256-GCM-SHA384: ECDHE-RSA-AES256-GCM-SHA384: TLS_AES_256_GCM_SHA384: TLS_AES_128_GCM_SHA256"
labels:
security-tier: hardened
spec:
ingressClassName: public-nginx
tls:
- hosts:
- app.example.test
secretName: web-tls
rules:
- host: app.example.test
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web-svc
port:
number: 80
Valeurs par défaut au niveau du contrôleur via ConfigMap (exemple construit) :
apiVersion: v1
kind: ConfigMap
metadata:
name: ingress-nginx-controller
namespace: ingress-nginx
data:
server-tokens: "false"
ssl-protocols: "TLSv1.2 TLSv1.3"
# Gardez une liste de chiffrements concise ; évitez les suites obsolètes
# ssl-ciphers: "TLS_AES_256_GCM_SHA384: TLS_AES_128_GCM_SHA256"
3) Ajouter l'authentification par certificat client (mTLS) quand c'est pertinent
Pour des portails d'admin ou des API internes, exigez un certificat client pour atteindre l'app. Stockez le bundle CA dans un Secret. Exemple (exemple construit) :
kubectl -n app create secret generic client-ca --from-file=ca.crt=ca.pem
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: admin
namespace: app
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/auth-tls-secret: "app/client-ca"
nginx.ingress.kubernetes.io/auth-tls-verify-client: "on"
nginx.ingress.kubernetes.io/auth-tls-verify-depth: "1"
nginx.ingress.kubernetes.io/auth-tls-pass-certificate-to-upstream: "true"
nginx.ingress.kubernetes.io/whitelist-source-range: "203.0.113.0/24,198.51.100.0/24"
labels:
security-tier: hardened
spec:
ingressClassName: public-nginx
tls:
- hosts: ["admin.example.test"]
secretName: web-tls
rules:
- host: admin.example.test
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: admin-svc
port:
number: 8443
4) Ajouter des allow-lists IP et de la limitation de débit
Restreignez les sources de trafic et limitez les abus.
Annotations (exemple construit) :
metadata:
annotations:
nginx.ingress.kubernetes.io/whitelist-source-range: "203.0.113.0/24,198.51.100.0/24"
nginx.ingress.kubernetes.io/limit-rps: "10"
nginx.ingress.kubernetes.io/limit-burst: "20"
Assurez la préservation de l'IP cliente au niveau du Service du contrôleur, afin que les allow-lists et le rate limiting évaluent les adresses réelles :
apiVersion: v1
kind: Service
metadata:
name: ingress-nginx-controller
namespace: ingress-nginx
spec:
type: LoadBalancer
externalTrafficPolicy: Local
loadBalancerSourceRanges:
- 0.0.0.0/0 # resserrez pour les endpoints admin, ex. CIDR corporate
5) Sécuriser les Secrets et limiter les permissions
- Conservez les secrets TLS dans le même namespace que l'Ingress qui les consomme.
- Restreignez qui peut créer/modifier Ingress et Secrets.
RBAC limitant les changements d'Ingress à un groupe dans le namespace app (exemple construit) :
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: ingress-maintainer
namespace: app
rules:
- apiGroups: ["networking.k8s.io"]
resources: ["ingresses"]
verbs: ["get","list","watch","create","update","patch"]
- apiGroups: [""]
resources: ["secrets"]
resourceNames: ["web-tls", "client-ca"]
verbs: ["get","list","watch","update","patch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: ingress-maintainer-binding
namespace: app
subjects:
- kind: Group
name: app-team # identité d'exemple
roleRef:
kind: Role
name: ingress-maintainer
apiGroup: rbac.authorization.k8s.io
Contrôle d'admission imposant TLS et un IngressClass spécifique (exemple construit) :
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
name: enforce-ingress-tls-and-class
spec:
matchConstraints:
resourceRules:
- apiGroups: ["networking.k8s.io"]
apiVersions: ["v1"]
operations: ["CREATE","UPDATE"]
resources: ["ingresses"]
validations:
- expression: "has(object.spec.tls) && size(object.spec.tls) > 0"
message: "Ingress must configure TLS."
- expression: "object.spec.ingressClassName == 'public-nginx'"
message: "Ingress must target approved ingressClassName."
---
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicyBinding
metadata:
name: enforce-ingress-tls-and-class-binding
spec:
policyName: enforce-ingress-tls-and-class
validationActions: ["Deny"]
6) Contraindre les chemins réseau
Assurez-vous que le contrôleur n'atteint que les backends nécessaires et que les pairs ne peuvent pas joindre le contrôleur de manière inattendue.
NetworkPolicy autorisant uniquement le contrôleur Ingress à appeler votre service (exemple construit) :
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-only-ingress
namespace: app
spec:
podSelector:
matchLabels:
app: web
policyTypes: ["Ingress"]
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: ingress-nginx
podSelector:
matchLabels:
app.kubernetes.io/name: ingress-nginx
ports:
- protocol: TCP
port: 80
Vérifications et diagnostics
Effectuez ces contrôles après chaque changement. Validez d'abord sur un seul hostname.
Référence rapide (résultats d'exemple construits) :
| Vérification | Commande | Attendu |
|---|---|---|
| Ingress admis | kubectl describe ingress -n app web | IngressClass public-nginx ; règles et TLS renseignés. |
| Adresse allouée | kubectl get ingress -n app web -o wide | ADDRESS renseignée avec IP/hostname du LB. |
| HTTP → HTTPS | curl -I http://app.example.test | 301/308 avec Location: https://app.example.test/ |
| Version TLS min | openssl s_client -connect app.example.test:443 -tls1_0 | Échec du handshake ; OK en TLSv1.2. |
| HSTS présent | curl -I https://app.example.test | En-tête Strict-Transport-Security présent. |
| mTLS appliqué | curl -I https://admin.example.test | 400/401/403 sans cert client ; 200 avec cert. |
| Limitation de débit | boucle curl | Certains 429 sous forte pression RPS. |
Détails :
- Admission et IngressClass
kubectl describe ingress -n app web
Attendu : annotations visibles, IngressClassName: public-nginx, pas de Deny dans Events.
- Redirection TLS et en-têtes
curl -I http://app.example.test
curl -I https://app.example.test
Attendu : HTTP renvoie 301/308 vers https. HTTPS renvoie 200 avec Strict-Transport-Security. Pas de fuite de version du serveur si server-tokens=false.
- Plancher de protocole (TLSv1.2+)
# Échec attendu avec protocoles obsolètes
openssl s_client -connect app.example.test:443 -tls1_0 </dev/null
# Succès attendu avec TLSv1.2
openssl s_client -connect app.example.test:443 -tls1_2 </dev/null | grep -i "protocol\|cipher"
Attendu : TLS 1.0 échoue ; TLS 1.2 réussit avec un chiffrement moderne.
- Application du mTLS
Sans certificat client :
curl -I https://admin.example.test
Attendu : 400/401/403 selon la configuration.
Avec certificat client (fichiers d'exemple construits) :
curl -I https://admin.example.test \
--cert client.crt --key client.key
Attendu : 200 avec en-têtes de l'upstream.
- Rate limiting et allow-lists
Depuis un réseau autorisé, envoyez une rafale :
for i in {1..50}; do curl -s -o /dev/null -w "%{http_code}\n" https://app.example.test; done | sort | uniq -c
Attendu : 200 et possiblement 429 selon RPS et burst.
Depuis un réseau bloqué (ou en resserrant temporairement la whitelist), attendez-vous à 403.
- Logs du contrôleur et des requêtes
kubectl logs -n ingress-nginx deploy/ingress-nginx-controller | tail -n 50
Recherchez des messages de handshake TLS, des notifications de rate limiting ou des échecs de certificat client. Des erreurs récurrentes indiquent une mauvaise configuration.
- Vérifications NetworkPolicy
Depuis un autre namespace, tentez d'atteindre le pod du service. Avec un pod de test temporaire, l'accès doit échouer si la policy est en place (exemple construit) :
kubectl run -n default tmp --image=alpine --restart=Never --rm -it -- sh -c "apk add --no-cache curl >/dev/null && curl -Is http://web.app.svc.cluster.local:80 || true"
Attendu : délai d'attente ou refus de connexion lorsque la policy bloque le trafic.
Modes de panne et reprise
Ce qui casse souvent et comment rétablir rapidement.
- Problèmes de secret TLS
- Symptôme : erreurs TLS côté edge ou logs du contrôleur sans certificat valide.
- Vérif : le type du secret est kubernetes.io/tls et correspond à tls.secretName de l'Ingress.
- Correctif :
kubectl -n app get secret web-tls -o yaml | grep type
kubectl -n app delete secret web-tls
kubectl -n app create secret tls web-tls --key server.key --cert server.crt
kubectl -n app rollout restart deploy/ingress-nginx-controller # si nécessaire
- HTTP ne redirige pas vers HTTPS
- Symptôme : HTTP 200 sur le port 80.
- Vérif : annotation ssl-redirect="true" au niveau Ingress ou contrôleur.
- Correctif : ajoutez l'annotation et réappliquez. Vérifiez avec curl -I.
- TLS 1.0/1.1 toujours acceptés
- Symptôme : openssl -tls1_0 réussit.
- Vérif : ssl-protocols dans annotations/ConfigMap.
- Correctif : définissez "TLSv1.2 TLSv1.3" ; redémarrez le contrôleur si nécessaire.
- mTLS verrouille des utilisateurs valides
- Symptôme : tout le monde reçoit 400/401/403.
- Vérifs : nom/clé du Secret CA, verify-depth, et si les clients envoient bien un cert.
- Retour arrière rapide : retirez les annotations auth-tls-* et réappliquez. Conservez un manifeste de secours pour restaurer vite.
- Limitation de débit trop agressive
- Symptôme : de nombreux 429 en charge normale.
- Correctif : augmentez limit-rps et limit-burst ou retirez temporairement. Vérifiez avec la boucle de rafale.
- Allow-list IP trop serrée
- Symptôme : 403 pour des utilisateurs attendus.
- Correctif : ajoutez les plages CIDR et réappliquez. Testez d'abord sur un host de staging.
- NetworkPolicy bloque les backends
- Symptôme : 502/504 côté contrôleur ; logs montrant des échecs de connexion upstream.
- Vérifs : sélecteurs de namespace/pod dans la policy, numéros de ports.
- Correctif : ajustez sélecteurs/ports ; retestez depuis le namespace du contrôleur.
- Les source ranges du LB bloquent les sondes
- Symptôme : LB jamais sain ; champ ADDRESS en attente ou trafic trou noir.
- Correctif : incluez les IP des sondes du fournisseur dans loadBalancerSourceRanges. Pour confirmer, élargissez temporairement à 0.0.0.0/0, puis resserrez.
Guidage de rollback :
- Exportez toujours le manifeste courant avant les changements :
kubectl -n app get ingress web -o yaml > web-backup-$(date +%Y%m%d%H%M%S).yaml
- En cas de casse, réappliquez immédiatement le backup :
kubectl apply -f web-backup-20240101123456.yaml
- Pour des changements globaux (ConfigMap, Service), mettez en scène hors production. Si la prod est impactée, revenez à la version précédente et redémarrez le déploiement du contrôleur pour reprendre les réglages.
Vérification d'expiration de certificat et rétablissement (exemple construit) :
# Décoder et vérifier la date Not After
kubectl -n app get secret web-tls -o jsonpath='{.data.tls\.crt}' | base64 -d | openssl x509 -noout -enddate
# Renouveler en remplaçant le secret, puis revérifier
kubectl -n app create secret tls web-tls --key new.key --cert new.crt --dry-run=client -o yaml | kubectl apply -f -
Liste de contrôle opérationnelle
À utiliser lors des revues régulières et avant/après changements.
- Inventaire
- Confirmez les versions de Kubernetes, du contrôleur et de l'IngressClass.
- Dressez la liste des hosts exposés à Internet et leurs namespaces.
- TLS et en-têtes
- Tous les hosts publics imposent HTTPS avec HSTS ; TLSv1.2+ uniquement.
- Les secrets sont de type kubernetes.io/tls et non partagés entre namespaces sans lien.
- Pas de fuite de version serveur (server-tokens à false).
- Contrôle d'accès
- mTLS requis pour les endpoints admin ou internes.
- Allow-lists IP présentes pour les services restreints.
- Limites de débit ajustées avec marge.
- Permissions et politiques
- RBAC limite la modification d'Ingress et des secrets associés aux équipes autorisées.
- ValidatingAdmissionPolicy impose TLS et l'IngressClass approuvé.
- NetworkPolicies n'autorisent que le contrôleur à joindre les backends.
- Load balancer
- externalTrafficPolicy=Local si vous avez besoin de l'IP cliente.
- loadBalancerSourceRanges restreint les IP entrantes quand c'est possible.
- Vérification
- Les checks curl et openssl passent pour chaque host.
- Les logs du contrôleur ne montrent pas d'erreurs TLS ou upstream récurrentes.
- Préparation à la reprise
- Manifeste exporté avant changement ; backups fiables disponibles.
- Étapes de rollback documentées et testées sur un host de staging.
Conclusion
Vous avez durci Kubernetes Ingress en liant explicitement un IngressClass, en imposant TLS et HSTS, en ajoutant mTLS quand nécessaire, en restreignant le trafic via allow-lists IP et limites de débit, en verrouillant secrets et RBAC, en appliquant des NetworkPolicies et en resserrant l'exposition du load balancer. Vous avez vérifié chaque contrôle avec des checks observables et préparé des procédures de retour arrière pour une reprise rapide. Étendez maintenant de l'hôte durci à tous les services exposés à Internet, en gardant des changements étroits, testables et réversibles. En itérant avec un plan clair et des vérifications mesurables, vous réduisez le rework et maintenez des points d'entrée sûrs.