21. Conformité et gouvernance#
La gouvernance d’une infrastructure cloud native repose sur la capacité à exprimer, appliquer et auditer des règles de façon automatique et reproductible. Le principe de Policy as Code traduit cette exigence en pratique : les politiques de sécurité, de conformité et d’exploitation deviennent des artefacts versionnés, testables et déployables au même titre que le code applicatif.
Policy as Code : principe et outillage#
Pourquoi Policy as Code ?#
Les approches traditionnelles de gouvernance reposent sur des documents Word, des checklists manuelles et des revues périodiques. Elles souffrent de trois problèmes fondamentaux :
Dérive de configuration : les règles appliquées en production divergent progressivement des règles documentées
Délai de détection : une non-conformité n’est découverte qu’à l’occasion d’un audit, souvent plusieurs mois après son introduction
Absence de traçabilité : il est impossible de savoir qui a autorisé quelle exception et pourquoi
Policy as Code résout ces trois problèmes en déplaçant la définition des règles dans des fichiers de code, vérifiés automatiquement à chaque déploiement.
Les trois outils principaux#
OPA (Open Policy Agent) est un moteur de politique généraliste, développé par la CNCF. Il évalue des règles exprimées en langage Rego contre des données JSON arbitraires. OPA peut s’intégrer à Kubernetes (via Gatekeeper), Terraform, les API REST, et tout système capable d’émettre des requêtes JSON.
Kyverno est un moteur de politique nativement Kubernetes. Ses politiques sont exprimées en YAML, ce qui le rend plus accessible aux équipes opérationnelles. Il prend en charge la validation, la mutation et la génération de ressources.
Conftest est un outil en ligne de commande qui permet de tester des fichiers de configuration (YAML, JSON, HCL, Dockerfile) contre des règles OPA/Rego. Il s’intègre naturellement dans les pipelines CI.
OPA : Open Policy Agent#
Architecture#
OPA fonctionne comme un service indépendant (sidecar ou service centralisé) qui reçoit des requêtes d’autorisation sous forme de documents JSON. Son architecture est volontairement simple :
Un bundle contient les règles Rego et les données de référence
L”API REST expose un endpoint
/v1/data/{path}pour évaluer les règlesLa commande
opa evalpermet de tester localement une règle contre des données
Langage Rego#
Rego est un langage déclaratif inspiré de Datalog. Une règle Rego exprime une condition : si la condition est vraie, la règle est satisfaite.
# Règle : interdire les images sans tag explicite (pas de "latest")
package kubernetes.admission
import future.keywords.in
deny[msg] {
input.request.kind.kind == "Pod"
container := input.request.object.spec.containers[_]
endswith(container.image, ":latest")
msg := sprintf("Image %v utilise le tag 'latest' interdit", [container.image])
}
deny[msg] {
input.request.kind.kind == "Pod"
container := input.request.object.spec.containers[_]
not contains(container.image, ":")
msg := sprintf("Image %v n'a pas de tag explicite", [container.image])
}
Intégration Kubernetes : OPA Gatekeeper#
Gatekeeper est l’intégration officielle d’OPA dans Kubernetes. Il s’appuie sur les admission webhooks pour intercepter chaque requête à l’API server avant qu’elle ne soit persistée dans etcd.
Gatekeeper introduit deux Custom Resource Definitions :
ConstraintTemplate: définit la règle Rego et le schéma de paramétrageConstraint: instancie la règle avec des paramètres concrets (namespaces cibles, exceptions…)
# ConstraintTemplate : définit la règle Rego
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8srequiredlabels
spec:
crd:
spec:
names:
kind: K8sRequiredLabels
validation:
openAPIV3Schema:
type: object
properties:
labels:
type: array
items:
type: string
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8srequiredlabels
violation[{"msg": msg}] {
provided := {label | input.review.object.metadata.labels[label]}
required := {label | label := input.parameters.labels[_]}
missing := required - provided
count(missing) > 0
msg := sprintf("Labels manquants : %v", [missing])
}
---
# Constraint : instancie la règle sur les Namespaces
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredLabels
metadata:
name: require-team-label
spec:
match:
kinds:
- apiGroups: [""]
kinds: ["Namespace"]
parameters:
labels: ["team", "environment", "cost-center"]
Kyverno : politiques Kubernetes natives#
ClusterPolicy et ses trois modes#
Une ClusterPolicy Kyverno peut opérer en trois modes :
validate : rejette les ressources non conformes.
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: disallow-privileged-containers
spec:
validationFailureAction: Enforce
rules:
- name: check-privileged
match:
any:
- resources:
kinds: ["Pod"]
validate:
message: "Les conteneurs privilégiés sont interdits."
pattern:
spec:
containers:
- =(securityContext):
=(privileged): "false"
mutate : modifie automatiquement les ressources à la création.
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: add-default-resources
spec:
rules:
- name: add-resource-limits
match:
any:
- resources:
kinds: ["Pod"]
mutate:
patchStrategicMerge:
spec:
containers:
- (name): "?*"
resources:
limits:
memory: "512Mi"
cpu: "500m"
generate : crée automatiquement des ressources associées, par exemple une NetworkPolicy de refus par défaut dans chaque nouveau namespace.
Admission controllers Kubernetes#
Webhooks validating et mutating#
Le cycle de vie d’une requête à l’API Kubernetes suit un ordre strict :
Authentification : vérification de l’identité (certificat TLS, token OIDC, token de ServiceAccount)
Autorisation RBAC : vérification des permissions de l’identité sur la ressource demandée
Admission mutating : les webhooks de mutation modifient la ressource (l’ordre entre webhooks est non garanti)
Validation de schéma : vérification contre l’OpenAPI schema de la ressource
Admission validating : les webhooks de validation acceptent ou rejettent la ressource dans son état muté final
Persistance etcd : écriture de la ressource validée
L’ordre mutating → validating est fondamental : Kyverno et Gatekeeper valident la ressource après toutes les mutations, y compris les leurs.
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
name: policy-webhook
webhooks:
- name: validate.policies.example.com
admissionReviewVersions: ["v1"]
clientConfig:
service:
name: policy-service
namespace: policy-system
path: /validate
caBundle: <base64-ca-cert>
rules:
- apiGroups: [""]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["pods"]
failurePolicy: Fail # Rejeter si le webhook est indisponible
sideEffects: None
Audit logging Kubernetes#
L’audit logging Kubernetes enregistre chaque requête à l’API server. Il est configuré via un fichier de politique qui définit le niveau de verbosité par type de ressource et d’opération.
# /etc/kubernetes/audit-policy.yaml
apiVersion: audit.k8s.io/v1
kind: Policy
rules:
# Secrets : métadonnées uniquement (protéger les données sensibles)
- level: Metadata
resources:
- group: ""
resources: ["secrets", "configmaps"]
# Pods : enregistrer la requête complète
- level: Request
resources:
- group: ""
resources: ["pods"]
verbs: ["create", "update", "patch", "delete"]
# Accès anonymes : tout enregistrer
- level: RequestResponse
nonResourceURLs: ["/api*", "/apis*"]
users: ["system:anonymous"]
# Par défaut : métadonnées seulement
- level: Metadata
Les quatre niveaux d’audit :
None : la requête n’est pas enregistrée
Metadata : méta-informations (qui, quand, sur quoi) mais pas le corps de la requête
Request : métadonnées + corps de la requête
RequestResponse : métadonnées + requête + réponse complète
Frameworks de conformité#
SOC 2, PCI-DSS, ISO 27001#
Ces frameworks définissent des contrôles organisationnels et techniques. Leur traduction en contrôles Kubernetes est directe :
Framework |
Exigence |
Contrôle technique Kubernetes |
|---|---|---|
SOC 2 CC6.1 |
Accès logique restreint |
RBAC + NetworkPolicy |
SOC 2 CC7.2 |
Détection d’anomalies |
Falco + audit logs |
PCI-DSS 6.4 |
Scan des vulnérabilités |
Trivy dans le pipeline CI |
PCI-DSS 10.2 |
Journalisation des accès |
Audit logging niveau Request |
ISO 27001 A.12.6 |
Gestion des vulnérabilités |
Kyverno + image signing policy |
CIS Benchmarks pour Kubernetes#
Le Center for Internet Security publie des benchmarks Kubernetes organisés en six catégories :
Control Plane : sécurisation de l’API server, du scheduler, du controller manager
Etcd : chiffrement au repos, authentification mutuelle TLS
Control Plane Configuration : audit logging activé, admission controllers configurés
Worker Nodes : kubelet, fichiers de configuration, durcissement du système d’exploitation
Policies : RBAC, Pod Security Standards, NetworkPolicies par défaut
Managed Services : spécificités EKS, GKE, AKS
L’outil kube-bench automatise la vérification de ces contrôles et produit un rapport JSON exploitable dans les pipelines CI.
Image signing avec Cosign#
Workflow sign/verify#
Cosign, développé par Sigstore, permet de signer les images OCI et de vérifier ces signatures dans les pipelines et les admission controllers.
# Génération d'une paire de clés
cosign generate-key-pair
# Signature d'une image après push (avec digest pour l'immutabilité)
cosign sign --key cosign.key \
ghcr.io/myorg/myapp:v1.2.3@sha256:abc123...
# Vérification de la signature
cosign verify --key cosign.pub \
ghcr.io/myorg/myapp:v1.2.3
# Signature keyless avec OIDC (GitHub Actions, Workload Identity GCP)
cosign sign \
--oidc-issuer https://token.actions.githubusercontent.com \
ghcr.io/myorg/myapp:v1.2.3
Rekor est le journal de transparence de Sigstore. Chaque signature est enregistrée dans un log immuable et public, permettant l’audit a posteriori de toutes les signatures émises par une organisation.
Kyverno peut enforcer la vérification de signature via une politique verifyImages :
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: verify-image-signature
spec:
rules:
- name: check-image-signature
match:
any:
- resources:
kinds: ["Pod"]
verifyImages:
- imageReferences: ["ghcr.io/myorg/*"]
attestors:
- entries:
- keys:
publicKeys: |-
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYI...
-----END PUBLIC KEY-----
SBOM et réglementation#
NTIA Minimum Elements#
Le SBOM (Software Bill of Materials) est devenu une exigence réglementaire suite à l’executive order américain de mai 2021 sur la cybersécurité. La NTIA définit sept éléments minimaux :
Nom du fournisseur du composant
Nom du composant
Version du composant
Identifiants uniques (PURL, CPE)
Relations de dépendance entre composants
Auteur du SBOM
Horodatage de génération
Les formats standards sont SPDX (Linux Foundation) et CycloneDX (OWASP). syft génère des SBOM, grype les analyse pour détecter les CVE.
# Génération d'un SBOM au format SPDX depuis une image OCI
syft ghcr.io/myorg/myapp:v1.2.3 -o spdx-json > sbom.spdx.json
# Analyse des vulnérabilités à partir du SBOM
grype sbom:./sbom.spdx.json
# Attestation du SBOM avec Cosign
cosign attest --predicate sbom.spdx.json \
--type spdxjson \
--key cosign.key \
ghcr.io/myorg/myapp:v1.2.3
RBAC Kubernetes comme politique#
Le RBAC (Role-Based Access Control) de Kubernetes est le premier niveau de gouvernance. Ses principes fondamentaux :
Least privilege : n’accorder que les permissions strictement nécessaires à chaque identité
Séparation des devoirs : les développeurs ne peuvent pas modifier les politiques de sécurité
Audit régulier : revue périodique des bindings pour détecter l’accumulation de permissions
# Auditer les permissions effectives d'un ServiceAccount
kubectl auth can-i --list \
--as=system:serviceaccount:production:deploy-bot \
-n production
# Lister tous les ClusterRoleBindings avec leurs sujets
kubectl get clusterrolebindings -o json \
| jq '.items[] | select(.subjects != null) | {name: .metadata.name, subjects: .subjects}'
Visualisations#
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
from matplotlib.patches import FancyBboxPatch
import seaborn as sns
sns.set_theme(style="whitegrid", palette="muted", font_scale=1.1)
# Radar chart : couverture des contrôles CIS Kubernetes
# 6 catégories × 3 niveaux de maturité
categories = [
"Control Plane",
"Etcd",
"Config CP",
"Worker Nodes",
"Policies",
"Managed Svcs"
]
N = len(categories)
scores_debutant = [30, 20, 35, 25, 40, 15]
scores_intermediaire = [65, 55, 70, 60, 75, 50]
scores_avance = [90, 85, 92, 88, 95, 80]
angles = [n / float(N) * 2 * np.pi for n in range(N)]
angles += angles[:1]
fig, ax = plt.subplots(figsize=(8, 8), subplot_kw=dict(polar=True))
couleurs = ["#d62728", "#ff7f0e", "#2ca02c"]
labels_radar = ["Débutant", "Intermédiaire", "Avancé"]
profils = [scores_debutant, scores_intermediaire, scores_avance]
for scores, couleur, label in zip(profils, couleurs, labels_radar):
vals = scores + scores[:1]
ax.plot(angles, vals, "o-", linewidth=2, color=couleur, label=label)
ax.fill(angles, vals, alpha=0.15, color=couleur)
ax.set_xticks(angles[:-1])
ax.set_xticklabels(categories, size=11)
ax.set_ylim(0, 100)
ax.set_yticks([20, 40, 60, 80, 100])
ax.set_yticklabels(["20 %", "40 %", "60 %", "80 %", "100 %"], size=8)
ax.set_title(
"Couverture des contrôles CIS Kubernetes\npar niveau de maturité",
size=13, pad=20
)
ax.legend(loc="upper right", bbox_to_anchor=(1.35, 1.1))
plt.show()
# Évolution du taux de conformité avec et sans Policy as Code
sns.set_theme(style="whitegrid", palette="muted", font_scale=1.1)
np.random.seed(42)
mois = np.arange(0, 25)
conformite_sans = np.clip(
85 - 0.8 * mois + np.random.normal(0, 5, len(mois)),
38, 95
)
conformite_avec = np.clip(
60 + 1.8 * mois - 0.04 * mois**2 + np.random.normal(0, 2, len(mois)),
55, 98
)
fig, ax = plt.subplots(figsize=(10, 5))
ax.plot(mois, conformite_sans, "o-", color="#d62728", linewidth=2,
markersize=5, label="Sans Policy as Code")
ax.plot(mois, conformite_avec, "s-", color="#2ca02c", linewidth=2,
markersize=5, label="Avec Policy as Code")
ax.axhline(y=80, color="#ff7f0e", linestyle="--", linewidth=1.5,
xmin=0, xmax=1, label="Seuil de conformité (80 %)")
ax.fill_between(mois, 80, 105, alpha=0.07, color="#2ca02c")
ax.axvline(x=6, color="#1f77b4", linestyle=":", linewidth=1.5)
ax.annotate(
"Déploiement\nPolicy as Code",
xy=(6, 70), xytext=(9, 57),
arrowprops=dict(arrowstyle="->", color="#1f77b4"),
color="#1f77b4", fontsize=10
)
ax.set_xlabel("Mois")
ax.set_ylabel("Taux de conformité (%)")
ax.set_title("Évolution du taux de conformité avec et sans Policy as Code")
ax.legend()
ax.set_ylim(33, 105)
plt.show()
# Flux d'admission Kubernetes : de la requête client à etcd
sns.set_theme(style="whitegrid", palette="muted", font_scale=1.1)
fig, ax = plt.subplots(figsize=(14, 5))
ax.set_xlim(0, 14)
ax.set_ylim(0, 5)
ax.axis("off")
etapes = [
(1.0, 2.5, "Requête\nclient", "#4c72b0"),
(3.0, 2.5, "Authn\n& Authz", "#4c72b0"),
(5.2, 2.5, "Mutating\nWebhooks", "#dd8452"),
(7.4, 2.5, "Validation\nde schéma", "#55a868"),
(9.6, 2.5, "Validating\nWebhooks", "#c44e52"),
(11.8, 2.5, "Persistance\netcd", "#8172b2"),
]
larg, haut = 1.6, 1.1
for x, y, texte, couleur in etapes:
boite = FancyBboxPatch(
(x - larg / 2, y - haut / 2), larg, haut,
boxstyle="round,pad=0.1",
facecolor=couleur, edgecolor="white", linewidth=2, alpha=0.88
)
ax.add_patch(boite)
ax.text(x, y, texte, ha="center", va="center",
fontsize=9, color="white", fontweight="bold")
xs = [e[0] for e in etapes]
for i in range(len(xs) - 1):
ax.annotate(
"",
xy=(xs[i+1] - larg / 2 - 0.05, 2.5),
xytext=(xs[i] + larg / 2 + 0.05, 2.5),
arrowprops=dict(arrowstyle="->", color="#333333", lw=2)
)
# Flèches de rejet
for x_rejet in [5.2, 9.6]:
ax.annotate(
"",
xy=(x_rejet, 1.05),
xytext=(x_rejet, 1.95),
arrowprops=dict(arrowstyle="->", color="#d62728", lw=2)
)
ax.text(x_rejet, 0.65, "REJECT", ha="center", color="#d62728",
fontsize=9, fontweight="bold")
ax.set_title(
"Flux d'admission Kubernetes : de la requête client à etcd",
fontsize=13, pad=15
)
plt.show()
Résumé#
Policy as Code transforme les règles de gouvernance en artefacts versionnés, testables et déployables automatiquement, éliminant la dérive de configuration et le délai de détection des non-conformités.
OPA est un moteur généraliste qui évalue des règles Rego contre des données JSON ; Gatekeeper l’intègre nativement à Kubernetes via les
ConstraintTemplateet lesConstraint.Kyverno offre une alternative native Kubernetes dont les politiques YAML sont accessibles aux équipes opérationnelles sans apprentissage d’un nouveau langage.
L’ordre d’exécution des admission controllers est fixe : mutating webhooks → validation de schéma → validating webhooks ; une ressource est toujours validée dans son état final muté.
L’audit logging à niveau
RequestouRequestResponseest indispensable pour la traçabilité réglementaire des accès aux secrets et aux ressources sensibles.Les frameworks SOC 2, PCI-DSS et ISO 27001 se traduisent directement en contrôles techniques Kubernetes : RBAC, NetworkPolicy, audit logs, scan de vulnérabilités dans le pipeline CI.
Cosign et Rekor fournissent un mécanisme de signature des images OCI et un journal de transparence immuable ; Kyverno peut enforcer ces signatures à l’admission via
verifyImages.Le SBOM est devenu une exigence réglementaire depuis l’executive order US de 2021 ; les formats SPDX et CycloneDX permettent l’inventaire des composants et leur analyse CVE par
grype.Les CIS Benchmarks organisent les contrôles Kubernetes en six catégories ;
kube-benchautomatise leur vérification et produit des rapports exploitables dans les pipelines CI/CD.Le RBAC Kubernetes doit suivre le principe de moindre privilège et faire l’objet d’audits réguliers pour détecter l’accumulation progressive de permissions non nécessaires.