SSD Nodes Learn 🎉 VPS dès $5.50/mois
Guides Matt ConnorPar Matt Connor · Mis à jour le 2026-08-21

Sécuriser un cluster k3s mono-nœud sur un VPS

Découvrez les 5 failles d’une installation k3s par défaut : ports 6443, 10250, kubeconfig, NodePorts et pods privilégiés, puis corrigez-les.

Ce qu’expose un cluster k3s à nœud unique dès le premier jour

Un cluster k3s à nœud unique installé sur un VPS public est exposé à cinq endroits précis dès le lendemain de l’exécution de l’installateur en une ligne : le serveur d’API Kubernetes sur TCP 6443, le kubelet sur TCP 10250, le fichier kubeconfig présent sur le disque, la plage de NodePort que votre firewall ne peut pas détecter, et tout pod autorisé à demander privileged ou hostPath. Chacun de ces points se corrige en quelques minutes. Ce guide part du principe que k3s fonctionne déjà. Si ce n’est pas le cas, commencez par installer k3s sur un VPS avec un nœud unique, puis revenez ici.

Vérifiez ce qui est en écoute avant de modifier quoi que ce soit.

sudo ss -tulpn | grep -E '6443|10250|10256|8472'

Une installation par défaut expose 6443 (le serveur d’API), 10250 (le kubelet), 10256 (le health check de kube-proxy) et 8472/udp (l’overlay Flannel, qui utilise VXLAN, virtual extensible LAN). k3s se lie par défaut à 0.0.0.0. Chacun de ces services est donc accessible sur votre adresse publique, et pas uniquement sur loopback.

Pourquoi le port 6443 donne accès à tout le cluster

Tout ce qui peut s’authentifier sur le port 6443 avec des droits d’administration peut créer un pod, et un pod peut devenir root sur l’hôte. Le port 6443 donne accès à la machine.

Un port 6443 ouvert ne signifie pas que la machine sera immédiatement compromise, car Kubernetes n’accepte pas les mots de passe. Il exige un certificat client ou un bearer token. Deux points restent toutefois vrais.

Premièrement, l’API server répond à certaines requêtes sans aucun identifiant. Le RBAC (role-based access control) par défaut de Kubernetes associe le groupe system:unauthenticated à un rôle nommé system:public-info-viewer, qui autorise /version, /healthz, /livez et /readyz. Depuis une autre machine :

curl -sk https://YOUR_SERVER_IP:6443/version

Cette commande renvoie la version exacte de Kubernetes. Elle sert de donnée d’entrée pour rechercher les CVE (common vulnerabilities and exposures) et explique pourquoi un scanner considère votre serveur comme intéressant. Tout accès à des chemins supplémentaires est refusé, et le refus vous identifie :

forbidden: User "system:anonymous" cannot get path "/api"

Deuxièmement, toute vulnérabilité de l’API server est accessible à distance tant que ce port est ouvert. Les mises à jour de sécurité deviennent alors obligatoires.

La correction la plus simple consiste à ajouter une règle de firewall. Avec ufw, il faut plus d’une ligne, car k3s fait transiter le trafic du cluster par le même kernel.

sudo ufw default deny incoming
sudo ufw allow OpenSSH
sudo ufw allow from 203.0.113.10 to any port 6443 proto tcp
sudo ufw allow from 10.42.0.0/16 to any
sudo ufw allow from 10.43.0.0/16 to any
sudo ufw enable

Les deux dernières règles viennent directement de la documentation de k3s. 10.42.0.0/16 est le réseau par défaut des pods et 10.43.0.0/16 le réseau par défaut des services. Sans ces règles, ufw bloque le trafic interne au cluster, et les pods ne peuvent plus joindre l’API server ni communiquer entre eux. L’exemple k3s autorise le port 6443 depuis toutes les adresses. Le remplacer par votre propre adresse est la modification la plus importante. Si ufw vous est peu familier, les bases du firewall ufw pour un VPS présentent les politiques par défaut dont cette configuration dépend.

La documentation de k3s est claire au sujet du port overlay : « Le port VXLAN des nœuds ne doit pas être exposé à Internet, car cela permet à n’importe qui d’accéder au réseau de votre cluster. » Une politique par défaut qui refuse le trafic entrant règle ce problème sans avoir à nommer le port.

