SSD Nodes Learn 🎉 VPS desde $5.50/mês
Guias Matt ConnorPor Matt Connor · Atualizado 2026-08-21

Como proteger um cluster k3s de nó único em VPS

Veja o que o k3s deixa exposto: TCP 6443, kubelet em 10250, kubeconfig, NodePorts fora da firewall e pods privilegiados, com correções práticas.

O que um cluster k3s de nó único expõe no primeiro dia

Um cluster k3s de nó único num VPS público fica exposto em cinco pontos específicos depois de concluído o instalador de uma linha: o servidor da API do Kubernetes em TCP 6443, o kubelet em TCP 10250, o ficheiro kubeconfig armazenado no disco, o intervalo de NodePort que a firewall não consegue detetar e qualquer pod autorizado a solicitar privileged ou hostPath. Cada ponto tem uma correção que demora alguns minutos. Este guia assume que o k3s já está em execução. Caso contrário, comece por instalar um k3s de nó único num VPS e volte depois.

Veja o que está à escuta antes de alterar qualquer coisa.

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

Uma instalação predefinida mostra 6443 (o servidor da API), 10250 (o kubelet), 10256 (a verificação de integridade do kube-proxy) e 8472/udp (a rede overlay do flannel, que usa VXLAN, virtual extensible LAN). Por predefinição, o k3s associa-se a 0.0.0.0. Por isso, todos esses serviços ficam no seu endereço público, e não apenas no loopback.

Por que a porta 6443 dá acesso ao cluster inteiro

Tudo o que conseguir autenticar na porta 6443 com direitos de administrador pode criar um pod, e um pod pode obter root no host. A porta 6443 dá acesso à máquina.

Uma porta 6443 exposta não implica um comprometimento imediato, porque o Kubernetes não aceita palavras-passe. É necessário um certificado de cliente ou um bearer token. Ainda assim, há dois factos importantes.

Primeiro, o servidor da API responde a alguns pedidos sem qualquer credencial. O RBAC (role-based access control) predefinido do Kubernetes associa o grupo system:unauthenticated a uma função chamada system:public-info-viewer, que permite /version, /healthz, /livez e /readyz. A partir de outra máquina:

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

Isto devolve a versão exata do Kubernetes, que serve de entrada para uma pesquisa de CVE (common vulnerabilities and exposures) e explica por que razão um scanner considera o seu servidor interessante. Todos os caminhos além desses são recusados, e a resposta identifica-o:

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

Segundo, qualquer erro no servidor da API fica acessível remotamente enquanto essa porta estiver aberta. A partir desse momento, aplicar atualizações deixa de ser opcional.

A correção mais simples é uma regra de firewall. O ufw precisa de mais do que uma linha neste caso, porque o k3s encaminha o tráfego do cluster através do mesmo 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

As duas últimas regras vêm diretamente da documentação do k3s. 10.42.0.0/16 é a rede predefinida dos pods e 10.43.0.0/16 é a rede predefinida dos serviços. Sem essas regras, o ufw descarta o tráfego interno do cluster, e os pods perdem o acesso ao servidor da API e uns aos outros. O exemplo do k3s permite a porta 6443 a partir de qualquer origem; substituí-la pelo seu próprio endereço é a alteração que vale a pena fazer. Se ainda não conhece o ufw, os fundamentos do firewall ufw para um VPS explicam as políticas predefinidas das quais isto depende.

A documentação do k3s é direta sobre a porta de overlay: "A porta VXLAN nos nós não deve ser exposta à Internet, porque isso permite que qualquer pessoa aceda à rede do seu cluster." Uma política predefinida de negação do tráfego de entrada resolve isto sem precisar de indicar a porta.

