360 lines
14 KiB
Bash
Executable File
360 lines
14 KiB
Bash
Executable File
#!/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"
|
|
|
|
# --- ResourceQuota : empêche le DoS par création massive de ressources ---
|
|
# POURQUOI: Sans quota, l'équipe externe peut créer 10 000 pods, des centaines de PVC,
|
|
# ou allouer des dizaines de Go de RAM. Cela peut saturer le scheduler,
|
|
# remplir le disque etcd ou faire OOM le nœud.
|
|
echo ""
|
|
echo "1bis. ResourceQuota anti-DoS dans ${NAMESPACE}..."
|
|
kubectl apply -f - <<EOF
|
|
apiVersion: v1
|
|
kind: ResourceQuota
|
|
metadata:
|
|
name: external-app-quota
|
|
namespace: ${NAMESPACE}
|
|
spec:
|
|
hard:
|
|
pods: "20"
|
|
requests.cpu: "4"
|
|
requests.memory: "8Gi"
|
|
limits.cpu: "8"
|
|
limits.memory: "16Gi"
|
|
persistentvolumeclaims: "5"
|
|
requests.storage: "20Gi"
|
|
services: "10"
|
|
services.nodeports: "0"
|
|
services.loadbalancers: "0"
|
|
secrets: "20"
|
|
configmaps: "20"
|
|
EOF
|
|
echo " ✓ ResourceQuota créé (max 20 pods, 8 CPU, 16 Gi RAM)"
|
|
|
|
# --- LimitRange : valeurs par défaut + max par container ---
|
|
# POURQUOI: Sans LimitRange, un pod peut consommer tout le CPU/RAM du nœud.
|
|
# Cela force des valeurs raisonnables même si l'équipe externe oublie
|
|
# de définir resources.requests/limits dans ses manifests.
|
|
echo ""
|
|
echo "1ter. LimitRange dans ${NAMESPACE}..."
|
|
kubectl apply -f - <<EOF
|
|
apiVersion: v1
|
|
kind: LimitRange
|
|
metadata:
|
|
name: external-app-limits
|
|
namespace: ${NAMESPACE}
|
|
spec:
|
|
limits:
|
|
- type: Container
|
|
default:
|
|
cpu: "500m"
|
|
memory: "512Mi"
|
|
ephemeral-storage: "1Gi"
|
|
defaultRequest:
|
|
cpu: "100m"
|
|
memory: "128Mi"
|
|
ephemeral-storage: "256Mi"
|
|
max:
|
|
cpu: "2"
|
|
memory: "2Gi"
|
|
ephemeral-storage: "4Gi"
|
|
min:
|
|
cpu: "10m"
|
|
memory: "16Mi"
|
|
EOF
|
|
echo " ✓ LimitRange créé (max 2 CPU / 2 Gi par container)"
|
|
|
|
# --- 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} d'escalader ses privilèges via RBAC."
|
|
spec:
|
|
validationFailureAction: Enforce
|
|
rules:
|
|
# Interdire toute création de ressources cluster-scoped (déjà bloqué par RBAC,
|
|
# mais defense in depth — si un bug RBAC laisse passer, Kyverno bloque)
|
|
- 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: {}
|
|
# Interdire la création de Role/RoleBinding qui font référence à un ClusterRole privilégié
|
|
# POURQUOI: Le RBAC standard permet de créer un RoleBinding qui lie un SA à un
|
|
# ClusterRole comme "cluster-admin" (escalade par référence). On bloque
|
|
# explicitement cette voie d'escalade.
|
|
- name: deny-rolebinding-to-clusteradmin
|
|
match:
|
|
any:
|
|
- resources:
|
|
kinds: [RoleBinding]
|
|
subjects:
|
|
- kind: ServiceAccount
|
|
name: ${SA_NAME}
|
|
namespace: ${NAMESPACE}
|
|
validate:
|
|
message: "Le compte ${SA_NAME} ne peut pas binder un ClusterRole privilégié."
|
|
deny:
|
|
conditions:
|
|
any:
|
|
- key: "{{ request.object.roleRef.name }}"
|
|
operator: AnyIn
|
|
value:
|
|
- cluster-admin
|
|
- admin
|
|
- edit
|
|
- system:masters
|
|
- key: "{{ request.object.roleRef.kind }}"
|
|
operator: Equals
|
|
value: ClusterRole
|
|
# Interdire les Roles avec verbes/ressources wildcards (couvert par block-rbac-wildcards
|
|
# global, mais ciblé spécifiquement sur ce SA pour les messages d'erreur)
|
|
- name: deny-wildcard-role-creation
|
|
match:
|
|
any:
|
|
- resources:
|
|
kinds: [Role]
|
|
subjects:
|
|
- kind: ServiceAccount
|
|
name: ${SA_NAME}
|
|
namespace: ${NAMESPACE}
|
|
validate:
|
|
message: "Le compte ${SA_NAME} ne peut pas créer de Role avec wildcards (*) ou verbes d'escalade."
|
|
deny:
|
|
conditions:
|
|
any:
|
|
- key: "{{ request.object.rules[].verbs[] | contains(@, '*') }}"
|
|
operator: Equals
|
|
value: true
|
|
- key: "{{ request.object.rules[].resources[] | contains(@, '*') }}"
|
|
operator: Equals
|
|
value: true
|
|
- key: "{{ request.object.rules[].verbs[] | contains(@, 'escalate') }}"
|
|
operator: Equals
|
|
value: true
|
|
- key: "{{ request.object.rules[].verbs[] | contains(@, 'bind') }}"
|
|
operator: Equals
|
|
value: true
|
|
- key: "{{ request.object.rules[].verbs[] | contains(@, 'impersonate') }}"
|
|
operator: Equals
|
|
value: true
|
|
EOF
|
|
echo " ✓ Policy anti-escalade Kyverno créée (3 règles)"
|
|
|
|
# --- 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 (et qui sont bloqués) :"
|
|
echo " - Container breakout (pod privileged) → refusé par Kyverno + PSA"
|
|
echo " - Évasion namespace (hostNetwork/PID/IPC) → refusé par Kyverno"
|
|
echo " - Mount hostPath → refusé par Kyverno"
|
|
echo " - Shell dans container → bloqué par KubeArmor"
|
|
echo " - Outils d'évasion (nsenter, unshare) → bloqués par KubeArmor"
|
|
echo " - Accès socket containerd → bloqué par KubeArmor"
|
|
echo " - Écriture /etc, /usr, /bin → bloquée par KubeArmor"
|
|
echo " - Lecture secrets existants → interdit par RBAC"
|
|
echo " - Création ClusterRole/Binding → interdit par RBAC + Kyverno"
|
|
echo " - RoleBinding vers cluster-admin → bloqué par Kyverno"
|
|
echo " - Service NodePort/LoadBalancer → bloqué par Kyverno"
|
|
echo " - hostPort → bloqué par Kyverno"
|
|
echo " - Image :latest ou registry inconnu → refusé par Kyverno"
|
|
echo " - DoS par création massive de pods → bloqué par ResourceQuota (max 20 pods)"
|
|
echo " - DoS RAM/CPU → bloqué par LimitRange (max 2 CPU / 2 Gi par container)"
|
|
echo " - Communication inter-namespace → bloquée par NetworkPolicy default-deny"
|
|
echo " - Énumération nodes/namespaces → interdit par RBAC"
|
|
echo "============================================="
|