La correction la plus robuste consiste à ne plus accéder à l’API via l’adresse publique et à utiliser à la place une adresse VPN ou mesh. Le certificat du serveur doit contenir l’adresse à laquelle vous vous connectez. Ajoutez-la donc comme SAN (subject alternative name) dans /etc/rancher/k3s/config.yaml :

tls-san:
  - 10.8.0.1
  - k3s.example.com
secrets-encryption: true
sudo systemctl restart k3s
sudo k3s secrets-encrypt status

secrets-encryption: true chiffre les objets Secret dans le datastore. La documentation de k3s précise que « le chiffrement des Secrets ne peut pas être activé sur un serveur existant sans le redémarrer », et que les Secrets écrits avant cette modification conservent leur ancien format jusqu’à l’exécution de sudo k3s secrets-encrypt reencrypt. Il faut bien comprendre ce que cette mesure apporte. Elle protège un fichier de datastore copié depuis une sauvegarde. Elle ne protège pas contre une personne capable de communiquer avec l’API server, car celui-ci déchiffre les Secrets pour les utilisateurs autorisés à les lire. La même distinction s’applique à tout secret store auto-hébergé. C’est pourquoi un audit de durcissement de Vaultwarden se concentre sur l’admin token et le fichier de sauvegarde, plutôt que sur le chiffrement lui-même.

Pourquoi le kubelet sur le port 10250 est important

Le kubelet est l’agent qui démarre les conteneurs. Son API sur le port 10250 liste les pods et exécute des commandes à l’intérieur. Un kubelet qui accepte les requêtes anonymes fournit un shell distant sur chaque workload de la machine.

Vérifiez le vôtre :

curl -sk https://127.0.0.1:10250/pods | head -c 60

Un k3s récent répond Unauthorized, car le kubelet demande au serveur d’API d’authentifier et d’autoriser chaque appelant. Si une liste de pods au format JSON est renvoyée à la place, l’accès anonyme est activé. Toute personne pouvant atteindre le port 10250 peut alors lire vos conteneurs et y exécuter des commandes.

Fermez également ce port depuis l’extérieur. Sur un nœud unique, le seul client du kubelet est le control plane sur la même machine. Ce trafic passe par l’interface loopback, que ufw accepte par défaut. Refuser le port 10250 depuis Internet ne vous coûte rien. Si vous êtes arrivé ici depuis un kubectl top défaillant ou un metrics-server qui ne se stabilise pas, les causes sont répertoriées dans erreurs du port 10250 du kubelet.

Votre kubeconfig est un identifiant d’administrateur du cluster

k3s écrit /etc/rancher/k3s/k3s.yaml avec root comme propriétaire et avec le mode 600. La documentation précise la conséquence d’une modification : « Le fichier kubeconfig appartient à root et est écrit avec le mode 600 par défaut. Modifier le mode à 644 permet aux autres utilisateurs non privilégiés de l’hôte de le lire. »

Interprétez cela ainsi : le mode 644 transforme chaque compte local en administrateur du cluster. De nombreux guides recommandent précisément cette modification, généralement avec --write-kubeconfig-mode 644, afin que kubectl fonctionne sans sudo. Cela fonctionne en transmettant l’identifiant d’administration à toute personne disposant d’un shell.

Copiez plutôt le fichier pour un seul utilisateur.

mkdir -p ~/.kube
sudo install -o "$USER" -g "$USER" -m 600 /etc/rancher/k3s/k3s.yaml ~/.kube/config
kubectl get nodes

Vérifiez ensuite que les permissions du fichier d’origine sont toujours restrictives :

stat -c '%a %U:%G' /etc/rancher/k3s/k3s.yaml

600 root:root est le résultat attendu. Ce fichier contient un certificat client pour un membre de system:masters, le groupe que l’API server considère comme autorisé sans condition. Les règles RBAC ne sont donc jamais consultées pour ce certificat. Kubernetes ne possède pas de liste de révocation de certificats. Une copie divulguée reste donc valide jusqu’à la rotation de l’autorité de certification du cluster. Traitez ce fichier comme une clé privée SSH et limitez le nombre de comptes pouvant y accéder. C’est le même principe que pour les comptes utilisateur appliquant le principe du moindre privilège sur un VPS.