A correção mais robusta é deixar de aceder à API através do endereço público e usar um endereço de VPN ou de uma rede mesh. O certificado do servidor tem de incluir o endereço ao qual se liga. Adicione-o como SAN (subject alternative name) em /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 cifra os objetos Secret no datastore. A documentação do k3s indica que "Secrets-encryption cannot be enabled on an existing server without restarting it", e os Secrets gravados antes da alteração mantêm o formato antigo até executar sudo k3s secrets-encrypt reencrypt. É importante entender o que isto protege. A medida protege um ficheiro do datastore copiado de uma cópia de segurança. Não protege contra alguém que consiga comunicar com o servidor da API, porque o servidor da API desencripta os Secrets para quem tiver autorização para os ler. A mesma distinção aplica-se a qualquer armazenamento de secrets autoalojado. Por isso, uma revisão de segurança do Vaultwarden concentra-se no token de administrador e no ficheiro de cópia de segurança, e não na encriptação em si.

Por que o kubelet na porta 10250 é importante

O kubelet é o agente que inicia os containers. A API na porta 10250 lista pods e executa comandos dentro deles. Um kubelet que aceita pedidos anónimos fornece um shell remoto para todas as cargas de trabalho na máquina.

Verifique o seu:

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

Um k3s atual responde Unauthorized, porque o kubelet pede ao API server para autenticar e autorizar cada cliente. Se for devolvida uma lista de pods em JSON, o acesso anónimo está ativo e qualquer pessoa que consiga alcançar a porta 10250 pode ler dados e executar comandos nos seus containers.

Bloqueie-a externamente de qualquer forma. Num único nó, o único cliente do kubelet é o control plane no mesmo sistema, e esse tráfego entra pela interface de loopback, que o ufw aceita por predefinição. Negar a porta 10250 a partir da Internet não tem qualquer custo. Se chegou aqui a partir de um kubectl top com problemas ou de um metrics-server que não estabiliza, as causas estão reunidas em erros da porta 10250 do kubelet.

Seu kubeconfig é uma credencial de administrador do cluster

O k3s grava /etc/rancher/k3s/k3s.yaml como propriedade de root e com o modo 600. A documentação explica a consequência de alterar esse modo: "O ficheiro kubeconfig pertence a root e é gravado, por predefinição, com o modo 600. Alterar o modo para 644 permite que outros utilizadores sem privilégios no host o leiam."

Interprete isso da seguinte forma: o modo 644 transforma todas as contas locais em administradores do cluster. Muitos tutoriais sugerem exatamente essa alteração, normalmente com --write-kubeconfig-mode 644, para que kubectl funcione sem sudo. Isto funciona porque entrega a credencial de administrador a qualquer pessoa com acesso a uma shell.

Copie o ficheiro apenas para um utilizador.

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

Depois confirme que o original continua protegido:

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

600 root:root é o resultado esperado. Esse ficheiro contém um certificado de cliente de um membro de system:masters, o grupo que o API server considera sempre autorizado. Por isso, as regras RBAC nunca são consultadas para esse certificado. O Kubernetes não tem uma lista de revogação de certificados. Uma cópia exposta continua válida até alterar a autoridade de certificação do cluster. Trate esse ficheiro como uma chave privada SSH e mantenha pequena a lista de contas que podem aceder-lhe. O princípio é o mesmo que em contas de utilizador com privilégios mínimos num VPS.

O firewall não deteta tráfego NodePort

Um serviço type: NodePort abre uma porta entre 30000 e 32767 em todos os endereços atribuídos ao nó, incluindo o endereço público. Um serviço type: LoadBalancer vai mais longe no k3s: o ServiceLB, o balanceador de carga incluído, agenda um pod pequeno por serviço em kube-system, que reserva diretamente a porta do serviço no host.

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

Agora vem a parte que surpreende muitas pessoas. Bloqueie essa porta com ufw e ela continua a responder.

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

