Écrire un modèle de contrainte

Cette rubrique explique comment écrire un modèle de contrainte personnalisé et comment l'utiliser pour étendre Policy Controller.

Aperçu

Les modèles de contrainte peuvent être utilisés pour étendre Policy Controller. Dans le cas où vous ne pouvez pas trouver un modèle pré-écrit qui convient à vos besoins, il est possible d'écrire le vôtre.

Les règles de Policy Controller sont décrites à l'aide de l'OPA Constraint Framework et écrites dans Rego. Une règle peut évaluer n'importe quel champ d'un objet Kubernetes.

Écrire des règles à l'aide de Rego est une compétence spécialisée. Pour cette raison, une bibliothèque de modèles de contraintes courants est installée par défaut. La plupart des utilisateurs peuvent invoquer ces modèles de contrainte lors de la création de contraintes. Si vous avez des besoins spécifiques, vous pouvez créer vos propres modèles de contrainte.

Les modèles de contrainte vous permettent de séparer la logique d'une règle de ses exigences spécifiques, à fin de réutilisation et de délégation. Vous pouvez créer des contraintes à l'aide de modèles de contraintes développés par des tiers, tels que des projets Open Source, des éditeurs de logiciels ou des experts en réglementation.

Avant de commencer

Exemple de modèle de contrainte

Vous trouverez ci-dessous un exemple de modèle de contrainte qui refuse toutes les ressources dont le nom correspond à une valeur fournie par le créateur de la contrainte. Le reste de cette page abordera le contenu du modèle, en soulignant les concepts importants en cours de route.

Modèle de contrainte

apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
  name: k8sdenyname
spec:
  crd:
    spec:
      names:
        kind: K8sDenyName
      validation:
        # Schema for the `parameters` field
        openAPIV3Schema:
          properties:
            invalidName:
              type: string
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package k8sdenynames
        violation[{"msg": msg}] {
          input.review.object.metadata.name == input.parameters.invalidName
          msg := sprintf("The name %v is not allowed", [input.parameters.invalidName])
        }

Contrainte

Voici un exemple de contrainte qu'un utilisateur peut implémenter pour refuser toutes les ressources nommées "policy-violation" (non-respect des règles) :

apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sDenyName
metadata:
  name: no-policy-violation
spec:
  parameters:
    invalidName: "policy-violation"

Composantes d'un modèle de contrainte

Les modèles de contrainte comportent deux éléments importants :

  • Le schéma de la contrainte que vous souhaitez que les utilisateurs créent. Le schéma d'un modèle de contrainte est stocké dans le champ crd.

  • Le code source Rego qui est exécuté lorsque la contrainte est évaluée. Le code source Rego d'un modèle est stocké dans le champ targets.

Le champ CRD

Le champ CRD est un modèle de création de la définition de ressource personnalisée Kubernetes qui définit la ressource de contrainte pour le serveur d'API Kubernetes. Il vous suffit de remplir les champs suivants :

  • spec.crd.spec.names.kind est le genre de la contrainte. Lorsqu'il est en minuscule, la valeur de ce champ doit être égale à metadata.name.
  • spec.crd.spec.validation.openAPIV3Schema est le schéma pour le champ spec.parameters de la ressource de la contrainte (le reste du schéma de la contrainte est défini automatiquement par Anthos Config Management). Il suit les mêmes conventions que dans une ressource CRD standard. Sa définition est documentée ici.

Préfixer le modèle de contrainte avec le nom "K8s" est une convention qui nous permet d'éviter les collisions avec d'autres types de modèles de contrainte, tels que les modèles Forseti ciblant les ressources GCP.

Le code source Rego

Zone

Le code source Rego est stocké sous le champ spec.targets, où targets est un tableau d'objets au format {"target": "admission.k8s.gatekeeper.sh", "rego": <REGO SOURCE CODE>, "libs": <LIST OF REGO LIBRARIES>}. Actuellement, une seule entrée dans targets est autorisée.

  • target indique à Anthos Config Management quel système nous examinons (dans le cas présent, Kubernetes)
  • rego est le code source de la contrainte
  • libs est une liste facultative de bibliothèques de code Rego qui seront mises à la disposition du modèle de contrainte. Il est destiné à faciliter l'utilisation des bibliothèques partagées et dépasse le cadre de ce tutoriel.

Code source

Examinons le code source Rego pour la contrainte ci-dessus :

package k8sdenynames

violation[{"msg": msg}] {
   input.review.object.metadata.name == input.parameters.invalidName
   msg := sprintf("The name %v is not allowed", [input.parameters.invalidName])
}