Votre pare-feu ne voit pas le trafic NodePort

Un service type: NodePort ouvre un port compris entre 30000 et 32767 sur chaque adresse détenue par le nœud, y compris l’adresse publique. Un service type: LoadBalancer va plus loin sur k3s : ServiceLB, le load balancer intégré, planifie un petit pod par service dans kube-system. Ce pod réserve directement le port du service sur l’hôte.

kubectl -n kube-system get pods | grep svclb
sudo ss -tulpn | grep -E ':3[0-2][0-9]{3}'

Voici la partie qui surprend. Bloquez ce port avec ufw : il continue de répondre.

sudo ufw deny 30080/tcp
curl http://YOUR_SERVER_IP:30080

La page se charge toujours, en raison du chemin suivi par le paquet. kube-proxy écrit des règles DNAT (destination network address translation) dans la chaîne PREROUTING de la table nat, et PREROUTING s’exécute avant toute décision de filtrage. La destination devient l’adresse d’un pod, qui n’est pas l’hôte. Le noyau envoie donc le paquet dans la chaîne FORWARD, sans jamais passer par INPUT. Les règles de ufw se trouvent dans INPUT. Le paquet ne les rencontre jamais. L’ordre des sauts est visible ici :

sudo iptables -S PREROUTING -t nat | head
sudo iptables -S FORWARD | head

KUBE-SERVICES se trouve en tête de PREROUTING, et les sauts Kubernetes dans FORWARD passent avant les chaînes propres à ufw. C’est le même mécanisme qui permet à Docker de publier des ports en contournant ufw, et les solutions sont les mêmes.

  • Filtrez au niveau du pare-feu réseau de votre fournisseur. Il s’exécute devant la machine et ne dépend pas du routage effectué par votre noyau.
  • N’utilisez ni NodePort ni LoadBalancer. Laissez les services sur ClusterIP et atteignez-les avec kubectl port-forward via la session SSH déjà ouverte.
  • Exposez un seul ingress sur 80 et 443, et rien d’autre.
  • Réduisez la plage avec service-node-port-range sous kube-apiserver-arg, afin qu’un NodePort créé par erreur utilise un port que vous surveillez.

ufw reste utile. Il contrôle le trafic destiné directement à l’hôte, notamment celui de SSH et du serveur d’API. Il ne filtre simplement pas le trafic destiné aux pods. Compter sur lui pour le faire peut rendre une base de données accessible.

Un pod avec hostPath ou privileged est root sur votre VPS

Les conteneurs sont des processus ordinaires exécutés sur votre kernel, avec une vue restreinte de celui-ci. Plusieurs champs de pod suppriment cette restriction.

  • securityContext.privileged: true donne au conteneur toutes les capabilities Linux et l’accès aux périphériques de l’hôte.
  • hostPath monte un répertoire de l’hôte dans le pod. Un pod qui monte / en lecture-écriture peut ajouter une clé à /root/.ssh/authorized_keys.
  • hostPID: true place le conteneur dans le namespace des processus de l’hôte. Une commande nsenter visant le PID 1 ouvre alors un shell sur l’hôte.
  • hostNetwork: true place le conteneur sur la pile réseau de l’hôte. Il peut alors utiliser les ports de l’hôte et atteindre les services liés à loopback.

La question « qui peut créer des pods ici ? » revient donc à demander « qui est root sur ce VPS ? ». Tout ServiceAccount disposant de create sur les pods d’un namespace quelconque est équivalent à root, sauf si un mécanisme rejette d’abord le pod.

Ce mécanisme est l’admission Pod Security, intégrée à l’API server. La configuration rapide consiste à ajouter un label par namespace. Aucun redémarrage n’est nécessaire.

kubectl label namespace default \
  pod-security.kubernetes.io/enforce=baseline \
  pod-security.kubernetes.io/enforce-version=latest \
  pod-security.kubernetes.io/warn=restricted

baseline rejette les quatre champs ci-dessus. restricted va plus loin et impose un utilisateur non root, un profil seccomp (secure computing mode), l’absence d’escalade de privilèges et des capabilities réduites à ALL, ce qui rend de nombreux charts publiés inutilisables. Activer l’application de baseline tout en générant seulement des avertissements pour restricted permet de voir ce qui fonctionnerait avant de l’imposer.

