Sommaire
Au sein d’un cluster Kubernetes, il vient rapidement un moment où l’on souhaite rajouter une couche de sécurité, notamment en ce qui concerne les flux réseaux.
Dans une optique de réduction des coûts, on peut se retrouver à vouloir mutualiser sur un même cluster plusieurs ensembles (contrats, clients, etc.) qui ne doivent en aucun cas pouvoir communiquer entre eux. Toutefois, chacun de ces ensembles doit pouvoir communiquer librement avec les autres composants communs du cluster (Kube DNS, Kubernetes API, etc.) et être atteignable par des composants définis (Ingress controller, stack de monitoring, etc.)
Pour résoudre cette problématique, différentes solutions peuvent être mises en place. Nous verrons les avantages et inconvénients de chacune et le choix vers lequel nous nous sommes dirigés au final.
Solutions envisagées
Après une rapide analyse, deux principales solutions ont été posées sur la table : l’utilisation d’une solution de service mesh a l’instar d’istio ou linkerd ; ou l’utilisation des Network Policies (objet natif apparu en 2017 dans kubernetes 1.8).
Services Mesh
Avantages des services Mesh
- Contrôle granulaire des flux : ils permettent un contrôle précis des communications entre les différents services du cluster.
- Observabilité : ils offrent une meilleure visibilité sur les flux réseaux, ce qui facilite le débogage et la résolution des problèmes.
- Sécurité renforcée : ils permettent de mettre en place des politiques de sécurité avancées, comme l'authentification et l'autorisation mutuelle.
- Résilience : ils peuvent améliorer la résilience du cluster en cas de défaillance d'un service.
Inconvénients des services Mesh
- Complexité : ils peuvent être complexes à mettre en place et à gérer.
- Performances : ils peuvent avoir un impact sur les performances du cluster.
- Coût : si les solutions de service Mesh Open Source sont gratuites, les solutions commerciales peuvent être coûteuses.
Network Policies
Les Network Policies sont un objet natif de Kubernetes qui permet de contrôler les flux réseaux entre les pods.
Avantages des Network Policies
- Simplicité : elles sont relativement simples à mettre en place et à gérer.
- Performances : elles n'ont que peu d'impact sur les performances du cluster.
- Coût : elles sont gratuites, car il s'agit d'une fonctionnalité native de Kubernetes.
Inconvénients des Network Policies
- Contrôle moins granulaire : elles ne permettent pas un contrôle aussi précis des flux que les services Mesh.
- Observabilité : elles n'offrent pas la même visibilité sur les flux réseaux que les services Mesh.
- Sécurité : elles ne permettent pas de mettre en place des politiques de sécurité aussi avancées que les services Mesh.
Solution retenue
Après avoir étudié les différentes solutions, nous avons décidé d'utiliser les Network Policies pour la ségrégation des flux réseaux au sein de notre cluster Kubernetes.
Les raisons de notre choix:
- Simplicité : nous avons privilégié une solution simple à mettre en place et à gérer.
- Performances : nous ne voulions pas d’impact sur les performances de notre cluster.
- Coût : nous ne voulions pas investir dans une solution commerciale.
Implémentation écartée
Dans un premier temps, nous avons voulu utiliser les Network Policies en mode paranoïaque : c'est-à-dire refuser tout trafic entrant et sortant d’un namespace donné, puis autoriser au cas par cas les flux en fonction des besoins.
Cette approche, bien que beaucoup plus sécurisée, avait deux défauts majeurs :
- Maintenabilité : il faut définir tous les flux entrants et sortants nécessaires à chaque pod d’un namespace. À chaque ajout d’un nouveau pod ou modification d’un pod existant, des adaptations peuvent être nécessaires.
- Complexité : pour répondre au besoin initial d’isolation de flux entre certains namespaces, ce mode paranoïaque n’est pas nécessaire. Une solution bien plus simple peut être mise en place.
Implémentation retenue
In fine, il a été décidé de laisser le trafic sortant des pods de chaque namespace libre d’aller où bon lui semble (ce qui était déjà le comportement avant mise en place des Network Policies). Seul le trafic entrant doit être régulé. En effet, si on souhaite ne pas autoriser de trafic entre un namespace A et un namespace B, on peut le laisser quitter A ; il suffit de ne pas le laisser entrer dans B.
Ainsi, une liste de namespaces autorisés à communiquer les uns avec les autres a été dressée. Les règles de network policies on pu être appliquées en se basant soit sur un système de labels apposés sur les namespaces soit sur un nom de namespace en particulier.
Voici un exemple :
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-ingress-namespace
namespace: monitoring-test01
spec:
podSelector: {}
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
client: test01
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: ingress
Dans cet exemple, le trafic du namespace monitoring-test01 est autorisé pour tous les pods provenant des namespaces :
- dont le nom est ingress (pour autoriser l’ingress controller a envoyer du trafic) ;
- qui porte le label client=test01 .
Industrialisation
La validation d’un POC nous a amené à considérer une industrialisation de cette solution. En effet, le déploiement à la main des Network Policies serait une tâche fastidieuse et répétitive. Il est donc indispensable d’automatiser cette tâche.
SubChart
Afin de simplifier la labellisation du namespace et la mise en place de la Network Policy, nous avons mis en place un chart Helm dédié que nous avons publié sur Docker hub. Ce dernier se compose :
- de la network policy a déployer ;
- d’un hook de post install/post update pour labelliser le namespace de la release.
Au niveau des values du chart, on notera :
- un ensemble de labels a appliquer sur le namespace où le chart sera déployé ;
- un tableau de labels qui définit les namespaces depuis lesquels le trafic sera autorisé.
Dans notre cas d’utilisation, les namespaces à protéger contenaient déjà un chart Helm. Afin d’appliquer la solution de manière automatisée, nous l’avons intégrée en temps que dépendance (suivant un pattern d’umbrella chart).
Kyverno
Dans un second temps, la mise en place de l’industrialisation se fait via l’utilisation du gestionnaire de policies Kyverno. Ce dernier permet de définir des “cluster policies” qui s’occuperont de labelliser les namespaces et générer les network policies.
Labellisation des namespaces
Le regroupement des namespaces pouvant communiquer entre eux se fait par l’application d’un label commun sur ces derniers. Avec Kyverno, nous pouvons utiliser une cluster policy de type mutation pour ajouter ce labels sur nos namespaces. Dans notre cas, les labels à regrouper sont tous suffixés par l’identifiant du client (test02 dans notre exemple). La policy suivante ajoute le label client=test02 sur les namespaces concernés :
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: add-client-label-namespaces
annotations:
policies.kyverno.io/title: Add client label to namespaces
policies.kyverno.io/category: Client isolation
policies.kyverno.io/severity: medium
policies.kyverno.io/subject: Namespace
kyverno.io/kyverno-version: 1.8.0
policies.kyverno.io/minversion: 1.7.0
kyverno.io/kubernetes-version: "1.24"
policies.kyverno.io/description: >-
Add a client label on client namespaces
spec:
mutateExistingOnPolicyUpdate: true
rules:
- name: label-client-namespaces
match:
any:
- resources:
kinds:
- Namespace
mutate:
targets:
- apiVersion: v1
kind: Namespace
patchStrategicMerge:
metadata:
<(name): "*-test02"
labels:
contract: test02
Ainsi, tout namespace existant ou futur étant suffixé par le nom du client se verra appliquer le label désiré.
Création des network policies
Une fois nos namespaces labellisés, il devient possible d’appliquer les Network Policies (car elles se basent sur une sélection de namespaces par labels).
Pour cela, nous allons utiliser une cluster policy de type génération. Dans l’exemple ci-dessous, nous nous occuperons encore de notre client test02 :
---
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: generate-client-namespace-network-policy
annotations:
policies.kyverno.io/title: Add client Network Policy
policies.kyverno.io/category: Client isolation
policies.kyverno.io/severity: medium
policies.kyverno.io/subject: NetworkPolicy
kyverno.io/kyverno-version: 1.8.0
policies.kyverno.io/minversion: 1.7.0
kyverno.io/kubernetes-version: "1.24"
policies.kyverno.io/description: >-
Network Policies are used to control the traffic flow between Pods in a cluster.
This policy allow communication between client namespaces.
spec:
generateExisting: true
rules:
- name: generate-client-namespace-network-policy
match:
any:
- resources:
kinds:
- Namespace
selector:
matchLabels:
client: test02
generate:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
name: allow-ingress-namespace
namespace: ""
synchronize: true
data:
spec:
podSelector: { }
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
client: ""
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: ingress
...
Cette cluster policy génèrera dans tous les namespaces ayant le label client=test02 une network policy qui autorise le trafic depuis les namespaces labellisés client=test02 et depuis le namespace qui porte le nom “ingress”.
Conclusion
Les Network Policies sont une solution native, simple et efficace pour la ségrégation des flux réseaux au sein d'un cluster Kubernetes. Elles sont une bonne alternative aux services Mesh pour les environnements où la simplicité, les performances et le coût sont des facteurs importants.
Cependant, il est important de connaître les limitations des Network Policies et de mettre en place des mesures pour les compenser.
- Contrôle moins granulaire : nous sommes conscients que nous n'avons pas un contrôle aussi précis des flux que nous le pourrions avec un service Mesh.
- Observabilité : nous sommes conscients que nous n'avons pas la même visibilité sur les flux réseaux que nous le pourrions avec un service Mesh.
- Sécurité : nous sommes conscients que nous ne pouvons pas mettre en place des politiques de sécurité aussi avancées que nous le pourrions avec un service Mesh.
- Scalabilité : l’utilisation de network policies a grande échelle peut nécessiter de s’appuyer sur d’autres outils (comme Kyverno) ce qui peut rajouter une couche de complexité.
Pour pallier ces limitations, il est possible de mettre en place des mécanismes de surveillance afin de détecter les anomalies ainsi que des politiques de sécurité pour limiter les risques.