From d23d2cbe37256c42d720243fdb16af5b05e9de7e Mon Sep 17 00:00:00 2001 From: Xor290 Date: Sun, 16 Aug 2026 20:01:14 +0200 Subject: [PATCH] chore: update --- deploy/beszel-networkpolicy.yml | 89 ++++++++++++ deploy/cert-manager/networkpolicy.yml | 127 ++++++++++++++++++ deploy/chart-gestion/backend/values.yaml | 6 +- deploy/chart-gestion/frontend/values.yaml | 2 +- .../templates/networkpolicy-deny-default.yaml | 16 +++ .../templates/networkpolicy-dns.yaml | 25 ++++ .../templates/networkpolicy-ingress.yaml | 19 +++ .../templates/networkpolicy-internet-pub.yaml | 21 +++ .../templates/networkpolicy-intra-demo.yaml | 22 +++ .../templates/networkpolicy.yaml | 106 --------------- .../chart-gestion/network-policy/values.yaml | 1 - .../chart-gestion/resource-quota/Chart.yaml | 6 + .../templates/resourcequota.yaml | 36 +++++ .../chart-gestion/resource-quota/values.yaml | 34 +++++ deploy/daemonset-beszel.yml | 26 ++++ deploy/velero/networkpolicy.yml | 86 ++++++++++++ deploy/velero/values.yaml | 34 +++-- 17 files changed, 530 insertions(+), 126 deletions(-) create mode 100644 deploy/beszel-networkpolicy.yml create mode 100644 deploy/cert-manager/networkpolicy.yml create mode 100644 deploy/chart-gestion/network-policy/templates/networkpolicy-deny-default.yaml create mode 100644 deploy/chart-gestion/network-policy/templates/networkpolicy-dns.yaml create mode 100644 deploy/chart-gestion/network-policy/templates/networkpolicy-ingress.yaml create mode 100644 deploy/chart-gestion/network-policy/templates/networkpolicy-internet-pub.yaml create mode 100644 deploy/chart-gestion/network-policy/templates/networkpolicy-intra-demo.yaml delete mode 100644 deploy/chart-gestion/network-policy/templates/networkpolicy.yaml create mode 100644 deploy/chart-gestion/resource-quota/Chart.yaml create mode 100644 deploy/chart-gestion/resource-quota/templates/resourcequota.yaml create mode 100644 deploy/chart-gestion/resource-quota/values.yaml create mode 100644 deploy/velero/networkpolicy.yml diff --git a/deploy/beszel-networkpolicy.yml b/deploy/beszel-networkpolicy.yml new file mode 100644 index 0000000..1075c20 --- /dev/null +++ b/deploy/beszel-networkpolicy.yml @@ -0,0 +1,89 @@ +# Isolation réseau du namespace monitoring (beszel-agent) — deny-by-default + +# allowlist : +# - Egress : HTTPS sortant vers le Hub (HUB_URL, hôte externe arbitraire — +# voir beszel-agent-secret.yml) pour l'auto-enregistrement au démarrage. +# - Ingress : le Hub se connecte à l'agent en SSH sur le port LISTEN +# (45876) pour récupérer les métriques — IP du Hub non fixe/connue +# d'avance, ouvert largement mais authentifié par TOKEN/KEY (voir +# beszel-agent-secret.yml), pas par l'origine réseau. +# +# ⚠️ LIMITE IMPORTANTE : le DaemonSet tourne en hostNetwork: true (voir +# daemonset-beszel.yml) — avec Calico (CNI de ce cluster, voir +# deploy/cni/calico-install.yml), un NetworkPolicy standard ne s'applique en +# général PAS aux pods hostNetwork (il faudrait un HostEndpoint/ +# GlobalNetworkPolicy Calico, une ressource différente, pour un enforcement +# réel au niveau du nœud). Ce manifeste reste appliqué pour la correction +# d'intention et si hostNetwork est retiré un jour, mais ne pas compter +# dessus comme protection effective du port 45876 en l'état — un pare-feu +# hôte (iptables/nftables/ufw) ou une GlobalNetworkPolicy Calico est +# nécessaire pour une vraie restriction. + +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: default-deny-all + namespace: monitoring +spec: + podSelector: {} + policyTypes: + - Ingress + - Egress + +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-ingress-hub-ssh + namespace: monitoring +spec: + podSelector: {} + policyTypes: + - Ingress + ingress: + - from: [] + ports: + - protocol: TCP + port: 45876 + +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-egress-dns + namespace: monitoring +spec: + podSelector: {} + policyTypes: + - Egress + egress: + - to: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: kube-system + ports: + - protocol: UDP + port: 53 + - protocol: TCP + port: 53 + +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-egress-hub-https + namespace: monitoring +spec: + podSelector: {} + policyTypes: + - Egress + egress: + - to: + - ipBlock: + cidr: 0.0.0.0/0 + except: + - 10.0.0.0/8 + - 172.16.0.0/12 + - 192.168.0.0/16 + ports: + - protocol: TCP + port: 443 diff --git a/deploy/cert-manager/networkpolicy.yml b/deploy/cert-manager/networkpolicy.yml new file mode 100644 index 0000000..d101a12 --- /dev/null +++ b/deploy/cert-manager/networkpolicy.yml @@ -0,0 +1,127 @@ +# Isolation réseau du namespace cert-manager — même logique que +# deploy/chart-gestion/network-policy (deny-by-default + allowlist), adaptée +# aux flux réels de cert-manager : +# - Ingress : l'API server appelle le webhook d'admission de cert-manager +# (validation des CRD Certificate/Issuer/ClusterIssuer) sur le port 10250. +# - Egress : API server (lecture/écriture des CRD + Secrets), DNS, et +# Internet en HTTPS (Let's Encrypt ACME + API Cloudflare pour le +# challenge DNS-01 — IPs non fixes, pas de CIDR précis possible). +# +# ⚠️ AVANT D'APPLIQUER : remplacer CHANGE_ME_apiserver_ip par l'IP réelle de +# l'API server de votre cluster (trouvable via : +# kubectl get endpoints kubernetes -n default -o wide +# ). Une mauvaise valeur bloque l'accès de cert-manager à l'API Kubernetes — +# le renouvellement automatique des certificats s'arrête silencieusement +# (pas d'impact immédiat, le cert en cours reste valide jusqu'à expiration). +# Testez avec `kubectl apply --dry-run=server` puis surveillez +# `kubectl get certificate -A` après application. + +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: default-deny-all + namespace: cert-manager +spec: + podSelector: {} + policyTypes: + - Ingress + - Egress + +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-intra-namespace + namespace: cert-manager +spec: + podSelector: {} + policyTypes: + - Ingress + - Egress + ingress: + - from: + - podSelector: {} + egress: + - to: + - podSelector: {} + +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-ingress-apiserver-webhook + namespace: cert-manager +spec: + podSelector: {} + policyTypes: + - Ingress + ingress: + - from: + - ipBlock: + cidr: CHANGE_ME_apiserver_ip/32 + ports: + - protocol: TCP + port: 10250 + +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-egress-dns + namespace: cert-manager +spec: + podSelector: {} + policyTypes: + - Egress + egress: + - to: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: kube-system + ports: + - protocol: UDP + port: 53 + - protocol: TCP + port: 53 + +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-egress-apiserver + namespace: cert-manager +spec: + podSelector: {} + policyTypes: + - Egress + egress: + - to: + - ipBlock: + cidr: CHANGE_ME_apiserver_ip/32 + ports: + - protocol: TCP + port: 6443 + +--- +# Let's Encrypt (ACME) + API Cloudflare (challenge DNS-01) : IPs publiques +# non fixes, seul HTTPS sortant est nécessaire. +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-egress-internet-https + namespace: cert-manager +spec: + podSelector: {} + policyTypes: + - Egress + egress: + - to: + - ipBlock: + cidr: 0.0.0.0/0 + except: + - 10.0.0.0/8 + - 172.16.0.0/12 + - 192.168.0.0/16 + ports: + - protocol: TCP + port: 443 diff --git a/deploy/chart-gestion/backend/values.yaml b/deploy/chart-gestion/backend/values.yaml index e2cbeca..c94effd 100644 --- a/deploy/chart-gestion/backend/values.yaml +++ b/deploy/chart-gestion/backend/values.yaml @@ -2,7 +2,7 @@ replicaCount: 2 image: repository: backend-mln - tag: helm + tag: latest pullPolicy: IfNotPresent service: @@ -27,10 +27,6 @@ persistence: uploads: enabled: true size: 5Gi - # Répliqué sur 2 nœuds (Longhorn) : survit à la perte d'un nœud. Voir - # deploy/longhorn/storageclass.yml pour le prérequis d'installation. - # N'a d'effet que pour storage_driver=local (les uploads S3 ne passent - # jamais par ce PVC, voir ProvisionConfig.StorageDriver). storageClass: "longhorn-replicated" accessMode: ReadWriteOnce diff --git a/deploy/chart-gestion/frontend/values.yaml b/deploy/chart-gestion/frontend/values.yaml index 8975942..13a4224 100644 --- a/deploy/chart-gestion/frontend/values.yaml +++ b/deploy/chart-gestion/frontend/values.yaml @@ -4,7 +4,7 @@ apiUrl: "" image: repository: frontend-mln - tag: helm + tag: latest pullPolicy: IfNotPresent service: diff --git a/deploy/chart-gestion/network-policy/templates/networkpolicy-deny-default.yaml b/deploy/chart-gestion/network-policy/templates/networkpolicy-deny-default.yaml new file mode 100644 index 0000000..77b210e --- /dev/null +++ b/deploy/chart-gestion/network-policy/templates/networkpolicy-deny-default.yaml @@ -0,0 +1,16 @@ +--- +# ------------------------------------------------------------------- +# 1. Deny-by-default : sans règle explicite plus bas, AUCUN trafic entrant +# ni sortant n'est autorisé pour les pods de ce namespace. C'est ce qui +# empêche par défaut toute communication vers un autre namespace de démo +# et vers l'API Kubernetes. +# ------------------------------------------------------------------- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: default-deny-all +spec: + podSelector: {} + policyTypes: + - Ingress + - Egress diff --git a/deploy/chart-gestion/network-policy/templates/networkpolicy-dns.yaml b/deploy/chart-gestion/network-policy/templates/networkpolicy-dns.yaml new file mode 100644 index 0000000..707aeef --- /dev/null +++ b/deploy/chart-gestion/network-policy/templates/networkpolicy-dns.yaml @@ -0,0 +1,25 @@ +--- +# ------------------------------------------------------------------- +# 4. DNS uniquement vers kube-system, port 53. C'est la SEULE ouverture vers +# kube-system : aucun accès à autre chose dans ce namespace, et l'API +# server (kubernetes.default.svc, namespace "default") n'est jamais +# autorisé nulle part dans ce chart -> bloqué par le deny-by-default. +# ------------------------------------------------------------------- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-egress-dns +spec: + podSelector: {} + policyTypes: + - Egress + egress: + - to: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: {{ .Values.kubeSystemNamespace }} + ports: + - protocol: UDP + port: 53 + - protocol: TCP + port: 53 diff --git a/deploy/chart-gestion/network-policy/templates/networkpolicy-ingress.yaml b/deploy/chart-gestion/network-policy/templates/networkpolicy-ingress.yaml new file mode 100644 index 0000000..923d5db --- /dev/null +++ b/deploy/chart-gestion/network-policy/templates/networkpolicy-ingress.yaml @@ -0,0 +1,19 @@ +--- +# ------------------------------------------------------------------- +# 3. Ingress externe autorisé : uniquement depuis le Traefik partagé +# (aucune autre démo, aucun autre client, ne peut ouvrir de connexion +# directe vers les pods de cette démo). +# ------------------------------------------------------------------- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-ingress-from-traefik +spec: + podSelector: {} + policyTypes: + - Ingress + ingress: + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: {{ .Values.traefikNamespace }} diff --git a/deploy/chart-gestion/network-policy/templates/networkpolicy-internet-pub.yaml b/deploy/chart-gestion/network-policy/templates/networkpolicy-internet-pub.yaml new file mode 100644 index 0000000..c43ea64 --- /dev/null +++ b/deploy/chart-gestion/network-policy/templates/networkpolicy-internet-pub.yaml @@ -0,0 +1,21 @@ +--- +# ------------------------------------------------------------------- +# 5. Internet public sortant (webhooks Telegram/NowPayments, GPS TomTom...), +# en excluant les plages privées : ça couvre par construction l'IP de +# l'API server et celles des autres namespaces, sans avoir besoin de la +# connaître explicitement. +# ------------------------------------------------------------------- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-egress-internet +spec: + podSelector: {} + policyTypes: + - Egress + egress: + - to: + - ipBlock: + cidr: 0.0.0.0/0 + except: + {{ toYaml .Values.blockedEgressCIDRs | nindent 14 }} diff --git a/deploy/chart-gestion/network-policy/templates/networkpolicy-intra-demo.yaml b/deploy/chart-gestion/network-policy/templates/networkpolicy-intra-demo.yaml new file mode 100644 index 0000000..b93cbb2 --- /dev/null +++ b/deploy/chart-gestion/network-policy/templates/networkpolicy-intra-demo.yaml @@ -0,0 +1,22 @@ +--- +# ------------------------------------------------------------------- +# 2. Trafic intra-démo : frontend <-> backend <-> postgresql/redis, dans +# CE namespace uniquement (le podSelector du namespaceSelector par défaut +# de Kubernetes limite déjà à ce namespace : pas de namespaceSelector ici +# équivaut à "même namespace"). +# ------------------------------------------------------------------- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-intra-namespace +spec: + podSelector: {} + policyTypes: + - Ingress + - Egress + ingress: + - from: + - podSelector: {} + egress: + - to: + - podSelector: {} diff --git a/deploy/chart-gestion/network-policy/templates/networkpolicy.yaml b/deploy/chart-gestion/network-policy/templates/networkpolicy.yaml deleted file mode 100644 index 9feb26f..0000000 --- a/deploy/chart-gestion/network-policy/templates/networkpolicy.yaml +++ /dev/null @@ -1,106 +0,0 @@ -# ------------------------------------------------------------------- -# 1. Deny-by-default : sans règle explicite plus bas, AUCUN trafic entrant -# ni sortant n'est autorisé pour les pods de ce namespace. C'est ce qui -# empêche par défaut toute communication vers un autre namespace de démo -# et vers l'API Kubernetes. -# ------------------------------------------------------------------- -apiVersion: networking.k8s.io/v1 -kind: NetworkPolicy -metadata: - name: default-deny-all -spec: - podSelector: {} - policyTypes: - - Ingress - - Egress - ---- -# ------------------------------------------------------------------- -# 2. Trafic intra-démo : frontend <-> backend <-> postgresql/redis, dans -# CE namespace uniquement (le podSelector du namespaceSelector par défaut -# de Kubernetes limite déjà à ce namespace : pas de namespaceSelector ici -# équivaut à "même namespace"). -# ------------------------------------------------------------------- -apiVersion: networking.k8s.io/v1 -kind: NetworkPolicy -metadata: - name: allow-intra-namespace -spec: - podSelector: {} - policyTypes: - - Ingress - - Egress - ingress: - - from: - - podSelector: {} - egress: - - to: - - podSelector: {} - ---- -# ------------------------------------------------------------------- -# 3. Ingress externe autorisé : uniquement depuis le Traefik partagé -# (aucune autre démo, aucun autre client, ne peut ouvrir de connexion -# directe vers les pods de cette démo). -# ------------------------------------------------------------------- -apiVersion: networking.k8s.io/v1 -kind: NetworkPolicy -metadata: - name: allow-ingress-from-traefik -spec: - podSelector: {} - policyTypes: - - Ingress - ingress: - - from: - - namespaceSelector: - matchLabels: - kubernetes.io/metadata.name: {{ .Values.traefikNamespace }} - ---- -# ------------------------------------------------------------------- -# 4. DNS uniquement vers kube-system, port 53. C'est la SEULE ouverture vers -# kube-system : aucun accès à autre chose dans ce namespace, et l'API -# server (kubernetes.default.svc, namespace "default") n'est jamais -# autorisé nulle part dans ce chart -> bloqué par le deny-by-default. -# ------------------------------------------------------------------- -apiVersion: networking.k8s.io/v1 -kind: NetworkPolicy -metadata: - name: allow-egress-dns -spec: - podSelector: {} - policyTypes: - - Egress - egress: - - to: - - namespaceSelector: - matchLabels: - kubernetes.io/metadata.name: {{ .Values.kubeSystemNamespace }} - ports: - - protocol: UDP - port: 53 - - protocol: TCP - port: 53 - ---- -# ------------------------------------------------------------------- -# 5. Internet public sortant (webhooks Telegram/NowPayments, GPS TomTom...), -# en excluant les plages privées : ça couvre par construction l'IP de -# l'API server et celles des autres namespaces, sans avoir besoin de la -# connaître explicitement. -# ------------------------------------------------------------------- -apiVersion: networking.k8s.io/v1 -kind: NetworkPolicy -metadata: - name: allow-egress-internet -spec: - podSelector: {} - policyTypes: - - Egress - egress: - - to: - - ipBlock: - cidr: 0.0.0.0/0 - except: - {{- toYaml .Values.blockedEgressCIDRs | nindent 14 }} diff --git a/deploy/chart-gestion/network-policy/values.yaml b/deploy/chart-gestion/network-policy/values.yaml index 5cd68fc..006119d 100644 --- a/deploy/chart-gestion/network-policy/values.yaml +++ b/deploy/chart-gestion/network-policy/values.yaml @@ -13,7 +13,6 @@ kubeSystemNamespace: kube-system # Namespace du Traefik partagé (voir deploy/chart-gestion/traefik) : seule # origine externe autorisée à ouvrir une connexion ingress vers cette démo. traefikNamespace: traefik - # Plages privées exclues de l'egress "internet" autorisé (RFC1918) : empêche # tout mouvement latéral vers d'autres pods/namespaces/nœuds/l'API server par # IP directe, tout en laissant sortir vers l'internet public (Telegram, diff --git a/deploy/chart-gestion/resource-quota/Chart.yaml b/deploy/chart-gestion/resource-quota/Chart.yaml new file mode 100644 index 0000000..e7c4f3b --- /dev/null +++ b/deploy/chart-gestion/resource-quota/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: gestion-resource-quota +description: Plafond agrégé de ressources par namespace de démo (ResourceQuota + LimitRange) — empêche un client de saturer le cluster au détriment des autres démos, même via l'autoscaling (HPA) du backend/frontend. +type: application +version: 0.1.0 +appVersion: "1.0.0" diff --git a/deploy/chart-gestion/resource-quota/templates/resourcequota.yaml b/deploy/chart-gestion/resource-quota/templates/resourcequota.yaml new file mode 100644 index 0000000..f3a1182 --- /dev/null +++ b/deploy/chart-gestion/resource-quota/templates/resourcequota.yaml @@ -0,0 +1,36 @@ +apiVersion: v1 +kind: ResourceQuota +metadata: + name: demo-quota +spec: + hard: + requests.cpu: {{ .Values.quota.requestsCpu | quote }} + requests.memory: {{ .Values.quota.requestsMemory | quote }} + limits.cpu: {{ .Values.quota.limitsCpu | quote }} + limits.memory: {{ .Values.quota.limitsMemory | quote }} + pods: {{ .Values.quota.maxPods | quote }} + persistentvolumeclaims: {{ .Values.quota.maxPVCs | quote }} + +--- +# Défauts requests/limits pour tout container qui ne les préciserait pas — +# nécessaire pour que le ResourceQuota ci-dessus n'empêche pas la création +# de pods qui omettraient ces champs. +apiVersion: v1 +kind: LimitRange +metadata: + name: demo-limits +spec: + limits: + - type: Container + defaultRequest: + cpu: {{ .Values.limitRange.defaultRequestCpu | quote }} + memory: {{ .Values.limitRange.defaultRequestMemory | quote }} + default: + cpu: {{ .Values.limitRange.defaultLimitCpu | quote }} + memory: {{ .Values.limitRange.defaultLimitMemory | quote }} + min: + cpu: {{ .Values.limitRange.minCpu | quote }} + memory: {{ .Values.limitRange.minMemory | quote }} + max: + cpu: {{ .Values.limitRange.maxCpu | quote }} + memory: {{ .Values.limitRange.maxMemory | quote }} diff --git a/deploy/chart-gestion/resource-quota/values.yaml b/deploy/chart-gestion/resource-quota/values.yaml new file mode 100644 index 0000000..f55c651 --- /dev/null +++ b/deploy/chart-gestion/resource-quota/values.yaml @@ -0,0 +1,34 @@ +# Plafonds calculés à partir du pire cas d'une démo au complet, HPA au max +# (voir deploy/chart-gestion/{postgresql,redis,backend,frontend,lbtelegram} +# /values.yaml — postgres 1 replica, redis 1 replica, backend jusqu'à 5 +# replicas, frontend jusqu'à 4, lbtelegram 1 replica si activé) : +# requests cpu : 250m + 100m + 5×100m + 4×50m + 100m = 1150m -> arrondi 2 +# requests mem : 256 + 128 + 5×128 + 4×64 + 128 = 1408Mi -> arrondi 2Gi +# limits cpu : 1000m + 500m + 5×500m + 4×200m + 500m = 5300m -> arrondi 6 +# limits mem : 1024 + 512 + 5×256 + 4×128 + 256 = 3584Mi -> arrondi 5Gi +# Marge volontaire au-dessus du calcul pour laisser de la place à des pods +# ponctuels (exec admin, job de migration) sans bloquer le provisioning. +quota: + requestsCpu: "2" + requestsMemory: "2Gi" + limitsCpu: "6" + limitsMemory: "5Gi" + # Cap dur sur le nombre de pods/PVC du namespace — empêche un pod-spam ou + # une boucle de CrashLoop mal configurée d'épuiser les IP/ressources du + # nœud. 3 PVC utilisés aujourd'hui (postgres, redis, uploads) + marge. + maxPods: "20" + maxPVCs: "5" + +# Défauts appliqués à tout container qui ne préciserait pas ses propres +# requests/limits — sans ça, le ResourceQuota ci-dessus rendrait la création +# de pod obligatoirement explicite (K8s rejette un pod sans requests/limits +# dans un namespace où un ResourceQuota les couvre). +limitRange: + defaultRequestCpu: "100m" + defaultRequestMemory: "128Mi" + defaultLimitCpu: "500m" + defaultLimitMemory: "256Mi" + minCpu: "10m" + minMemory: "16Mi" + maxCpu: "2" + maxMemory: "1Gi" diff --git a/deploy/daemonset-beszel.yml b/deploy/daemonset-beszel.yml index 85950d1..729b3cc 100644 --- a/deploy/daemonset-beszel.yml +++ b/deploy/daemonset-beszel.yml @@ -28,6 +28,14 @@ spec: - effect: NoSchedule key: node-role.kubernetes.io/control-plane operator: Exists + # Niveau pod : s'applique à tous les containers (ici un seul). + securityContext: + runAsNonRoot: true + runAsUser: 101 + runAsGroup: 101 + fsGroup: 101 + seccompProfile: + type: RuntimeDefault containers: - name: beszel-agent image: henrygd/beszel-agent:latest @@ -45,6 +53,10 @@ spec: volumeMounts: - name: data mountPath: /var/lib/beszel-agent + # Filet de sécurité pour readOnlyRootFilesystem : couvre d'éventuelles + # écritures temporaires (non documentées) sans réouvrir tout le rootfs. + - name: tmp + mountPath: /tmp resources: requests: cpu: 10m @@ -52,11 +64,25 @@ spec: limits: cpu: 50m memory: 32Mi + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: [ALL] + # Inféré (pas de doc officielle confirmant l'absence d'écriture + # ailleurs) : le seul chemin persistant documenté par le projet + # est /var/lib/beszel-agent (déjà monté ci-dessous), ce qui + # suggère que c'est le seul état écrit sur disque (ex. clé hôte + # SSH). À VÉRIFIER après rollout (kubectl logs -n monitoring + # -l app=beszel-agent) — si le pod crash-loop ou que le Hub ne + # voit plus l'agent, repasser cette valeur à false. + readOnlyRootFilesystem: true volumes: - name: data hostPath: path: /var/lib/beszel-agent type: DirectoryOrCreate + - name: tmp + emptyDir: {} # - name: docker-sock # hostPath: # path: /var/run/docker.sock diff --git a/deploy/velero/networkpolicy.yml b/deploy/velero/networkpolicy.yml new file mode 100644 index 0000000..44a70a2 --- /dev/null +++ b/deploy/velero/networkpolicy.yml @@ -0,0 +1,86 @@ +# Isolation réseau du namespace velero — deny-by-default + allowlist, adaptée +# aux flux réels de Velero : +# - Ingress : AUCUN. Personne n'a besoin d'ouvrir une connexion vers les +# pods Velero (le CLI "velero backup/restore" parle à l'API server, pas +# directement aux pods) ; le node-agent (File System Backup) lit les +# données des volumes via hostPath sur le nœud, pas par le réseau. +# - Egress : API server (CRD Backup/Restore/Schedule + lecture des Secrets +# à sauvegarder), DNS, et le endpoint S3/RustFS/MinIO configuré dans +# values.yaml. +# +# ⚠️ AVANT D'APPLIQUER : remplacer CHANGE_ME_apiserver_ip et +# CHANGE_ME_s3_endpoint_ip par les IP réelles (API server : voir +# deploy/cert-manager/networkpolicy.yml ; endpoint S3 : IP de votre instance +# RustFS/MinIO, celle configurée dans s3Url de values.yaml). Une mauvaise +# valeur ne casse rien d'immédiat (les démos continuent de tourner) mais fait +# silencieusement échouer TOUTES les sauvegardes — vérifiez après application +# avec `velero backup create test-netpol --wait` puis `velero backup describe +# test-netpol` (statut Completed attendu). + +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: default-deny-all + namespace: velero +spec: + podSelector: {} + policyTypes: + - Ingress + - Egress + +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-egress-dns + namespace: velero +spec: + podSelector: {} + policyTypes: + - Egress + egress: + - to: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: kube-system + ports: + - protocol: UDP + port: 53 + - protocol: TCP + port: 53 + +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-egress-apiserver + namespace: velero +spec: + podSelector: {} + policyTypes: + - Egress + egress: + - to: + - ipBlock: + cidr: CHANGE_ME_apiserver_ip/32 + ports: + - protocol: TCP + port: 6443 + +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-egress-s3-storage + namespace: velero +spec: + podSelector: {} + policyTypes: + - Egress + egress: + - to: + - ipBlock: + cidr: CHANGE_ME_s3_endpoint_ip/32 + ports: + - protocol: TCP + port: 443 diff --git a/deploy/velero/values.yaml b/deploy/velero/values.yaml index 2e3f7b4..62bf847 100644 --- a/deploy/velero/values.yaml +++ b/deploy/velero/values.yaml @@ -27,6 +27,19 @@ # -f deploy/velero/values.yaml # kubectl apply -f deploy/velero/schedule.yml # +# 5. Sécurité du bucket — à vérifier CÔTÉ RustFS/MinIO (rien de tout ça ne +# se configure depuis ce repo) : chaque sauvegarde contient les Secrets +# K8s en clair (mots de passe postgres/redis, JWT, tokens Telegram...) +# de toutes les démos. +# - Chiffrement at-rest activé sur le bucket/disque côté RustFS/MinIO +# (indépendant du header SSE ci-dessous, qui ne fait que le demander +# au serveur). +# - Clé d'accès dédiée à ce bucket UNIQUEMENT (voir étape 1) — jamais la +# clé d'admin du cluster de stockage. +# - Versioning ou object-lock (WORM) activé si le fournisseur le +# supporte : sans ça, une clé compromise peut aussi supprimer/écraser +# l'historique des sauvegardes, pas seulement les données live. +# # Pourquoi File System Backup (kopia) plutôt que des snapshots CSI Longhorn : # ça fonctionne indépendamment du provisioner de stockage (pas de # VolumeSnapshotClass/CRD supplémentaire à maintenir), et couvre aussi bien @@ -61,23 +74,18 @@ configuration: name: cloud-credentials key: cloud config: - # Région du bucket (AWS) ou région arbitraire acceptée par votre - # fournisseur S3-compatible (souvent "us-east-1" par défaut). - region: "CHANGE_ME_region" - # URL du endpoint S3-compatible — uniquement si ce n'est PAS AWS S3 - # (ex: "https://s3.fr-par.scw.cloud", "https://s3.eu-de.io.cloud.ovh.net"). - # Laisser vide pour AWS S3. - s3Url: "" - # La plupart des fournisseurs S3-compatibles (hors AWS) exigent le - # style "path" plutôt que "virtual-hosted" pour adresser un bucket. + region: "us-east-1" + s3Url: "CHANGE_ME_https://votre-endpoint-rustfs-ou-minio" s3ForcePathStyle: "true" + insecureSkipTLSVerify: "false" + # Chiffrement côté serveur du contenu des sauvegardes (SSE-S3, + # AES256 géré par le bucket) — RustFS et MinIO le supportent tous + # les deux. Ne dispense pas de vérifier le chiffrement at-rest réel + # du stockage sous-jacent (voir étape 5 ci-dessus). + serverSideEncryption: "AES256" - # Pas de VolumeSnapshotLocation : sauvegarde des volumes via File System - # Backup (node-agent + kopia) plutôt que des snapshots CSI natifs. volumeSnapshotLocation: [] snapshotsEnabled: false -# Node-agent (DaemonSet) : nécessaire pour sauvegarder le contenu réel des -# PVC (postgres, redis, uploads) via File System Backup. deployNodeAgent: true