Vérifiez que cela fonctionne :

kubectl apply -f - <<'EOF'
apiVersion: v1
kind: Pod
metadata:
  name: pstest
spec:
  containers:
  - name: app
    image: busybox
    command: ["sleep", "60"]
    securityContext:
      privileged: true
EOF

L’API server le refuse et indique le champ rejeté :

Error from server (Forbidden): error when creating "STDIN": pods "pstest" is forbidden: violates PodSecurity "baseline:latest": privileged (container "app" must not set securityContext.privileged=true)

Pour définir une valeur par défaut à l’échelle du cluster plutôt que d’ajouter un label à chaque namespace, k3s documente un fichier de configuration de l’admission à l’emplacement /var/lib/rancher/k3s/server/psa.yaml :

apiVersion: apiserver.config.k8s.io/v1
kind: AdmissionConfiguration
plugins:
- name: PodSecurity
  configuration:
    apiVersion: pod-security.admission.config.k8s.io/v1beta1
    kind: PodSecurityConfiguration
    defaults:
      enforce: "baseline"
      enforce-version: "latest"
      warn: "restricted"
      warn-version: "latest"
    exemptions:
      namespaces: [kube-system]

Indiquez ce fichier à l’API server dans /etc/rancher/k3s/config.yaml, puis redémarrez k3s :

kube-apiserver-arg:
  - 'admission-control-config-file=/var/lib/rancher/k3s/server/psa.yaml'

L’exemption kube-system est obligatoire. Les pods ServiceLB de k3s utilisent des ports de l’hôte, ce que baseline interdit. Si vous omettez kube-system de la liste, ces pods seront rejetés lors de leur prochaine recréation. Gardez une deuxième session SSH ouverte lorsque vous redémarrez k3s après une modification de l’admission.

Profitez-en également pour noter que k3s fournit un contrôleur de network policy et l’active par défaut. Les objets NetworkPolicy prennent donc effet sur ce cluster sans installation supplémentaire. Ce n’est pas le cas de toutes les distributions Kubernetes. C’est l’outil qui empêche un pod compromis d’atteindre le reste du cluster.

Supprimez les composants inclus que vous n’utilisez pas

L’installateur déploie plusieurs modules complémentaires. Chacun ajoute un listener et un élément supplémentaire à mettre à jour. --disable accepte les valeurs suivantes : coredns, servicelb, traefik, local-storage, metrics-server, runtimes.

Conservez coredns. Aucun élément du cluster ne peut résoudre un nom sans lui. Les autres sont facultatifs. Dans /etc/rancher/k3s/config.yaml :

disable:
  - traefik
  - servicelb
disable-helm-controller: true
sudo systemctl restart k3s
kubectl get pods -A

k3s supprime les composants que vous désactivez. Les pods traefik et les pods svclb- disparaissent donc automatiquement. Prenez d’abord connaissance des conséquences. Si servicelb est supprimé, chaque service type: LoadBalancer reste définitivement à <pending>, car rien ne lui attribue d’adresse. Si traefik est supprimé, il n’y a plus de contrôleur d’ingress et les objets Ingress ne font plus rien. Désactivez ces composants lorsque vous acheminez le trafic autrement, par exemple avec un reverse proxy sur l’hôte. Laissez-les activés lorsque vous les utilisez. disable-helm-controller: true supprime le contrôleur qui surveille les ressources HelmChart. Il s’agit d’un composant privilégié dont vous n’avez pas besoin si vous exécutez vous-même helm.

Désactiver le montage automatique du token ServiceAccount par défaut

Chaque pod reçoit un token ServiceAccount dans /var/run/secrets/kubernetes.io/serviceaccount/token, sauf indication contraire. Le ServiceAccount default ne possède aucune permission RBAC. Le token seul ne permet donc pas de faire grand-chose. En revanche, il fournit à un attaquant présent dans un conteneur compromis un identifiant valide et un serveur API accessible. C’est la première étape de la plupart des scénarios d’escalade de privilèges dans un cluster.

Le guide de sécurisation de k3s désactive ce comportement par namespace :

