ping: permission denied (are you root?)
Бахилин Никита.
Всем привет. Я студент DevOps курса от компании YADRO, Бахилин Никита. Эту статью мне помог дополнить интересным материалом мой коллега по курсу - Уткин Даниил. Мы подготовили эту статью для ребят, таких же студентов, которые могут столкнуться с похожими проблемами при создании кластера Kubernetes. К сожалению, (или к счастью) никакая нейросетка сразу не скажет в чем проблема, да и материалов в интернете очень мало на эту тему. Поэтому, приятного чтения!
Данная статья состоит из двух частей:
- Решение проблемы способом который не противоречит требованию ДЗ.
- Причина этой проблемы. Альтернатива, Best Practice для ее решения.
Part1.
Немного предыстории.
Настал момент, когда мы докатились до Kuber****s . Задача стояла не сложная для опытного инженера, но, сложная для человека который никогда не нюхал k8s.
Задача: Организация k8s кластера в ручном режиме с помощью kubeadm и kubectl на базе cri-o (1.28+) и использованием calico как CNI плагин.
- Кластер доступен для взаимодействия через kubectl, команда возвращает корректную информацию о кластере
- Есть возможность сделать ping 8.8.8.8 с образом busybox
Обратим внимание на последний пункт.
Данный гайд уже подразумевает установленные на ваших узлах: kubectl, kubelet, kubeadm, crio.
Собственно, ради чего мы здесь сегодня собрались. На данном этапе все worker ноды должны быть присоединены к control-plane. Чтобы это проверить, можете воспользоваться следующей командой на master узле:
$ kubectl get nodes

Если у вас примерно такой же вывод и STATUS: Ready, то можно идти дальше.
Выполняем команду, которая по требованию в нашем ТЗ.
$ kubectl run -it --tty test --image=busybox -- sh

Как видите, нас перебрасывает внутрь контейнера test образа busybox.
Теперь проверяем возможность выполнить команду ping 8.8.8.8
/ # ping 8.8.8.8

Я перебирал множество вариантов, что я только не делал... И писал манифесты, и добавил SecureContext в поды, потом я написал Артуру Франку - cтарший инженер по разработке ПО в YADRO. И он мне сказал, что image должен запускаться без дополнительных манипуляций, просто с консоли, то есть никаких готовых pod.yml не должно быть c дополнительной настройкой.
Я начал капать глубже. Перешел на уровень хостов.
И тут мы здороваемся с мистером cri-o.
Я решил проверить его capabilities.
$ crio config
либо вот так:
$ crio config | grep NET_RAW

p.s. нам нужен именно NET_RAW. (CAP_NET_RAW) - док Linux

По умолчанию, этот механизм отключен. На то есть причины, о которых поговорим далее во второй части.
Давайте добавим этот capability в наше crio runtime.
$ vim /etc/crio/crio.conf
Либо
$ vim /etc/crio/crio.conf.d/20-default-caps.conf
[crio.runtime]
default_capabilities = [
"CHOWN",
"DAC_OVERRIDE",
"FOWNER",
"FSETID",
"KILL",
"SETGID",
"SETUID",
"SETPCAP",
"NET_BIND_SERVICE",
"NET_RAW"
]
Про каждую можете почитать отдельно. Можете скопировать и вставить в свою конфигурацию.
Теперь перезапускаем crio службу, чтобы изменения вошли в силу.
$ systemctl restart crio.service
Проделываем данную махинацию на всех узлах , на которых собираемся содержать pods. Либо можно воспользоваться моей Ansible ролью, которую я написал для вас :)
Так, служба перезапущена, опять запускаем контейнер, проверяем.
Результат:

Part2.
Автор этой части статьи - Уткин Даниил.
Абсолютно подписываюсь под всем, сказанным Никитой. Тоже столкнулся с данной проблемой и пришёл к схожим выводам. Единственное, что я сделал по-другому, — это разместил default_capabilities не в файле /etc/crio/crio.conf, а в файле /etc/crio/crio.conf.d/20-default-caps.conf, в связи с множеством удобств работы с *.d конфигурациями (но это не критично, личное предпочтение автора). Никакой претензии к постановке ping 8.8.8.8 также не имею, поскольку требование есть требование — надо выполнять.
Анализ проблемы
Хотел бы заострить внимание на анализе возникновения проблемы ping: permission denied (are you root?). Она возникает из-за того, что для возможности работы с ICMP (ping) требуется наличие capability CAP_NET_RAW, который разрешает создавать RAW и PACKET сокеты, работающие на сетевом и канальном уровне соответственно. Протокол ICMP как раз должен упаковываться сразу в IP-пакет без использования TCP и UDP, для чего ему и требуется создание RAW-сокета. Следовательно, нам нужно каким-то образом разрешить его использование. Что интересно, wget, curl и прочие функции будут работать без ошибок, потому что они используют стандартные сокеты.

Причины возникновения
Исходя из changelog cri-o, наличие NET_RAW (а также SYS_CHROOT) было удалено из default_capabilities в версии v1.18.0 по причине наличия уязвимостей, связанных с его использованием. Я посмотрел стандартные capabilities в moby/docker, и CAP_NET_RAW ими предоставляется. Хотя docker и не является эталоном безопасности, но, по всей видимости, в наличии этого разрешения его разработчики не видят проблемы. В дистрибутивах с поддержкой cri-o: minikube и k3s — я также не нашёл каких-то патчей для решения этой проблемы. Да и в issue-трекерах cri-o, minikube, k3s и прочих не так много упоминаний об этом. Из чего я сделал вывод, что большинству поддержка этой функциональности не очень-то и требуется.
Необходимость решения
А является ли это вообще проблемой? И тут скорее ответ — нет. В большинстве случаев во время работы программы редко используют пинги на уровне ICMP, всё-таки чаще это пинги с использованием вышележащих протоколов. Ping приходится использовать разработчикам в том случае, если что-то пошло не так или внутри debug-контейнера (во многих образах ping даже не установлен по умолчанию). Поэтому возникает вопрос, насколько обоснованно включать NET_RAW на уровне всего container runtime? Я считаю, что не очень обоснованно. Да, это в целом решает проблему, но создаёт дыру, которую умные разработчики зачем-то всё-таки закрыли.
Варианты решения
Пока изучал minikube и k3s, обратил внимание на то, что те поды, которым нужна была эта функциональность, просто добавляли её в securityContext.capabilities.add, что, наверное, самое лучшее решение в данном случае, исходя из принципа минимизации привилегий. Но если всё-таки хочется добиться добавления capability, например, на уровне определённых namespace или label? На помощь нам может прийти способы динамического управления профилями безопасности, например Kubernetes Security Profiles Operator или Dynamic Admission Control. Выбор решения, которое наиболее подходит, оставляю на читателя. В целом решение из первой части статьи меня также полностью удовлетворяют, но решил, что надо копнуть этот вопрос немного глубже.
СПАСИБО ЗА ВНИМАНИЕ!