A página continua a carregar por causa do percurso do pacote. O kube-proxy escreve regras DNAT (tradução do endereço de rede de destino) na cadeia PREROUTING da tabela nat, e PREROUTING é executada antes de qualquer decisão de filtragem. O destino passa a ser o endereço de um pod, que não pertence ao host. Por isso, o kernel envia o pacote pela cadeia FORWARD e nunca pela cadeia INPUT. As regras do ufw ficam em INPUT. O pacote nunca as encontra. A ordem dos saltos é visível:

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

KUBE-SERVICES fica no topo de PREROUTING, e os saltos do Kubernetes em FORWARD ficam acima das próprias cadeias do ufw. Este é o mesmo mecanismo que permite ao Docker publicar portas para além do ufw, e as respostas são as mesmas.

  • Filtre no firewall de rede do seu fornecedor. Ele é executado à frente da máquina e não depende da forma como o kernel encaminha o tráfego.
  • Não use NodePort nem LoadBalancer. Mantenha os serviços em ClusterIP e aceda-lhes com kubectl port-forward através da sessão SSH que já tem.
  • Exponha um único ingress nas portas 80 e 443, e nada mais.
  • Restrinja o intervalo com service-node-port-range em kube-apiserver-arg, para que um NodePort acidental fique num intervalo que esteja a monitorizar.

Continua a valer a pena executar o ufw. Ele gere o tráfego destinado ao próprio host, como o SSH e o servidor da API. Simplesmente não controla o tráfego dos pods, e esperar que o faça é a forma de acabar com uma base de dados acessível.

Um pod com hostPath ou privileged tem acesso equivalente a root no seu VPS

Os contentores são processos normais no seu kernel, com uma visão restrita dele. Vários campos dos pods removem essa restrição.

  • securityContext.privileged: true fornece ao contentor todas as capacidades do Linux e acesso aos dispositivos do host.
  • hostPath monta um diretório do host no pod. Um pod que monte / com acesso de escrita pode acrescentar uma chave a /root/.ssh/authorized_keys.
  • hostPID: true coloca o contentor no namespace de processos do host, onde nsenter contra o PID 1 abre uma shell no host.
  • hostNetwork: true coloca-o na pilha de rede do host, onde pode associar portas do host e alcançar serviços associados ao loopback.

Por isso, a pergunta "quem pode criar pods aqui" é equivalente à pergunta "quem é root neste VPS". Qualquer ServiceAccount com create em pods de qualquer namespace é equivalente a root, a menos que algo rejeite o pod primeiro.

Esse mecanismo é a admissão Pod Security, integrada no servidor da API. A configuração rápida usa uma etiqueta por namespace e não requer reinício.

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

baseline rejeita todos os quatro campos acima. restricted vai mais longe e exige um utilizador que não seja root, um perfil seccomp (secure computing mode), nenhuma escalada de privilégios e capacidades removidas até ALL, o que interrompe muitos charts publicados. Aplicar baseline e apenas emitir avisos sobre restricted permite identificar o que falharia antes de ativar a política.

Confirme que funciona:

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

O servidor da API recusa o pod e indica o campo que recusou:

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)

Para usar um valor predefinido a nível de todo o cluster, em vez de uma etiqueta em cada namespace, o k3s documenta um ficheiro de configuração de admissão em /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]

Indique esse ficheiro ao servidor da API em /etc/rancher/k3s/config.yaml e reinicie o k3s:

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

A isenção kube-system é obrigatória. Os próprios pods ServiceLB do k3s usam portas do host, o que baseline proíbe. Se deixar kube-system fora da lista, esses pods serão rejeitados na próxima vez que algum componente os recriar. Mantenha uma segunda sessão SSH aberta quando reiniciar o k3s depois de alterar a configuração de admissão.

Há ainda uma vantagem adicional: o k3s inclui um controlador de políticas de rede e ativa-o por predefinição. Por isso, os objetos NetworkPolicy entram em vigor neste cluster sem instalar mais nada. Isto não acontece em todas as distribuições Kubernetes. Essa é a ferramenta usada para impedir que um pod comprometido alcance o resto da rede.