kubectl patch serviceaccount --namespace default default --patch '{"automountServiceAccountToken": false}'
kubectl patch serviceaccount --namespace kube-node-lease default --patch '{"automountServiceAccountToken": false}'
kubectl patch serviceaccount --namespace kube-public default --patch '{"automountServiceAccountToken": false}'

Vérifiez que la modification a été appliquée. Attendez quelques secondes que le pod démarre.

kubectl run t --image=busybox --restart=Never --command -- sleep 30
kubectl exec t -- ls /var/run/secrets/kubernetes.io/serviceaccount

Le chemin a disparu. ls affiche donc No such file or directory. Une charge de travail qui a réellement besoin d’accéder à l’API définit automountServiceAccountToken: true dans sa propre spécification de pod. Aucun accès n’est donc bloqué définitivement. C’est déjà un gain limité. Le gain le plus important consiste à ne pas attribuer à une charge de travail un ServiceAccount disposant de permissions réelles. Vous pouvez vérifier les droits actuellement associés à un token :

kubectl auth can-i --list --as=system:serviceaccount:default:default

Les limites de ressources, pour qu’un pod ne puisse pas arrêter le cluster

Sur un nœud unique, le control plane et vos workloads partagent le même kernel et le même pool de mémoire. Un pod qui fuit de la mémoire ne s’arrête pas toujours seul. Le tueur OOM (out of memory) du kernel choisit sa victime selon un score qui favorise les processus volumineux. k3s est un processus volumineux qui s’exécute longtemps. C’est donc parfois le cluster qui disparaît au lieu du pod. Plus rien ne redémarre alors vos workloads, car le composant qui redémarre les workloads est lui-même arrêté.

Un LimitRange définit des limites pour les pods qui n’en indiquent aucune :

apiVersion: v1
kind: LimitRange
metadata:
  name: defaults
  namespace: default
spec:
  limits:
  - type: Container
    default:
      cpu: 500m
      memory: 512Mi
    defaultRequest:
      cpu: 50m
      memory: 128Mi

Un ResourceQuota plafonne les ressources que l’ensemble du namespace peut demander :

apiVersion: v1
kind: ResourceQuota
metadata:
  name: cap
  namespace: default
spec:
  hard:
    limits.cpu: "3"
    limits.memory: 3Gi
    pods: "20"

Réservez ensuite de la mémoire pour k3s dans /etc/rancher/k3s/config.yaml :

kubelet-arg:
  - 'system-reserved=cpu=250m,memory=512Mi'

La différence apparaît à deux endroits. Un container arrêté parce qu’il a dépassé sa propre limite signale Reason: OOMKilled sous Last State dans kubectl describe pod, et le reste du nœud continue de fonctionner. Un nœud qui a complètement épuisé sa mémoire laisse une ligne Killed process dans dmesg et entraîne généralement l’arrêt des nœuds voisins. Le premier cas montre que votre limite fonctionne. Le second est précisément ce que les limites doivent empêcher.

Analyser les manifests dans la CI et le cluster k3s selon un calendrier

L’analyse doit être effectuée à deux endroits, car ces contrôles détectent des problèmes différents. Installez d’abord Trivy. Le projet fournit un paquet Debian avec chaque version, et 0.74.0 était la version courante en août 2026. Consultez donc la page des versions pour vérifier qu’une version plus récente n’est pas disponible avant de figer cette version dans l’automatisation.

sudo apt-get install -y wget
wget -q https://github.com/aquasecurity/trivy/releases/download/v0.74.0/trivy_0.74.0_Linux-64bit.deb
sudo dpkg -i trivy_0.74.0_Linux-64bit.deb
trivy --version

Le premier endroit est celui de vos manifests, avant leur déploiement dans le cluster.

trivy fs --scanners misconfig --severity HIGH,CRITICAL --exit-code 1 ./deploy

--exit-code 1 fait échouer le job de CI (intégration continue) lorsqu’une vulnérabilité est détectée. Chaque résultat indique le champ en cause et sa gravité. Un privileged: true que vous n’aviez pas l’intention de valider fait donc échouer le build au lieu d’atteindre le serveur API. Une vulnérabilité que vous avez décidé d’accepter doit être ajoutée à un fichier .trivyignore. Cette décision reste ainsi dans git, à côté du manifest qui l’a motivée.

