Как защитить одноузловой кластер k3s на VPS
Узнайте, как закрыть уязвимости k3s после установки: порты 6443 и 10250, права доступа к kubeconfig, настройки NodePort и запуск привилегированных подов. Пошаговая инструкция.
Что открывает одноузловой кластер k3s сразу после установки
Одноузловой кластер k3s на публичном VPS становится доступен по пяти направлениям сразу после завершения работы однострочного установщика: API-сервер Kubernetes на TCP 6443, kubelet на TCP 10250, файл kubeconfig на диске, диапазон портов NodePort, который не видит ваш межсетевой экран, и любой под, которому разрешено запрашивать privileged или hostPath. Для каждого из этих пунктов существует решение, занимающее несколько минут. В этом руководстве предполагается, что k3s уже запущен, поэтому, если это не так, начните с установки одноузлового k3s на VPS и вернитесь сюда позже.
Прежде чем вносить изменения, посмотрите, какие порты находятся в состоянии прослушивания.
sudo ss -tulpn | grep -E '6443|10250|10256|8472'Стандартная установка показывает порты 6443 (API-сервер), 10250 (kubelet), 10256 (проверка работоспособности kube-proxy) и 8472/udp (оверлейная сеть flannel, использующая VXLAN, virtual extensible LAN). По умолчанию k3s привязывается к 0.0.0.0, поэтому все эти порты доступны на вашем публичном адресе, а не только на loopback.
Почему порт 6443 — это весь кластер
Любой субъект, способный пройти аутентификацию на порту 6443 с правами администратора, может создать pod, а pod может получить права root на хосте. Порт 6443 — это вход в систему.
Открытый порт 6443 не означает мгновенный взлом, так как Kubernetes не принимает пароли. Ему требуются клиентский сертификат или bearer token. Тем не менее, остаются верными два факта.
Во-первых, API server отвечает на некоторые запросы без каких-либо учетных данных. Стандартная настройка RBAC (role-based access control) в Kubernetes привязывает группу system:unauthenticated к роли system:public-info-viewer, которая разрешает /version, /healthz, /livez и /readyz. С другой машины:
curl -sk https://YOUR_SERVER_IP:6443/versionЭтот запрос возвращает точную версию Kubernetes, которая является входными данными для поиска CVE (common vulnerabilities and exposures) и причиной, по которой сканер помечает ваш сервер как интересную цель. Любые запросы к путям за пределами этих будут отклонены, при этом в ответе на отказ будет указано ваше имя:
forbidden: User "system:anonymous" cannot get path "/api"Во-вторых, любая ошибка в API server становится удаленно эксплуатируемой, пока этот порт открыт. С этого момента установка патчей перестает быть опциональной.
Дешевое решение — правило межсетевого экрана. Для ufw здесь потребуется больше одной строки, так как k3s маршрутизирует трафик кластера через то же ядро.
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Последние две строки взяты непосредственно из документации k3s. 10.42.0.0/16 — это сеть pod по умолчанию, а 10.43.0.0/16 — сеть service по умолчанию. Без этих правил ufw будет отбрасывать внутренний трафик кластера, из-за чего pod потеряют связь с API server и друг с другом. Пример k3s разрешает доступ к 6443 отовсюду; стоит заменить это на ваш собственный адрес. Если вы новичок в ufw, основы работы с межсетевым экраном ufw на VPS описывают политики по умолчанию, от которых это зависит.
Документация k3s прямо говорит о порте overlay-сети: «Порт VXLAN на узлах не должен быть открыт для всего мира, так как это делает сеть вашего кластера доступной для любого желающего». Политика запрета по умолчанию для входящего трафика решает эту проблему без необходимости указывать конкретный порт.
Более надежное решение — полностью прекратить доступ к API через публичный адрес и использовать VPN или mesh-адрес. Сертификат сервера должен содержать адрес, к которому вы подключаетесь, поэтому добавьте его как SAN (subject alternative name) в /etc/rancher/k3s/config.yaml:
tls-san:
- 10.8.0.1
- k3s.example.com
secrets-encryption: truesudo systemctl restart k3s
sudo k3s secrets-encrypt statussecrets-encryption: true шифрует объекты Secret в хранилище данных. В документации k3s отмечено, что «шифрование секретов нельзя включить на работающем сервере без его перезапуска», а секреты, записанные до внесения изменений, сохранят свой старый вид, пока вы не выполните sudo k3s secrets-encrypt reencrypt. Четко понимайте, что это дает. Это защищает файл хранилища данных, скопированный из резервной копии. Это никак не помогает против того, кто может взаимодействовать с API server, так как API server расшифровывает секреты для любого, у кого есть права на их чтение. Это же различие проходит через любое self-hosted хранилище секретов, поэтому укрепление защиты Vaultwarden фокусируется на токене администратора и файле резервной копии, а не на самом шифровании.
Почему важен kubelet на порту 10250
kubelet — это агент, который запускает контейнеры. Его API на порту 10250 позволяет перечислять поды и выполнять команды внутри них. kubelet, принимающий анонимные запросы, предоставляет удаленную оболочку (shell) для любой рабочей нагрузки на машине.
Проверьте свой:
curl -sk https://127.0.0.1:10250/pods | head -c 60Актуальный k3s отвечает Unauthorized, так как kubelet запрашивает у API-сервера аутентификацию и авторизацию для каждого вызывающего субъекта. Если вместо этого возвращается JSON-список подов, значит, включен анонимный доступ, и любой, кто может подключиться к порту 10250, может читать данные из ваших контейнеров и выполнять в них команды.
В любом случае закройте этот порт для внешних подключений. На одиночном узле единственным клиентом kubelet является control plane на той же машине, и этот трафик проходит через интерфейс loopback, который ufw разрешает по умолчанию. Запрет доступа к 10250 из Интернета не несет никаких рисков. Если вы попали сюда из-за ошибки kubectl top или проблем с metrics-server, который не может корректно запуститься, причины собраны в ошибки kubelet на порту 10250.
Ваш kubeconfig — это учетные данные администратора кластера
k3s создает /etc/rancher/k3s/k3s.yaml, владельцем которого является root, с правами доступа 600. В документации указаны последствия изменения этих прав: «Файл kubeconfig принадлежит пользователю root и создается с правами доступа 600 по умолчанию. Изменение прав на 644 позволит читать его другим непривилегированным пользователям на хосте».
Понимайте это так: права 644 делают любую локальную учетную запись администратором кластера. Множество руководств предлагают именно это, обычно в виде --write-kubeconfig-mode 644, чтобы команда kubectl работала без sudo. Это работает за счет передачи прав администратора любому пользователю, имеющему доступ к оболочке (shell).
Вместо этого скопируйте файл для конкретного пользователя.
mkdir -p ~/.kube
sudo install -o "$USER" -g "$USER" -m 600 /etc/rancher/k3s/k3s.yaml ~/.kube/config
kubectl get nodesЗатем убедитесь, что исходный файл по-прежнему защищен:
stat -c '%a %U:%G' /etc/rancher/k3s/k3s.yaml600 root:root — это то, что вам нужно. В этом файле содержится клиентский сертификат для участника группы system:masters — группы, которую API-сервер считает безусловно доверенной, поэтому правила RBAC для нее никогда не проверяются. В Kubernetes нет списка отзыва сертификатов, а это значит, что скомпрометированная копия остается валидной до тех пор, пока вы не обновите центр сертификации кластера. Обращайтесь с ним как с закрытым ключом SSH и ограничьте количество учетных записей, имеющих к нему доступ; это тот же принцип, что и принцип наименьших привилегий для учетных записей на VPS.
Ваш межсетевой экран не видит трафик NodePort
Сервис type: NodePort открывает порт в диапазоне от 30000 до 32767 на каждом адресе узла, включая публичный. Сервис type: LoadBalancer в k3s работает иначе: встроенный балансировщик нагрузки ServiceLB запускает отдельный небольшой под для каждого сервиса в kube-system, который занимает порт сервиса непосредственно на хосте.
kubectl -n kube-system get pods | grep svclb
sudo ss -tulpn | grep -E ':3[0-2][0-9]{3}'Теперь момент, который часто вызывает удивление. Если заблокировать этот порт с помощью ufw, он продолжит отвечать.
sudo ufw deny 30080/tcp
curl http://YOUR_SERVER_IP:30080Страница всё равно загружается из-за пути прохождения пакета. kube-proxy записывает правила DNAT (destination network address translation) в цепочку PREROUTING таблицы nat, а PREROUTING выполняется до принятия любого решения о фильтрации. Пунктом назначения становится адрес пода, а не хоста, поэтому ядро направляет пакет в цепочку FORWARD, минуя INPUT. Правила ufw находятся в INPUT. Пакет никогда с ними не сталкивается. Порядок переходов виден здесь:
sudo iptables -S PREROUTING -t nat | head
sudo iptables -S FORWARD | headKUBE-SERVICES находится в самом верху PREROUTING, а переходы Kubernetes в FORWARD расположены выше собственных цепочек ufw. Это тот же механизм, который позволяет Docker публиковать порты в обход ufw, и решения здесь аналогичны.
- Фильтруйте трафик на сетевом экране вашего провайдера. Он работает перед сервером и не зависит от того, как настроена маршрутизация в вашем ядре.
- Откажитесь от NodePort и LoadBalancer. Оставьте сервисы с типом
ClusterIPи обращайтесь к ним черезkubectl port-forwardпо уже установленному SSH-соединению. - Открывайте только один Ingress на портах 80 и 443, и ничего больше.
- Сузьте диапазон портов с помощью
service-node-port-rangeв настройкахkube-apiserver-arg, чтобы случайный NodePort оказался в зоне вашего контроля.
Использовать ufw всё равно стоит. Он управляет трафиком, адресованным самому хосту, например, SSH и API-серверу. Он просто не контролирует трафик подов, и ожидание обратного — это причина, по которой базы данных часто оказываются доступны извне.
Pod с hostPath или привилегированным режимом получает права root на вашем VPS
Контейнеры — это обычные процессы в вашем ядре с ограниченным представлением о системе. Несколько полей в спецификации pod снимают эти ограничения.
securityContext.privileged: trueпредоставляет контейнеру все Linux capabilities и доступ к устройствам хоста.hostPathмонтирует директорию хоста внутрь pod. Pod, который монтирует/в режиме чтения-записи, может добавить ключ в/root/.ssh/authorized_keys.hostPID: trueпомещает контейнер в пространство имен процессов хоста, гдеnsenterдля PID 1 открывает shell хоста.hostNetwork: trueпомещает его в сетевой стек хоста, где он может занимать порты хоста и обращаться к сервисам, привязанным к loopback.
Таким образом, вопрос «кто может создавать pod здесь» равносилен вопросу «кто является root на этом VPS». Любой ServiceAccount с правами create на создание pod в любом namespace эквивалентен root, если только что-то не блокирует такой pod на раннем этапе.
Этим «чем-то» является Pod Security admission, встроенный в API server. Кратко: это метка для namespace, которая не требует перезагрузки.
kubectl label namespace default \
pod-security.kubernetes.io/enforce=baseline \
pod-security.kubernetes.io/enforce-version=latest \
pod-security.kubernetes.io/warn=restrictedbaseline отклоняет все четыре указанных выше поля. restricted идет дальше и требует использования пользователя без прав root, профиля seccomp (secure computing mode), запрета на повышение привилегий и ограничения capabilities до ALL, что нарушает работу многих опубликованных чартов. Применение baseline с выводом предупреждений для restricted позволяет увидеть, что именно перестанет работать, прежде чем вы окончательно примените ограничения.
Проверка работоспособности:
kubectl apply -f - <<'EOF'
apiVersion: v1
kind: Pod
metadata:
name: pstest
spec:
containers:
- name: app
image: busybox
command: ["sleep", "60"]
securityContext:
privileged: true
EOFAPI server отклоняет запрос и указывает, какое именно поле вызвало отказ:
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)Для настройки по умолчанию на уровне всего кластера, вместо установки меток на каждый namespace, k3s предлагает использовать файл конфигурации admission по пути /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]Укажите этот файл для API server в /etc/rancher/k3s/config.yaml, затем перезапустите k3s:
kube-apiserver-arg:
- 'admission-control-config-file=/var/lib/rancher/k3s/server/psa.yaml'Исключение kube-system является обязательным. Собственные pod ServiceLB в k3s занимают порты хоста, что запрещено политикой baseline, поэтому отсутствие kube-system в списке приведет к тому, что эти pod будут отклонены при следующей попытке их пересоздания. Держите открытой вторую SSH-сессию при перезапуске k3s после изменения настроек admission.
Бонус: k3s поставляет контроллер сетевых политик и включает его по умолчанию, поэтому объекты NetworkPolicy работают в этом кластере без установки дополнительного ПО. Это верно не для всех дистрибутивов Kubernetes, и это основной инструмент для предотвращения доступа скомпрометированного pod к остальным частям системы.
Удаление неиспользуемых встроенных компонентов
Установщик развертывает набор дополнений. Каждое из них добавляет слушающий порт и еще один элемент, требующий обновления. --disable принимает следующие значения: coredns, servicelb, traefik, local-storage, metrics-server, runtimes.
Оставьте coredns. Без него в кластере не работает разрешение имен. Остальное — на ваше усмотрение. В /etc/rancher/k3s/config.yaml:
disable:
- traefik
- servicelb
disable-helm-controller: truesudo systemctl restart k3s
kubectl get pods -Ak3s удаляет отключенные компоненты, поэтому поды traefik и поды svclb- исчезают автоматически. Сначала оцените последствия. После удаления servicelb каждый сервис типа type: LoadBalancer навсегда останется в состоянии <pending>, так как никто не назначит ему адрес. После удаления traefik в кластере не останется ingress controller, поэтому объекты Ingress перестанут выполнять свои функции. Отключайте эти компоненты, если используете другой способ обработки трафика, например, reverse proxy на хосте, и не трогайте их, если они вам нужны. disable-helm-controller: true удаляет контроллер, который отслеживает ресурсы HelmChart; это привилегированный компонент, который не требуется, если вы запускаете helm самостоятельно.
Отключение автоматического монтирования токена ServiceAccount по умолчанию
Каждый pod получает токен ServiceAccount по адресу /var/run/secrets/kubernetes.io/serviceaccount/token, если не указано иное. ServiceAccount default не обладает правами RBAC, поэтому сам по себе токен дает немного. Однако злоумышленнику внутри скомпрометированного контейнера он предоставляет действительные учетные данные и доступный API server, что является первым шагом в большинстве сценариев повышения привилегий в кластере.
Руководство по обеспечению безопасности k3s рекомендует отключать эту функцию для каждого 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}'Проверьте результат. Дайте pod несколько секунд на запуск.
kubectl run t --image=busybox --restart=Never --command -- sleep 30
kubectl exec t -- ls /var/run/secrets/kubernetes.io/serviceaccountПуть отсутствует, поэтому ls выводит No such file or directory. Рабочая нагрузка, которой действительно необходим доступ к API, задает automountServiceAccountToken: true в спецификации своего pod, поэтому доступ не блокируется окончательно. Это небольшое, но полезное улучшение. Более значимый шаг — не предоставлять рабочей нагрузке ServiceAccount с реальными правами. Вы можете проверить, что позволяет текущий токен:
kubectl auth can-i --list --as=system:serviceaccount:default:defaultОграничение ресурсов, чтобы один pod не вывел из строя весь кластер
На одном узле control plane и ваши рабочие нагрузки используют одно ядро ОС и общий пул оперативной памяти. Pod, в котором происходит утечка памяти, не всегда завершается в одиночку. OOM (out of memory) killer ядра выбирает жертву на основе оценки, где больший вес имеют крупные процессы. Поскольку k3s — это крупный долгоживущий процесс, вместо pod может завершиться весь кластер. В этом случае перезапуск рабочих нагрузок не произойдет, так как процесс, отвечающий за их перезапуск, был остановлен.
Объект LimitRange задает лимиты для pod, в которых они не были указаны:
apiVersion: v1
kind: LimitRange
metadata:
name: defaults
namespace: default
spec:
limits:
- type: Container
default:
cpu: 500m
memory: 512Mi
defaultRequest:
cpu: 50m
memory: 128MiОбъект ResourceQuota ограничивает суммарный объем ресурсов, который может занять namespace:
apiVersion: v1
kind: ResourceQuota
metadata:
name: cap
namespace: default
spec:
hard:
limits.cpu: "3"
limits.memory: 3Gi
pods: "20"Зарезервируйте ресурсы для самого k3s в /etc/rancher/k3s/config.yaml:
kubelet-arg:
- 'system-reserved=cpu=250m,memory=512Mi'Разница проявляется в двух случаях. Контейнер, завершенный из-за превышения собственного лимита, сообщает Reason: OOMKilled в поле Last State в выводе kubectl describe pod, при этом остальные процессы на узле продолжают работу. Узел, на котором полностью закончилась память, оставляет строку Killed process в dmesg и обычно завершает работу соседних процессов. Первый случай означает, что лимиты работают корректно. Второй случай — это то, для предотвращения чего и существуют лимиты.
Сканирование манифестов в CI и кластере k3s по расписанию
Сканирование необходимо выполнять в двух местах, так как это позволяет выявить разные типы проблем. Сначала установите Trivy. Проект выпускает пакет Debian с каждым релизом; версия 0.74.0 была актуальна в августе 2026 года, поэтому перед добавлением в автоматизацию проверьте страницу релизов на наличие более новой версии.
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Первое место — это ваши манифесты до того, как они попадут в кластер.
trivy fs --scanners misconfig --severity HIGH,CRITICAL --exit-code 1 ./deploy--exit-code 1 прерывает выполнение задания CI (непрерывной интеграции) при обнаружении уязвимости. Каждый результат содержит имя проблемного поля и уровень его критичности, поэтому privileged: true, который вы не планировали добавлять, прервет сборку, не дойдя до API-сервера. Уязвимость, которую вы решили принять, фиксируется в файле .trivyignore, что сохраняет это решение в git рядом с манифестом, вызвавшим проблему.
Второе место — работающий кластер, сканирование которого выполняется по расписанию.
trivy k8s --compliance=k8s-cis-1.23 --report summaryВажное замечание об этой команде. trivy k8s развертывает pod-коллектор узла, которому требуется доступ к хосту для проверки настроек уровня узла. В кластере, где вы только что начали запрещать привилегированные pod-ы, на это стоит обратить внимание, а не пытаться обойти ограничение. trivy k8s --report summary --disable-node-collector пропускает коллектор, что приводит к потере проверок уровня узла.
kube-bench охватывает сторону хоста: права доступа к файлам и флаги процессов, где сосредоточена большая часть рекомендаций стандарта CIS (Center for Internet Security). Инструмент поставляется с профилем для k3s. Возьмите манифест Job из upstream и адаптируйте его.
curl -sfL -o kube-bench-job.yaml https://raw.githubusercontent.com/aquasecurity/kube-bench/main/job.yamlИзмените команду контейнера на ["kube-bench", "--benchmark", "k3s-cis-1.7"] и замените монтирования /etc/kubernetes и /var/lib/etcd на /etc/rancher и /var/lib/rancher, так как именно там k3s хранит свои файлы. Затем:
kubectl apply -f kube-bench-job.yaml
kubectl logs -l app=kube-bench --tail=-1Обратите внимание, что представляет собой этот Job: hostPID: true с монтированием директорий хоста, что в точности соответствует типу pod-ов, которые вы начали запрещать в предыдущем разделе. Запустите его в исключенном пространстве имен kube-system, изучите вывод, а затем kubectl delete job kube-bench. Необходимость использования привилегированного сканера не является поводом для отмены запрета на привилегированные рабочие нагрузки.
Содержимое образов — это отдельный вопрос. trivy image ghcr.io/example/app:1.4 считывает базу данных пакетов внутри образа и выводит список известных уязвимостей; это та же задача, что и при проверке сервера на наличие известных CVE.
Теперь о честности в отношении результатов сканирования. Одноузловой любительский кластер не пройдет длинный список проверок CIS, и большинство этих сбоев одновременно верны и неактуальны для вас. Стандарт написан для многоузловых, многопользовательских кластеров: etcd на отдельных хостах, отправка журналов аудита с машины, отдельный центр сертификации kubelet, плагины допуска для режима соответствия требованиям, под которые вы не подпадаете. k3s намеренно запускает control plane как единый процесс с одним конфигурационным файлом, поэтому проверки прав доступа к файлу манифеста kube-scheduler не могут быть пройдены, так как такого файла не существует.
Изучайте сбои в следующем порядке и останавливайтесь, когда польза от анализа исчерпается: права доступа и владельцы файлов в /etc/rancher и /var/lib/rancher, любые упоминания анонимного или неаутентифицированного доступа, любые отчеты о компонентах, привязанных к 0.0.0.0, и любые контейнеры, работающие от имени UID 0 без веской причины. Остальное может подождать появления второго узла или второго человека с доступом. Отчет на сто строк, который вы игнорируете, стоит меньше, чем отчет на пять строк, по которому вы принимаете меры.
Порядок усиления безопасности k3s для одного узла
- Установите политику по умолчанию для входящего трафика в ufw на deny, разрешите SSH, разрешите доступ к 6443 с вашего адреса, а также разрешите сети подов и сервисов.
- Скопируйте kubeconfig для вашего пользователя с правами 600 и никогда не устанавливайте
--write-kubeconfig-mode 644. - Убедитесь, что kubelet на порту 10250 отклоняет анонимные запросы, и закройте этот порт от доступа из Интернета.
- Отключите неиспользуемые встроенные компоненты, затем перезапустите k3s.
- Назначьте метки для пространств имен (namespaces) для Pod Security admission с помощью
enforce=baselineиwarn=restricted. - Отключите автоматическое монтирование токенов ServiceAccount по умолчанию.
- Добавьте LimitRange и ResourceQuota, а также зарезервируйте CPU и память для k3s.
- Добавьте
trivy fs --scanners misconfigв CI и ежемесячно проводите сканирование на соответствие CIS.
Хостовая система по-прежнему требует такого же внимания, как и любой другой сервер, а k3s добавляет свои особенности. Поскольку k3s устанавливается скриптом, а не через apt, apt upgrade не затрагивает его. Поддерживайте операционную систему в актуальном состоянии согласно собственному графику с помощью автоматических обновлений в Ubuntu, а k3s обновляйте целенаправленно, повторно запуская установщик с нужным каналом или версией. Легко забыть о двух путях обновления на одной машине, поэтому зафиксируйте, какой компонент и как обновляется.
FAQ
Безопасно ли открывать API-сервер k3s на порту 6443 для доступа из Интернета?
Сам по себе этот порт не является открытой дверью, так как API-сервер требует клиентский сертификат или токен и отклоняет любые другие запросы с ошибкой forbidden: User "system:anonymous". Однако остаются два риска. Анонимные пользователи могут прочитать /version, что позволяет сканеру точно определить версию Kubernetes. Кроме того, любая будущая уязвимость в API-сервере станет доступна удалённо, пока порт открыт. Для одноузлового кластера порт 6443 не нужен никому, кроме вашего kubectl, поэтому разрешите доступ только со своего IP-адреса с помощью sudo ufw allow from YOUR_IP to any port 6443 proto tcp, а остальное заблокируйте политикой по умолчанию.
Почему моё правило ufw не блокирует сервис NodePort?
Потому что пакет не доходит до цепочки, в которой находится ваше правило. kube-proxy добавляет правила DNAT в цепочку PREROUTING таблицы nat, которая срабатывает первой и переписывает адрес назначения на адрес пода. Пакет пересылается (forwarded), а не доставляется локально, поэтому он проходит через цепочку FORWARD и минует INPUT, где работают правила ufw. Проверьте это с помощью sudo iptables -S PREROUTING -t nat | head. Фильтруйте NodePort на сетевом межсетевом экране вашего провайдера или откажитесь от type: NodePort в пользу доступа к сервисам через kubectl port-forward.
Стоит ли запускать k3s с --write-kubeconfig-mode 644?
Нет. В документации k3s прямо сказано: «Изменение прав доступа на 644 позволит читать файл другим непривилегированным пользователям хоста». Этот файл содержит клиентский сертификат для system:masters, поэтому права 644 делают любую локальную учётную запись администратором кластера. Вместо этого скопируйте файл для конкретного пользователя с помощью sudo install -o "$USER" -g "$USER" -m 600 /etc/rancher/k3s/k3s.yaml ~/.kube/config, а оригинал оставьте по пути 600 root:root.
Какой бенчмарк kube-bench использовать для k3s?
Используйте k3s-cis-1.7. В документации kube-bench указано: «kube-bench включает бенчмарки для платформы Rancher K3S. Для запуска необходимо указать --benchmark k3s-cis-1.7 при выполнении команды kube-bench». Указывайте этот флаг явно, так как автоопределение предполагает структуру kubeadm, в то время как k3s хранит файлы в /etc/rancher и /var/lib/rancher. Ожидайте ошибок, которые не применимы к одноузловой конфигурации; в первую очередь обращайте внимание на права доступа к файлам и анонимный доступ.
Сломает ли Pod Security admission встроенные компоненты k3s?
Да, если применить его к kube-system. Поды ServiceLB, которые k3s создаёт для сервисов типа type: LoadBalancer, занимают порты на хосте, а baseline запрещает использование хостовых портов, поэтому при пересоздании такие поды будут отклонены. Исключите kube-system в конфигурационном файле admission или применяйте Pod Security только в виде меток к пространствам имён, которые вы создали сами. Начните с enforce=baseline и warn=restricted, чтобы увидеть, что именно сломает restricted, прежде чем включать принудительное применение.