Remova os componentes incluídos que não utiliza

O instalador implementa um conjunto de complementos. Cada um adiciona um listener e mais um componente para atualizar. --disable aceita estes valores: coredns, servicelb, traefik, local-storage, metrics-server, runtimes.

Mantenha coredns. Nada no cluster resolve nomes sem ele. Os restantes são opcionais. Em /etc/rancher/k3s/config.yaml:

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

O k3s elimina os componentes que desativa, por isso os pods do traefik e os pods de svclb- desaparecem automaticamente. Conheça primeiro as consequências. Sem servicelb, todos os serviços type: LoadBalancer permanecem em <pending> para sempre, porque nada lhes atribui um endereço. Sem traefik, não existe um controlador de ingress, por isso os objetos Ingress não fazem nada. Desative-os quando servir o tráfego de outra forma, como através de um reverse proxy no host, e mantenha-os ativos quando os utilizar. disable-helm-controller: true remove o controlador que monitoriza os recursos HelmChart, um componente privilegiado que não utiliza se executar helm por conta própria.

Desative a montagem automática do token ServiceAccount padrão

Cada pod recebe um token ServiceAccount em /var/run/secrets/kubernetes.io/serviceaccount/token, exceto se indicar o contrário. O default ServiceAccount não tem permissões RBAC, por isso o token, por si só, não permite fazer muita coisa. Para um atacante dentro de um contentor comprometido, o token fornece uma credencial válida e um servidor da API acessível. Esse é o primeiro passo descrito na maioria dos procedimentos de escalação num cluster.

O guia de hardening do k3s desativa este comportamento por 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}'

Confirme se a alteração foi aplicada. Aguarde alguns segundos para o pod iniciar.

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

O caminho desapareceu, por isso ls imprime No such file or directory. Uma carga de trabalho que precise efetivamente de acesso à API define automountServiceAccountToken: true na sua própria especificação de pod. Assim, nada fica bloqueado permanentemente. Esta é uma melhoria pequena por si só. A melhoria mais importante é não atribuir a uma carga de trabalho um ServiceAccount com permissões reais. Pode verificar quanto vale atualmente um token:

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

Limites de recursos, para que um pod não derrube o cluster

Num único nó, o control plane e as suas cargas de trabalho partilham o mesmo kernel e o mesmo conjunto de memória. Um pod que tenha uma fuga de memória nem sempre termina sozinho. O killer OOM (out of memory) do kernel escolhe a vítima com base numa pontuação que favorece processos grandes. O k3s é um processo grande e de longa duração. Por isso, pode desaparecer o cluster em vez do pod. Nesse caso, nada reinicia as suas cargas de trabalho, porque o componente que as reinicia foi o que morreu.

Um LimitRange preenche os limites dos pods que não definem nenhum:

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

Um ResourceQuota limita o que todo o namespace pode reservar:

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

Depois, reserve espaço para o próprio k3s em /etc/rancher/k3s/config.yaml:

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

A diferença aparece em dois locais. Um container terminado por exceder o seu próprio limite comunica Reason: OOMKilled em Last State dentro de kubectl describe pod, e o restante nó continua a funcionar. Um nó que fique completamente sem memória deixa uma linha Killed process em dmesg e normalmente arrasta consigo os nós vizinhos. O primeiro caso mostra que o seu limite está a funcionar. O segundo é precisamente o problema que os limites existem para evitar.

Analise os manifests no CI e o cluster k3s segundo uma agenda

A análise deve ocorrer em dois locais, e cada um deteta problemas diferentes. Instale primeiro o Trivy. O projeto disponibiliza um pacote Debian em cada release, e 0.74.0 era a versão atual em agosto de 2026. Consulte a página de releases para verificar se existe uma versão mais recente antes de fixar esta versão na automação.

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