Le second endroit est le cluster en fonctionnement, selon un calendrier.

trivy k8s --compliance=k8s-cis-1.23 --report summary

Une précision importante concernant cette commande. trivy k8s déploie un pod collecteur sur un nœud, qui nécessite un accès à l’hôte pour inspecter les paramètres de niveau nœud. Sur un cluster où vous venez de commencer à refuser les pods privilégiés, ce point mérite d’être pris en compte, et non contourné. trivy k8s --report summary --disable-node-collector désactive le collecteur et supprime également les contrôles de niveau nœud.

kube-bench couvre la partie hôte : permissions des fichiers et options des processus. C’est là que se trouve l’essentiel du benchmark CIS (Center for Internet Security). Le projet fournit un profil k3s. Reprenez le manifest Job fourni en amont et adaptez-le.

curl -sfL -o kube-bench-job.yaml https://raw.githubusercontent.com/aquasecurity/kube-bench/main/job.yaml

Modifiez la commande du conteneur pour utiliser ["kube-bench", "--benchmark", "k3s-cis-1.7"], et remplacez les montages /etc/kubernetes et /var/lib/etcd par /etc/rancher et /var/lib/rancher, car c’est à cet emplacement que k3s conserve ses fichiers. Ensuite :

kubectl apply -f kube-bench-job.yaml
kubectl logs -l app=kube-bench --tail=-1

Notez ce qu’est réellement ce Job : hostPID: true avec des montages de répertoires de l’hôte. C’est exactement la structure de pod que vous avez commencé à refuser dans la section précédente. Exécutez-le dans l’espace de noms exempté kube-system, consultez la sortie, puis kubectl delete job kube-bench. Le fait qu’un scanner doive être privilégié ne justifie pas l’arrêt de l’interdiction des workloads privilégiés.

Le contenu des images est une question distincte. trivy image ghcr.io/example/app:1.4 lit la base de données des paquets à l’intérieur d’une image et répertorie les paquets connus comme vulnérables. C’est le même contrôle que lorsque vous recherchez les CVE connues sur votre serveur.

Venons-en maintenant à la sortie du scanner. Un cluster de loisir à un seul nœud échouera à une longue liste de contrôles CIS. La plupart de ces échecs sont à la fois corrects et sans rapport avec votre situation. Le benchmark est conçu pour un cluster multi-nœud et multi-tenant : etcd sur des hôtes distincts, journaux d’audit envoyés hors de la machine, autorité de certification distincte pour les certificats kubelet et plugins d’admission correspondant à un régime de conformité auquel vous n’êtes pas soumis. k3s exécute délibérément le control plane comme un processus unique avec un seul fichier de configuration. Les contrôles qui vérifient les permissions d’un fichier manifest kube-scheduler ne peuvent donc pas réussir, car ce fichier n’existe pas.

Lisez les échecs dans cet ordre et arrêtez-vous lorsque leur valeur devient insuffisante : modes et propriétaires des fichiers sous /etc/rancher et /var/lib/rancher, tout ce qui mentionne un accès anonyme ou non authentifié, tout résultat signalant un composant lié à 0.0.0.0, puis tout conteneur exécuté avec l’UID 0 sans raison valable. Le reste peut attendre l’ajout d’un deuxième nœud ou l’intervention d’une deuxième personne disposant des accès nécessaires. Un rapport de cent lignes que vous ignorez vaut moins qu’un rapport de cinq lignes sur lequel vous agissez.

Ordre de durcissement de k3s pour un nœud unique

  1. Définissez la policy entrante par défaut d’ufw sur deny, autorisez SSH, autorisez 6443 depuis votre propre adresse, puis autorisez les réseaux des pods et des services.
  2. Copiez le kubeconfig dans votre compte utilisateur avec le mode 600 et ne définissez jamais --write-kubeconfig-mode 644.
  3. Vérifiez que le kubelet sur 10250 refuse les requêtes anonymes et laissez ce port inaccessible depuis Internet.
  4. Désactivez les composants fournis que vous n’utilisez pas, puis redémarrez k3s.
  5. Étiquetez vos namespaces pour l’admission Pod Security avec enforce=baseline et warn=restricted.
  6. Désactivez l’automountage par défaut des tokens des ServiceAccount.
  7. Ajoutez une LimitRange et une ResourceQuota, puis réservez du CPU et de la mémoire pour k3s.
  8. Intégrez trivy fs --scanners misconfig à la CI et exécutez un scan CIS chaque mois.

