Signed-off-by: Louis Labeyrie <labeyrielouis@gmail.com>
This commit is contained in:
2026-04-27 15:29:07 +02:00
parent 5760eab56a
commit 1e24ec3243
10 changed files with 1501 additions and 125 deletions

View File

@@ -0,0 +1,225 @@
#!/bin/bash
# Partie 1 - Génération d'un kubeconfig restreint pour l'équipe externe
# À exécuter sur le nœud MASTER
#
# Ce kubeconfig permet à l'équipe externe de :
# ✓ Déployer une application dans le namespace "external-app"
# ✓ Gérer Deployments, Services, Pods, ConfigMaps dans ce namespace
# ✗ Accéder aux autres namespaces
# ✗ Créer des ClusterRoles ou ClusterRoleBindings
# ✗ Exécuter kubectl exec (pods/exec interdit)
# ✗ Lire les secrets existants (create/update seulement)
set -e
NAMESPACE="external-app"
SA_NAME="external-deployer"
TOKEN_DURATION="24h"
OUTPUT_FILE="./external-team-kubeconfig.yaml"
echo "=== Génération du kubeconfig restreint pour l'équipe externe ==="
echo ""
echo "Namespace : $NAMESPACE"
echo "Compte : $SA_NAME"
echo "Expiration : $TOKEN_DURATION"
echo "Fichier : $OUTPUT_FILE"
echo ""
# --- Namespace ---
echo "1. Création du namespace ${NAMESPACE}..."
kubectl create namespace "$NAMESPACE" --dry-run=client -o yaml | kubectl apply -f -
# Labeliser le namespace pour PodSecurity (enforce: restricted pour l'équipe externe)
kubectl label namespace "$NAMESPACE" \
pod-security.kubernetes.io/enforce=restricted \
pod-security.kubernetes.io/enforce-version=latest \
pod-security.kubernetes.io/audit=restricted \
pod-security.kubernetes.io/warn=restricted \
--overwrite
echo " ✓ Namespace créé avec PodSecurity=restricted"
# --- ServiceAccount ---
echo ""
echo "2. Création du ServiceAccount ${SA_NAME}..."
kubectl create serviceaccount "$SA_NAME" \
--namespace "$NAMESPACE" \
--dry-run=client -o yaml | kubectl apply -f -
echo " ✓ ServiceAccount créé"
# --- Role namespace-scoped (pas ClusterRole) ---
echo ""
echo "3. Création du Role (namespace-scoped)..."
kubectl apply -f - <<EOF
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: external-deployer-role
namespace: ${NAMESPACE}
rules:
# Déploiement d'application
- apiGroups: ["apps"]
resources: ["deployments", "replicasets", "statefulsets"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
# Workloads de base
- apiGroups: [""]
resources: ["pods", "services", "configmaps", "serviceaccounts"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
# Secrets : créer/modifier uniquement — PAS de lecture des secrets existants
# POURQUOI: Interdire "get"/"list" sur les secrets empêche la lecture des
# ServiceAccount tokens ou credentials déjà présents dans le namespace.
- apiGroups: [""]
resources: ["secrets"]
verbs: ["create", "update", "patch"]
# Logs des pods (debug légitime)
- apiGroups: [""]
resources: ["pods/log"]
verbs: ["get"]
# NetworkPolicy : peut en créer pour exposer son app, mais ne peut pas supprimer la deny-all
- apiGroups: ["networking.k8s.io"]
resources: ["networkpolicies"]
verbs: ["get", "list", "watch", "create", "update", "patch"]
# Ingress si nécessaire
- apiGroups: ["networking.k8s.io"]
resources: ["ingresses"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
# Lecture des events (debug)
- apiGroups: [""]
resources: ["events"]
verbs: ["get", "list", "watch"]
# INTERDITS (pas listés = interdits par RBAC) :
# - pods/exec (pas de shell interactif dans les pods)
# - pods/portforward (pas de tunnel direct)
# - ClusterRole, ClusterRoleBinding (pas d'escalade cluster-wide)
# - nodes, persistentvolumes, namespaces (ressources cluster-scoped)
# - secrets "get"/"list" (pas de lecture de credentials existants)
# - verbes "escalate", "bind", "impersonate"
EOF
echo " ✓ Role créé"
# --- RoleBinding ---
echo ""
echo "4. Création du RoleBinding..."
kubectl apply -f - <<EOF
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: external-deployer-binding
namespace: ${NAMESPACE}
subjects:
- kind: ServiceAccount
name: ${SA_NAME}
namespace: ${NAMESPACE}
roleRef:
kind: Role
name: external-deployer-role
apiGroup: rbac.authorization.k8s.io
EOF
echo " ✓ RoleBinding créé"
# --- Kyverno policy : interdire l'escalade cluster-wide depuis ce SA ---
echo ""
echo "5. Kyverno : politique d'escalade pour ${SA_NAME}..."
kubectl apply -f - <<EOF
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: restrict-external-deployer-escalation
annotations:
policies.kyverno.io/description: "Interdit au SA ${SA_NAME} de créer des ressources cluster-scoped."
spec:
validationFailureAction: Enforce
rules:
- name: deny-clusterrole-creation
match:
any:
- resources:
kinds: [ClusterRole, ClusterRoleBinding]
subjects:
- kind: ServiceAccount
name: ${SA_NAME}
namespace: ${NAMESPACE}
validate:
message: "Le compte ${SA_NAME} ne peut pas créer de ressources cluster-scoped."
deny: {}
EOF
echo " ✓ Policy anti-escalade Kyverno créée"
# --- Génération du token ---
echo ""
echo "6. Génération du token (durée: ${TOKEN_DURATION})..."
TOKEN=$(kubectl create token "$SA_NAME" \
--namespace "$NAMESPACE" \
--duration="$TOKEN_DURATION")
echo " ✓ Token généré (expire dans ${TOKEN_DURATION})"
# --- Construction du kubeconfig ---
echo ""
echo "7. Construction du kubeconfig..."
CLUSTER_NAME=$(kubectl config view --minify -o jsonpath='{.clusters[0].name}')
APISERVER=$(kubectl config view --minify -o jsonpath='{.clusters[0].cluster.server}')
CA_DATA=$(kubectl config view --minify --raw -o jsonpath='{.clusters[0].cluster.certificate-authority-data}')
cat > "$OUTPUT_FILE" <<EOF
apiVersion: v1
kind: Config
current-context: external-context
clusters:
- name: ${CLUSTER_NAME}
cluster:
server: ${APISERVER}
certificate-authority-data: ${CA_DATA}
contexts:
- name: external-context
context:
cluster: ${CLUSTER_NAME}
namespace: ${NAMESPACE}
user: ${SA_NAME}
users:
- name: ${SA_NAME}
user:
token: ${TOKEN}
EOF
chmod 600 "$OUTPUT_FILE"
echo " ✓ Kubeconfig écrit: $OUTPUT_FILE (chmod 600)"
# --- Vérification ---
echo ""
echo "=== Vérification des permissions ==="
echo ""
echo "Actions autorisées :"
kubectl auth can-i create deployments --as="system:serviceaccount:${NAMESPACE}:${SA_NAME}" -n "$NAMESPACE" && echo " ✓ create deployments" || echo " ✗ create deployments"
kubectl auth can-i create pods --as="system:serviceaccount:${NAMESPACE}:${SA_NAME}" -n "$NAMESPACE" && echo " ✓ create pods" || echo " ✗ create pods"
kubectl auth can-i create services --as="system:serviceaccount:${NAMESPACE}:${SA_NAME}" -n "$NAMESPACE" && echo " ✓ create services" || echo " ✗ create services"
echo ""
echo "Actions interdites :"
kubectl auth can-i get secrets --as="system:serviceaccount:${NAMESPACE}:${SA_NAME}" -n "$NAMESPACE" && echo " ✗ PROBLÈME: get secrets autorisé" || echo " ✓ get secrets refusé"
kubectl auth can-i create pods --as="system:serviceaccount:${NAMESPACE}:${SA_NAME}" -n kube-system && echo " ✗ PROBLÈME: create pods dans kube-system autorisé" || echo " ✓ create pods dans kube-system refusé"
kubectl auth can-i create clusterroles --as="system:serviceaccount:${NAMESPACE}:${SA_NAME}" && echo " ✗ PROBLÈME: create clusterroles autorisé" || echo " ✓ create clusterroles refusé"
kubectl auth can-i create nodes --as="system:serviceaccount:${NAMESPACE}:${SA_NAME}" && echo " ✗ PROBLÈME: create nodes autorisé" || echo " ✓ create nodes refusé"
echo ""
echo "============================================="
echo " KUBECONFIG PRÊT À PARTAGER"
echo "============================================="
echo ""
echo " Fichier : $OUTPUT_FILE"
echo " Namespace : $NAMESPACE"
echo " Expiration : ${TOKEN_DURATION} à partir de maintenant"
echo ""
echo " IMPORTANT : Ce token expire dans ${TOKEN_DURATION}."
echo " Pour régénérer : kubectl create token ${SA_NAME} -n ${NAMESPACE} --duration=${TOKEN_DURATION}"
echo ""
echo " Vecteurs d'attaque que l'équipe PEUT tenter :"
echo " - Escape via les NetworkPolicies (tenter de contacter d'autres namespaces)"
echo " - Escalade RBAC (tenter de créer des ClusterRoles/ClusterRoleBindings)"
echo " - Container breakout (pod privileged → refusé par Kyverno)"
echo " - Shell dans container (bloqué par KubeArmor)"
echo " - Lecture secrets (interdit par RBAC)"
echo " - Déploiement d'image malveillante avec :latest (refusé par Kyverno)"
echo "============================================="