O primeiro local são os seus manifests, antes de chegarem ao cluster.

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

--exit-code 1 faz o job de CI (integração contínua) falhar quando encontra um problema. Cada resultado identifica o campo com falha e a respetiva severidade. Assim, um privileged: true que não pretendia incluir no commit faz o build falhar em vez de chegar ao API server. Um problema que decidiu aceitar deve ser incluído num ficheiro .trivyignore. Dessa forma, a decisão fica no git, junto do manifest que a originou.

O segundo local é o cluster em execução, segundo uma agenda.

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

Há uma observação importante sobre esse comando. trivy k8s implementa um pod coletor em cada node, que precisa de acesso ao host para inspecionar definições ao nível do node. Num cluster onde começou recentemente a rejeitar pods privilegiados, deve ter isso em atenção em vez de contornar a política. trivy k8s --report summary --disable-node-collector ignora o coletor e, com ele, perde as verificações ao nível do node.

O kube-bench cobre o lado do host: permissões de ficheiros e flags de processos. É aí que se encontra a maior parte do benchmark CIS (Center for Internet Security). O kube-bench inclui um perfil para k3s. Use o manifest Job disponibilizado pelo projeto upstream e ajuste-o.

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

Altere o comando do container para ["kube-bench", "--benchmark", "k3s-cis-1.7"] e substitua as montagens /etc/kubernetes e /var/lib/etcd por /etc/rancher e /var/lib/rancher, porque é aí que o k3s guarda os seus ficheiros. Depois:

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

Observe o que esse Job é: hostPID: true mais montagens de diretórios do host. Essa é exatamente a forma de pod que começou a rejeitar há uma secção. Execute-o no namespace isento kube-system, leia o resultado e depois kubectl delete job kube-bench. O facto de um scanner precisar de privilégios não é motivo para deixar de proibir workloads privilegiados.

O conteúdo das imagens é uma questão separada. trivy image ghcr.io/example/app:1.4 lê a base de dados de pacotes dentro de uma imagem e lista os pacotes conhecidos como vulneráveis. É a mesma tarefa de quando verifica o seu servidor quanto a CVEs conhecidas.

Agora, a parte importante sobre o resultado dos scanners. Um cluster de hobby com um único node falhará uma lista extensa de controlos CIS. A maioria dessas falhas é correta, mas irrelevante para si. O benchmark foi escrito para um cluster com vários nodes e vários tenants: etcd em hosts separados, logs de auditoria enviados para fora da máquina, uma autoridade certificadora separada para o kubelet e plugins de admission destinados a um regime de conformidade que não lhe é aplicável. O k3s executa deliberadamente o control plane como um único processo com um ficheiro de configuração. Por isso, os controlos que verificam as permissões de um ficheiro de manifest kube-scheduler não podem passar, porque esse ficheiro não existe.

Leia as falhas pela ordem seguinte e pare quando o valor deixar de compensar: modos e propriedade dos ficheiros em /etc/rancher e /var/lib/rancher; tudo o que mencione acesso anónimo ou não autenticado; tudo o que indique um componente associado a 0.0.0.0; e qualquer container executado como UID 0 sem uma razão válida. O resto pode esperar por um segundo node ou por outra pessoa com acesso. Um relatório de cem linhas que ignora vale menos do que um relatório de cinco linhas sobre o qual atua.

Ordem de hardening do k3s para um único nó

  1. Defina a política padrão de entrada do ufw como deny, permita SSH, permita 6443 a partir do seu próprio endereço e permita as redes de pods e serviços.
  2. Copie o kubeconfig para o seu utilizador com o modo 600 e nunca defina --write-kubeconfig-mode 644.
  3. Confirme que o kubelet na porta 10250 recusa pedidos anónimos e mantenha essa porta fora da Internet.
  4. Desative os componentes incluídos que não utiliza e reinicie o k3s.
  5. Aplique etiquetas aos namespaces para a admissão do Pod Security com enforce=baseline e warn=restricted.
  6. Desative a montagem automática de tokens do ServiceAccount por predefinição.
  7. Adicione um LimitRange e um ResourceQuota e reserve CPU e memória para o k3s.
  8. Inclua trivy fs --scanners misconfig no CI e execute uma análise CIS todos os meses.