Quelques éléments sont à noter :

  • package k8sdenynames est requis par OPA (environnement d'exécution de Rego). La valeur est ignorée.
  • La règle Rego que Policy Controller invoque pour voir s'il y a des violations est nommée violation. Si cette règle a des correspondances, une violation de la contrainte s'est produite.
  • La règle violation possède la signature violation[{"msg": "violation message for the user"}], où la valeur de "msg" est le message de violation qui sera renvoyé à l'utilisateur.
  • Les paramètres fournis à la contrainte sont mis à disposition sous le mot clé input.parameters.
  • La requête en cours de test est stockée sous le mot clé input.review

input.review comprend les champs suivants :

  • uid est l'ID unique de cette requête spécifique, non disponible pendant l'audit
  • kind est l'information de genre pour l'objet en cours de test. Son format est le suivant :
    • kind le genre de la ressource
    • group le groupe de la ressource
    • version la version de la ressource
  • name est le nom de la ressource. Il peut être vide si l'utilisateur s'appuie sur le serveur d'API pour générer le nom sur une requête CREATE.
  • namespace est l'espace de noms de la ressource (non fourni pour les ressources à l'échelle d'un cluster)
  • operation est l'opération demandée (par exemple CREATE ou UPDATE), non disponible pendant l'audit.
  • userInfo correspond aux informations de l'utilisateur demandeur, non disponibles pendant l'audit
    • username est l'utilisateur effectuant la requête
    • uid est l'UID de l'utilisateur
    • groups est une liste de groupes dont l'utilisateur est membre
    • extra est toute information utilisateur supplémentaire fournie par Kubernetes
  • object est l'objet que l'utilisateur tente de modifier/créer
  • oldObject est l'état d'origine de l'objet, uniquement disponible sur les opérations UPDATE
  • dryRun indique si cette demande a été invoquée ou non avec kubectl --dry-run, non disponible pendant l'audit

Écrire des modèles de contraintes référentielles

Les modèles de contraintes référentielles sont des modèles qui permettent à l'utilisateur de contraindre un objet par rapport à d'autres objets : Par exemple, "ne pas autoriser la création d'un pod avant qu'une entrée correspondante ne soit connue", ou "ne pas autoriser deux services à avoir le même nom d'hôte".

Policy Controller vous permet d'écrire des contraintes référentielles en surveillant le serveur d'API vis-à-vis d'un ensemble de ressources fournies par l'utilisateur. Lorsqu'une ressource est modifiée, Policy Controller la met en cache localement afin qu'elle puisse être facilement référencée par le code source Rego. Policy Controller rend ce cache disponible sous le mot clé data.inventory.

Les ressources à l'échelle d'un cluster sont mises en cache à l'emplacement suivant :

data.inventory.cluster[<groupVersion>][<kind>][<name>]

Par exemple, un nœud nommé my-favorite-node peut se trouver sous :

data.inventory.cluster["v1"]["Node"]["my-favorite-node"]

Les ressources étendues à l'espace de noms sont mises en cache ici :

data.inventory.namespace[<namespace>][<groupVersion>][<kind>][<name>]

Par exemple, un ConfigMap nommé production-variables dans l'espace de noms shipping-prod peut se trouver sous  :

data.inventory.namespace["shipping-prod"]["v1"]["ConfigMap"]["production-variables"]

Le contenu complet de l'objet est stocké à cet emplacement de cache et peut être référencé dans votre Rego comme bon vous semble.

Plus d'informations sur Rego

Les informations ci-dessus fournissent les fonctionnalités uniques de Policy Controller qui facilitent l'écriture de contraintes sur les ressources Kubernetes dans Rego. Un tutoriel complet sur la façon d'écrire dans Rego est hors du champ d'application de ce guide. Toutefois, le site Web d'Open Policy Agent contient une documentation sur la syntaxe et les fonctionnalités du langage Rego lui-même.

Installer votre modèle de contrainte

Une fois que vous avez créé votre modèle de contrainte, il vous suffit de l'appliquer (kubectl apply) et Policy Controller se chargera de l'ingérer. Veillez à vérifier le champ status de votre modèle de contrainte pour vous assurer qu'il n'y a pas eu d'erreur lors de son instanciation. Si l'ingestion réussie, le champ status doit afficher created: true et le résultat de observedGeneration noté dans le champ status doit être égal au champ metadata.generation.

Une fois le modèle ingéré, vous pouvez lui appliquer des contraintes comme décrit dans la section Créer des contraintes.

Supprimer un modèle de contrainte

Vérifiez au préalable qu'aucune contrainte que vous souhaitez conserver n'utilise le modèle de contrainte :

kubectl get [TEMPLATE-NAME]

En cas de conflit de dénomination entre le nom du modèle de contrainte et un autre objet du cluster, vous pouvez utiliser la commande suivante à la place :

kubectl get [TEMPLATE-NAME].constraints.gatekeeper.sh

Supprimez le modèle de contrainte :

kubectl delete constrainttemplate [CONSTRAINT-TEMPLATE-NAME]

Lorsque vous supprimez un modèle de contrainte, vous ne pouvez plus créer de contraintes qui le référencent.

Et ensuite ?