Comment protéger Kubernetes avec OPA
Introduction
La mise en place d’un cluster Kubernetes dans votre organisation est une réalisation importante, mais elle pose également des défis en matière de surveillance, d’audit et de sécurisation du cluster.
Lorsqu’il s’agit de sécuriser le cluster, les ingénieurs de la plate-forme ou les ingénieurs en sécurité doivent suivre ce qui est déployé, la manière dont il est déployé et le niveau de vulnérabilité que l’application peut introduire dans la plate-forme. Dans le cas d’un cluster Kubernetes de grande entreprise, il devient essentiel d’appliquer des politiques d’étiquetage pour toutes les applications déployées. C’est là que l’Open Policy Agent Gatekeeper s’avère précieux.
Terminologies de l’OPA
Webhooks du contrôleur d’admission : il vérifie les demandes d’admission avant qu’elles ne soient conservées en tant qu’objets dans Kubernetes.
Open Policy Agent (OPA) : moteur de politiques pour les environnements cloud natifs. Il s’agit d’un cadre pour l’application des décisions politiques
-
Gatekeeper : contrôleur d’admission qui vérifie les demandes de création ou de mise à jour de ressources sur le cluster Kubernetes en appliquant des politiques exécutées par OPA.
Contrainte : Une déclaration selon laquelle son auteur souhaite qu’un système réponde à un ensemble donné d’exigences. C’est écrit en Rego. Il est évalué comme un ET logique ; Par conséquent, si une contrainte n’est pas satisfaite, l’ensemble de la demande est rejeté.
ConstraintTemplate : définit un moyen de valider un ensemble d’objets Kubernetes dans le contrôleur d’admission Kubernetes de Gatekeeper. Il comprend :
- Rego Code, qui définit la violation de la politique
- Constraint, qui représente l’instanciation de ConstraintTemplate
Comment fonctionne l’OPA
Open Policy Agent (OPA) est un moteur de politiques qui applique des stratégies pour l’infrastructure cloud à l’aide d’un langage déclaratif.
Principales caractéristiques d’OPA :
- Rédaction de politiques : OPA utilise son propre langage, Rego, pour rédiger des politiques. Rego est conçu pour inspecter et transformer des données structurées, telles que JSON et YAML.
- Décisions politiques : lorsque le logiciel doit prendre des décisions stratégiques, il interroge OPA et fournit des données structurées en entrée. OPA évalue l’entrée de la requête par rapport aux politiques et aux données pour générer des décisions de politique.
- Décharger les décisions politiques : OPA décharge les décisions politiques des services, ce qui leur permet de se concentrer sur d’autres tâches. Par exemple, OPA peut répondre à des questions telles que l’autorisation d’un appel d’API, le quota d’un utilisateur ou les hôtes sur lesquels un conteneur peut être déployé.
- Mises à jour dynamiques des politiques : OPA peut télécharger des ensembles de politiques et de données à partir de serveurs HTTP distants. Ces bundles sont chargés à la volée sans redémarrer OPA.
- Chaînage de règles : OPA permet le chaînage de règles, ce qui signifie qu’une variable de sortie peut être utilisée pour former d’autres variables de sortie.
Conditions préalables
- Un compte Cloud DigitalOcean pour accéder à la plateforme DigitalOcean.
- Un jeton d’accès personnel DigitalOcean pour l’utilisation de l’API DigitalOcean.
- Utilitaire Doctl pour gérer les ressources DigitalOcean à l’aide de l’interface en ligne de commande.
- Kubectl pour l’interaction du cluster Kubernetes.
Configurer OPA dans le cluster Kubernetes
Configuration du cluster DOKS
Pour déployer un cluster Kubernetes sur DigitalOcean, reportez-vous à Comment configurer un cluster k8s sur DigitalOcean ou utilisez la commande doctl
suivante directement à partir de votre interface de ligne de commande :
doctl kubernetes cluster create k8s-opa-cluster --region <region> --version 1.31.1-do.5 --node-pool "name=<name>;size=s-2vcpu-4gb;count=3"
Intégration d’OPA dans le cluster Kubernetes
Dans les cas où le maillage de services n’est pas utilisé, le moyen le plus simple d’intégrer OPA dans le cluster kubernetes est d’utiliser Gatekeeper. Pour ce faire, vous devez installer les CRD nécessaires pour le contrôleur d’accès. Cela peut se faire via des graphiques yaml
ou helm. Par souci de simplicité, nous allons le faire via yaml
.
kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper/v3.17.1/deploy/gatekeeper.yaml
Mais si nous voulons le faire via Helm, vous pouvez le faire comme ci-dessous :
helm repo add gatekeeper https://open-policy-agent.github.io/gatekeeper/charts
helm repo update
Helm install with gatekeeper-system namespace already created
$ helm install -n gatekeeper-system [RELEASE_NAME] gatekeeper/gatekeeper
Helm install and create namespace
$ helm install -n gatekeeper-system [RELEASE_NAME] gatekeeper/gatekeeper --create-namespace
Vous pouvez vous référer à la documentation GitHub sur l’installation de Gatekeeper Helm Chart
Cela se traduira par le déploiement des composants suivants :
Exemple de cas d’utilisation
Dans cet exemple, nous allons traiter d’un problème très courant auquel les ingénieurs de plateforme sont confrontés dans le cluster Kubernetes qu’ils gèrent. En d’autres termes, le calcul d’un nœud de travail est surutilisé lorsque les locataires ne définissent pas la plage de CPU et de mémoire que la charge de travail utilisera.
Pour ce faire, vous allez configurer une contrainte nommée : K8sRequiredResources
Sans contraintes
Vous trouverez ci-dessous l’exemple de déploiement que nous déployons sur le cluster Kubernetes, sans définir de contraintes de ressources
apiVersion: apps/v1
kind: Deployment
metadata:
name: unrestricted-deployment
spec:
replicas: 1
selector:
matchLabels:
app: unrestricted-deployment
template:
metadata:
labels:
app: unrestricted-deployment
spec:
containers:
- name: deployment-a
image: nginx:1.14.2
ports:
- containerPort: 80
Créer un ConstraintTemplate
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8srequiredresources
annotations:
description: >-
Requires workloads to specify resource limits and requests.
spec:
crd:
spec:
names:
kind: K8sRequiredResources
validation:
openAPIV3Schema:
type: object
properties:
resourceRequestRequired:
type: boolean
description: If true, resource requests should be specified.
resourceLimitRequired:
type: boolean
description: If true, resource limits should be specified.
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8srequiredresources
violation[{"msg": msg}] {
kind := input.review.kind.kind
workload_name := input.review.object.metadata.name
workload_namespace := input.review.object.metadata.namespace
ctr := input.review.object.spec.template.spec.containers[_]
input.parameters.resourceRequestRequired
not ctr.resources.requests
msg := sprintf("%v <%v> in <%v> namespace contains container <%v> with no resource requests specified", [kind, workload_name, workload_namespace, ctr.name])
}
violation[{"msg": msg}] {
kind := input.review.kind.kind
workload_name := input.review.object.metadata.name
workload_namespace := input.review.object.metadata.namespace
ctr := input.review.object.spec.template.spec.containers[_]
input.parameters.resourceLimitRequired
not ctr.resources.limits
msg := sprintf("%v <%v> in <%v> namespace contains container <%v> with no resource limits specified", [kind, workload_name, workload_namespace, ctr.name])
}
Une fois appliqué, vous pourrez interroger l’objet Kubernetes nommé k8srequiredresources
Créer une contrainte
Dans Contrainte, vous pouvez définir l’étendue des restrictions à imposer. Cela signifie à quel objet kubernetes cette contrainte sera appliquée et l’espace de noms à appliquer ainsi que leurs exceptions respectives. Vous trouverez ci-dessous un exemple de contrainte réelle que vous pouvez définir pour votre cas d’utilisation :
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredResources
metadata:
name: workload-must-have-resources-set
spec:
match:
kinds:
- apiGroups: ["apps"]
kinds: ["Deployment", "StatefulSet", "DaemonSet", "Pod"]
parameters:
resourceRequestRequired: true
resourceLimitRequired: true
Maintenant, si nous essayons d’appliquer le déploiement similaire après la suppression du précédent, l’erreur s’affichera avec l’augmentation du nombre de violations.
Contraintes post-configuration : le yaml de déploiement doit avoir des spécifications de ressources et de limites pour être déployé sur le cluster Kubernetes maintenant.
apiVersion: apps/v1
kind: Deployment
metadata:
name: unrestricted-deployment
spec:
replicas: 1
selector:
matchLabels:
app: unrestricted-deployment
template:
metadata:
labels:
app: unrestricted-deployment
spec:
containers:
- name: deployment-a
image: nginx:1.14.2
ports:
- containerPort: 80
resources:
requests:
memory: "64Mi"
cpu: "250m"
limits:
memory: "128Mi"
cpu: "500m"
Nettoyage
Vous pouvez supprimer le cluster k8s à l’aide de la commande suivante :
doctl kubernetes cluster delete k8s-opa-cluster --dangerous
Conclusion
Dans ce tutoriel, vous avez appris à implémenter des mesures de protection essentielles sur votre cluster Kubernetes pour éviter les utilisations abusives ou les vulnérabilités qui peuvent résulter de charges de travail déployées par les locataires. En suivant les étapes décrites, vous pouvez vous assurer que votre cluster est mieux protégé contre les menaces de sécurité potentielles et l’utilisation abusive des ressources. Ceci est particulièrement important dans les environnements multilocataires où plusieurs utilisateurs ou équipes partagent le même cluster, car cela permet de maintenir l’intégrité et la fiabilité de la plateforme.