L’hôte sous-jacent doit toujours être administré comme n’importe quel autre serveur, avec une particularité liée à k3s. k3s a été installé par un script et non par apt ; apt upgrade ne le met donc jamais à jour. Maintenez le système d’exploitation à jour selon son propre calendrier avec les mises à jour unattended sur Ubuntu, puis mettez k3s à niveau volontairement en relançant son installateur avec le channel ou la version souhaité. Deux mécanismes de mise à jour sur la même machine sont faciles à oublier. Notez donc quel composant suit quel mécanisme.

FAQ

Est-il sûr d’exposer le serveur d’API k3s sur le port 6443 sur Internet ?

Cela ne constitue pas à lui seul une porte ouverte, car le serveur d’API exige un certificat client ou un token et refuse toute autre requête avec forbidden: User "system:anonymous". Deux risques subsistent. Les appelants anonymes peuvent toujours lire /version, qui indique précisément à un scanner quelle release de Kubernetes rechercher. De plus, toute future vulnérabilité du serveur d’API devient accessible à distance tant que le port reste ouvert. Dans un cluster à un seul nœud, rien d’extérieur n’a besoin du port 6443, sauf votre propre kubectl. Autorisez donc les connexions depuis votre adresse avec sudo ufw allow from YOUR_IP to any port 6443 proto tcp et laissez la policy de refus par défaut gérer le reste.

Pourquoi ma règle ufw ne bloque-t-elle pas un service NodePort ?

Parce que le paquet n’atteint jamais la chain où se trouve votre règle. kube-proxy place les règles DNAT dans la table nat, dans la chain PREROUTING. Celle-ci s’exécute en premier et réécrit la destination vers l’adresse d’un pod. Le paquet est ensuite forwardé au lieu d’être livré localement. Il traverse donc FORWARD et ignore INPUT, où se trouvent les règles de ufw. Confirmez-le avec sudo iptables -S PREROUTING -t nat | head. Filtrez les NodePorts dans le firewall réseau de votre fournisseur, ou évitez type: NodePort et accédez plutôt aux services avec kubectl port-forward.

Dois-je exécuter k3s avec --write-kubeconfig-mode 644 ?

Non. La documentation de k3s précise son effet : « Changing the mode to 644 will allow it to be read by other unprivileged users on the host. » Ce fichier contient un certificat client pour system:masters. Avec le mode 644, chaque compte local devient donc administrateur du cluster. Copiez-le plutôt pour un seul utilisateur avec sudo install -o "$USER" -g "$USER" -m 600 /etc/rancher/k3s/k3s.yaml ~/.kube/config et laissez l’original en 600 root:root.

Quel benchmark kube-bench dois-je utiliser pour k3s ?

Utilisez k3s-cis-1.7. La documentation de kube-bench indique : « kube-bench includes benchmarks for Rancher K3S platform. To run this you will need to specify --benchmark k3s-cis-1.7 when you run the kube-bench command. » Indiquez-le explicitement, car l’auto-détection suppose une architecture kubeadm, tandis que k3s conserve ses fichiers sous /etc/rancher et /var/lib/rancher. Attendez-vous à des échecs qui ne s’appliquent pas à un nœud unique. Traitez en priorité les permissions des fichiers et l’accès anonyme.

Pod Security admission interrompra-t-il les composants k3s fournis par défaut ?

Oui, si vous l’activez sur kube-system. Les pods ServiceLB que k3s crée pour les services type: LoadBalancer réservent des ports sur l’hôte. Or baseline interdit les ports de l’hôte. Ces pods seront donc rejetés lors de leur prochaine recréation. Exemptez kube-system dans le fichier de configuration de l’admission, ou appliquez Pod Security uniquement avec des labels sur les namespaces dont vous êtes responsable. Commencez par enforce=baseline et warn=restricted afin de voir ce que restricted interromprait avant de l’activer.