Comme Kubernetes est devenu la plate-forme de facto pour orchestrer les charges de travail conteneurisées, de plus en plus d’utilisateurs ont commencé à chercher des moyens de contrôler et de sécuriser leurs clusters Kubernetes.
Le durcissement est une chose sûre, mais qu’en est-il application des politiques à l’intérieur d’un cluster ? Il s’agit d’une tâche complètement différente qui nécessite un ensemble d’outils différent.
Comme vous l’avez déjà deviné, une façon appropriée de le faire serait de définir politiques en tant que code, et un bon outil pour cela est « Open Policy Agent » ou OPA. Si vous ne savez pas de quoi je parle, veuillez d’abord prendre le temps de lire cette introduction : « Qu’est-ce que Policy-as-Code ? Une introduction à Open Policy Agent.
Pourquoi ne pas simplement utiliser RBAC ?
Pour mieux comprendre pourquoi nous devons utiliser un nouvel outil pour les politiques, prenons un exemple concret : imaginez que vous êtes un administrateur de cluster et que vous souhaitez restreindre ce qui peut s’exécuter dans votre cluster.
À première vue, cela ressemble à un cas d’utilisation parfaitement valide pour le contrôle d’accès basé sur les rôles (RBAC, un système d’autorisation pour créer et gérer des objets Kubernetes au niveau des ressources) : avec RBAC, vous pouvez facilement autoriser des cas tels que « l’utilisateur X peut faire Y dans l’espace de noms Z. »
Vous commencez par définir un Rôle dans le default
espace de noms pour accorder un accès en lecture aux pods :
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: default
name: pod-reader
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "watch", "list"]
Maintenant, un peu plus délicat : comment feriez-vous si vous vouliez restreindre, non pas l’accès aux ressources comme les pods, mais comment ils sont configurés?
C’est là que s’arrêtent les pouvoirs du RBAC : les contrôles d’accès ne peuvent pas aller au-delà et restreindre les configurations, les paramètres et le contenu des objets de Kubernetes. Pourtant, en tant qu’administrateur de cluster, vous pouvez absolument le souhaiter.
Pourquoi ne pas écrire mon propre contrôleur d’admission ?
Pour les besoins de l’exercice, nous allons imaginer que vous devez exiger que tous les objets de ressource de votre cluster avoir une étiquette spécifique.
Comme vous pouvez le voir, ceci n’est pas lié à un rôle ou à un groupe, en fait, il est lié à un champ très spécifique pour toutes les ressources de votre cluster.
Supposons que vous ayez besoin de contrôler les champs de pod (ou tous les champs d’autres types de ressources, d’ailleurs). Dans ce cas, vous avez une option : créer votre propre contrôleur d’admission validant. Un contrôleur d’admission est un morceau de code qui intercepte les requêtes adressées au serveur d’API Kubernetes avant la persistance de l’objet.
Dans le détail, cela fonctionnerait comme suit : une demande de création d’un nouveau pod est effectuée auprès du service d’API du cluster ; cela déclencherait une coutume ValidatingAdmissionWebhook
correspondant à cette demande ; le contrôleur appellerait un webhook de validation ; si le contrôleur rejette la demande, le service API la rejettera également.
Le problème avec cela est qu’il n’est pas évolutif : vous devrez écrire autant de contrôleurs d’admission personnalisés que de règles ou de politiques que vous souhaitez appliquer.
C’est là qu’intervient OPA, et nous verrons ensemble comment le mettre en place.
Ouvrir l’agent de stratégie dans Kubernetes
Pour résoudre le défi ci-dessus, ce dont nous avons vraiment besoin ici, c’est d’un système qui prend en charge plusieurs configurations couvrant différents types et champs de ressources et permet la réutilisation. Open Policy Agent (OPA) fournit exactement cela.
En un mot, le moteur de politique OPA évalue les demandes pour déterminer si elles sont conformes aux politiques configurées.
OPA peut facilement s’intégrer à Kubernetes : il attend une entrée JSON, est facile à conteneuriser et prend en charge la configuration dynamique, ce qui le rend bien adapté pour fournir une évaluation de politique pour le service d’API Kubernetes.
Plongeons-nous et montrons comment déployer et intégrer OPA avec Kubernetes.
Comment utiliser OPA avec Kubernetes
Dans cet exemple, nous montrerons comment intégrer OPA à Kubernetes en déployant une stratégie qui garantit que le nom d’hôte Ingress doit figurer sur la liste d’autorisation de l’espace de noms contenant Ingress.
C’est-à-dire que nous voulons refuser toutes les créations d’objets Ingress dont le nom d’hôte ne correspond pas à la liste d’autorisation.
Avant de commencer, téléchargez OPA si vous ne l’avez pas encore fait.
Préparer le cluster Kubernetes
Ce didacticiel nécessite Kubernetes 1.20 ou une version ultérieure. Pour exécuter le didacticiel localement, démarrez un cluster avec Kubernetes version 1.20+. minikube
est recommandé.
Si vous utilisez Kubernetes dans un service cloud, par exemple Amazon EKS, les contrôleurs d’admission dynamiques sont probablement déjà activés par défaut, ce qui vous permet de déployer des webhooks personnalisés. Sinon, vous devez activer le ValidatingAdmissionWebhook
lorsque le serveur d’API Kubernetes est démarré. Le ValidatingAdmissionWebhook
contrôleur d’admission est inclus dans l’ensemble recommandé de contrôleurs d’admission à activer.
Commençons minikube
activez le minikube
Module complémentaire Ingress, créez un espace de noms (pour déployer OPA) et configurez le contexte Kubernetes :
minikube start
minikube addons enable ingress
kubectl create namespace opa
kubectl config set-context opa-tutorial --user minikube --cluster minikube --namespace opa
kubectl config use-context opa-tutorial
La communication entre Kubernetes et OPA doit être sécurisée à l’aide de TLS. utilisons openssl
pour faire juste ça :
openssl genrsa -out ca.key 2048
openssl req -x509 -new -nodes -sha256 -key ca.key -days 100000 -out ca.crt -subj "/CN=admission_ca"
# generate the TLS key and certificate for OPA:
cat >server.conf <<EOF
[ req ]
prompt = no
req_extensions = v3_ext
distinguished_name = dn
[ dn ]
CN = opa.opa.svc
[ v3_ext ]
basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
extendedKeyUsage = clientAuth, serverAuth
subjectAltName = DNS:opa.opa.svc,DNS:opa.opa.svc.cluster,DNS:opa.opa.svc.cluster.local
EOF
openssl genrsa -out server.key 2048
openssl req -new -key server.key -sha256 -out server.csr -extensions v3_ext -config server.conf
openssl x509 -req -in server.csr -sha256 -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -days 100000 -extensions v3_ext -extfile server.conf
# create a secret to store the TLS credentials for OPA
kubectl create secret tls opa-server --cert=server.crt --key=server.key --namespace opa
Définir la politique avec Rego
Maintenant que le cluster Kubernetes est prêt, écrivons quelques stratégies. Commencez par créer un nouveau dossier pour les stocker :
mkdir policies && cd policies
Nous voulons une politique qui limite les noms d’hôte qu’un Ingress peut utiliser. Seuls les noms d’hôte correspondant aux expressions régulières spécifiées seront autorisés. Créer un fichier ingress-allowlist.rego
avec le contenu suivant :
package kubernetes.admission
import data.kubernetes.namespaces
operations := {"CREATE", "UPDATE"}
deny[msg] {
input.request.kind.kind == "Ingress"
operations[input.request.operation]
host := input.request.object.spec.rules[_].host
not fqdn_matches_any(host, valid_ingress_hosts)
msg := sprintf("invalid ingress host %q", [host])
}
valid_ingress_hosts := {host |
allowlist := namespaces[input.request.namespace].metadata.annotations["ingress-allowlist"]
hosts := split(allowlist, ",")
host := hosts[_]
}
fqdn_matches_any(str, patterns) {
fqdn_matches(str, patterns[_])
}
fqdn_matches(str, pattern) {
pattern_parts := split(pattern, ".")
pattern_parts[0] == "*"
suffix := trim(pattern, "*.")
endswith(str, suffix)
}
fqdn_matches(str, pattern) {
not contains(pattern, "*")
str == pattern
}
Si vous ne savez pas encore grand-chose sur le langage de politique Rego, lisez la documentation officielle.
Essentiellement, ce morceau de code fait ce qui suit :
- Le
valid_ingress_hosts
La fonction obtient l’annotation « ingress-allowlist » dans la section des métadonnées d’un espace de noms. - Ensuite, il essaie de faire correspondre le nom d’hôte à celui-ci à l’aide d’une expression régulière.
- S’il n’y a pas de correspondance, la demande sera refusée avec un message.
Ensuite, nous définirons la politique principale qui importera la politique de restriction de nom d’hôte ci-dessus et répondrons par une décision politique globale.
Note: dans cet exemple, puisque nous n’utilisons qu’une seule politique, cette politique principale est un peu redondante. Pourtant, dans des situations réelles où vous souhaitez appliquer plusieurs politiques, la politique principale est nécessaire pour prendre une décision globale à la fin.
Créer un fichier main.rego
avec le contenu suivant :
package system
import data.kubernetes.admission
main := {
"apiVersion": "admission.k8s.io/v1",
"kind": "AdmissionReview",
"response": response,
}
default uid := ""
uid := input.request.uid
response := {
"allowed": false,
"uid": uid,
"status": {
"message": reason,
},
} {
reason = concat(", ", admission.deny)
reason != ""
}
else := {"allowed": true, "uid": uid}
Construire et publier le bundle OPA
Avec le code de politique Rego prêt, il est temps de le construire. Exécutez les commandes suivantes dans le dossier des stratégies pour créer et publier :
cat > .manifest <<EOF
{
"roots": ["kubernetes/admission", "system"]
}
EOF
opa build -b .
# serve the OPA bundle using Nginx:
docker run --rm --name bundle-server -d -p 8888:80 -v ${PWD}:/usr/share/nginx/html:ro nginx:latest
Ici, nous allons créer un « bundle » à partir du code Rego, puis nous servons le bundle dans un serveur Nginx dans un conteneur Docker, localement, qui sera intégré à OPA à l’étape suivante.
Installer OPA en tant que contrôleur d’admission
Tout d’abord, déployons OPA :
kubectl apply -f https://gist.githubusercontent.com/IronCore864/035f7feca2c89ffd2809ec604fb3b873/raw/3e85364f72970b82f418544fd009fd478bc655ae/admission-controller.yaml
Voyons une partie de cela admission-controller.yaml
déposer:
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: opa
namespace: opa
name: opa
# ...
- name: opa
image: openpolicyagent/opa:0.47.3-rootless
# ...
- name: kube-mgmt
image: openpolicyagent/kube-mgmt:2.0.1
args:
- "--replicate-cluster=v1/namespaces"
- "--replicate=networking.k8s.io/v1/ingresses"
Il existe un conteneur nommé kube-mgmt
qui s’exécute en tant que side-car du conteneur OPA. kube-mgmt
gère les politiques/données des instances OPA dans Kubernetes en…