O host subjacente continua a precisar dos mesmos cuidados que qualquer outro servidor, e o k3s acrescenta uma particularidade. Foi instalado por um script e não pelo apt, por isso apt upgrade nunca o atualiza. Mantenha o sistema operativo atualizado segundo o seu próprio calendário, usando atualizações automáticas no Ubuntu, e atualize o k3s deliberadamente, executando novamente o instalador com o canal ou a versão pretendidos. É fácil esquecer dois caminhos de atualização na mesma máquina, por isso registe qual componente segue cada um.

FAQ

É seguro expor o servidor da API do k3s na porta 6443 à internet?

Por si só, não é uma porta aberta, porque o servidor da API exige um certificado de cliente ou um token e recusa tudo o resto com forbidden: User "system:anonymous". Permanecem dois riscos. Os clientes anónimos ainda podem ler /version, que indica a um scanner qual a versão do Kubernetes a procurar. Além disso, qualquer vulnerabilidade futura no servidor da API ficará acessível remotamente enquanto a porta estiver aberta. Num cluster de um único nó, nada externo precisa de 6443 além do seu próprio kubectl. Por isso, permita o acesso a partir do seu endereço com sudo ufw allow from YOUR_IP to any port 6443 proto tcp e deixe a política de negação predefinida tratar do restante.

Porque é que a minha regra do ufw não bloqueia um serviço NodePort?

Porque o pacote nunca chega à cadeia onde a sua regra está instalada. O kube-proxy coloca regras DNAT na cadeia PREROUTING da tabela nat. Essa cadeia é executada primeiro e reescreve o destino para um endereço de pod. O pacote é depois encaminhado, em vez de ser entregue localmente. Por isso, atravessa FORWARD e ignora INPUT, onde estão as regras do ufw. Confirme-o com sudo iptables -S PREROUTING -t nat | head. Filtre os NodePorts na firewall de rede do seu fornecedor ou evite type: NodePort e aceda aos serviços com kubectl port-forward.

Devo executar o k3s com --write-kubeconfig-mode 644?

Não. A documentação do k3s explica o efeito: "Changing the mode to 644 will allow it to be read by other unprivileged users on the host." Esse ficheiro contém um certificado de cliente para system:masters. Assim, o modo 644 transforma todas as contas locais em administradores do cluster. Em vez disso, copie-o para um único utilizador com sudo install -o "$USER" -g "$USER" -m 600 /etc/rancher/k3s/k3s.yaml ~/.kube/config e mantenha o original em 600 root:root.

Qual benchmark do kube-bench devo usar para o k3s?

Use k3s-cis-1.7. A documentação do kube-bench afirma: "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." Indique-o explicitamente, porque a deteção automática assume uma disposição kubeadm, enquanto o k3s mantém os seus ficheiros em /etc/rancher e /var/lib/rancher. Espere falhas que não se aplicam a um único nó e corrija primeiro as permissões dos ficheiros e o acesso anónimo.

A admissão Pod Security vai interromper os componentes incluídos no k3s?

Vai, se a aplicar em kube-system. Os pods ServiceLB que o k3s cria para serviços type: LoadBalancer reservam portas no host. O baseline proíbe portas do host, portanto esses pods serão rejeitados da próxima vez que forem recriados. Exclua kube-system do ficheiro de configuração da admissão ou aplique Pod Security apenas como etiquetas nos namespaces que administra. Comece com enforce=baseline e warn=restricted para conseguir ver o que restricted iria interromper antes de o ativar.