🔒 Отключаем анонимный доступ к kube-apiserver, но оставляем health checks!
Привет! Недавно ко мне пришел коллега-безопасник (Дима привет!) с интересным вопросом: как полностью отключить анонимный доступ к API-серверу Kubernetes, но оставить рабочими проверки
Проблема в том, что по умолчанию (`--anonymous-auth=true`) любой может дернуть эндпоинты health-чеков и не только health-чеков:
Это удобно, но создает потенциальный вектор атаки, если RBAC настроен не идеально или найдется уязвимость. Безопасники такое не любят. 😟
К счастью, в KEP-4633 сообщество Kubernetes предложило решение! Теперь можно тонко настроить, к каким путям разрешен анонимный доступ, даже если глобально он выключен.
Сделать это можно так:
Сначала выключаем глобальный анонимный доступ в манифесте
Затем создаем файл конфигурации
*(Не забудьте добавить
В итоге получаем:
- Запросы к
- Запросы к другим путям (например,
Кстати, эта функциональность появилась как Alpha в Kubernetes 1.31 и стала Beta в 1.32.
Теперь можно спать спокойнее, зная, что анонимный доступ под контролем!
#kubernetes #k8s #security #authentication #kubeadm #devops #infosec
Привет! Недавно ко мне пришел коллега-безопасник (Дима привет!) с интересным вопросом: как полностью отключить анонимный доступ к API-серверу Kubernetes, но оставить рабочими проверки
/livez, /readyz и /healthz? 🤔 Сходу не ответил, полез копаться в исходниках и KEPах.Проблема в том, что по умолчанию (`--anonymous-auth=true`) любой может дернуть эндпоинты health-чеков и не только health-чеков:
curl -k https://<API_SERVER_IP>:6443/livez
# Output: ok
Это удобно, но создает потенциальный вектор атаки, если RBAC настроен не идеально или найдется уязвимость. Безопасники такое не любят. 😟
К счастью, в KEP-4633 сообщество Kubernetes предложило решение! Теперь можно тонко настроить, к каким путям разрешен анонимный доступ, даже если глобально он выключен.
Сделать это можно так:
Сначала выключаем глобальный анонимный доступ в манифесте
kube-apiserver:
spec:
containers:
- command:
- kube-apiserver
# ... другие флаги ...
- --anonymous-auth=false # <--- Выключаем!
- --authentication-config=/etc/kubernetes/auth-config.yaml # <--- Указываем конфиг
Затем создаем файл конфигурации
/etc/kubernetes/auth-config.yaml на control plane нодах и монтируем его в под kube-apiserver:
# /etc/kubernetes/auth-config.yaml
apiVersion: apiserver.config.k8s.io/v1beta1
kind: AuthenticationConfiguration
anonymous:
enabled: true # Включаем анонимный доступ *только* для указанных путей
conditions:
- path: /livez
- path: /readyz
- path: /healthz
*(Не забудьте добавить
volume и volumeMount в манифест kube-apiserver для этого файла)*В итоге получаем:
- Запросы к
/livez, /readyz, /healthz проходят как system:anonymous.- Запросы к другим путям (например,
/apis) без аутентификации получают 401 Unauthorized.Кстати, эта функциональность появилась как Alpha в Kubernetes 1.31 и стала Beta в 1.32.
Теперь можно спать спокойнее, зная, что анонимный доступ под контролем!
#kubernetes #k8s #security #authentication #kubeadm #devops #infosec
👍20🔥3💋1
😠 Агент-лимитер “слепнет” после появления новых PV? Раз и навсегда разбираемся с `mountPropagation` в Kubernetes!
Недавно писал I/O-лимитер для LVM-томов: агент читает маунты подов, чтобы вычислить
Почему так происходит
* Каждый контейнер стартует в своём *privаte* mount-namespace -> изменения на хосте туда не пролетают.
* В Kubernetes это равно
Как починить?
Параметр
* None - полная изоляция (`rprivate`), дефолт.
* HostToContainer - маунты летят *с хоста -> в контейнер* (`rslave`). Нам нужен именно он.
* Bidirectional - маунты ходят в обе стороны (`rshared`). Работает *только* в `privileged`-контейнере, иначе Pod не стартует.
Спека
Теперь каждое *новое* bind-mount событие внутри
Что происходит под капотом
1. Kubelet видит
2. CRI-shim (containerd/CRI-O) делает каталог
3. Все будущие маунты, созданные kubelet’ом на хосте, автоматически “проливаются” в контейнер.
На заметку
*
* В Kubernetes 1.10 было краткое время, когда дефолтом неожиданно стал
* Cilium тоже использует этот трюк чтобы следить за bpf мапами.
Полезные ссылки
• Документация
• CRI интерфейс
• man mount
#kubernetes #k8s #mountPropagation #linux #devops #storage
Недавно писал I/O-лимитер для LVM-томов: агент читает маунты подов, чтобы вычислить
MAJOR:MINOR для добавления в io.max, и всё ок… пока на узел не приедет новый Pod. Его маунта агент уже не видит. Перезапуск помогает, но это же костыль!Почему так происходит
* Каждый контейнер стартует в своём *privаte* mount-namespace -> изменения на хосте туда не пролетают.
* В Kubernetes это равно
mountPropagation: None.Как починить?
Параметр
mountPropagation у volumeMounts имеет 3 режима:* None - полная изоляция (`rprivate`), дефолт.
* HostToContainer - маунты летят *с хоста -> в контейнер* (`rslave`). Нам нужен именно он.
* Bidirectional - маунты ходят в обе стороны (`rshared`). Работает *только* в `privileged`-контейнере, иначе Pod не стартует.
Спека
spec:
containers:
- name: my-agent
image: my-agent-image
volumeMounts:
- name: kubelet-pods
mountPath: /var/lib/kubelet/pods
mountPropagation: HostToContainer
volumes:
- name: kubelet-pods
hostPath:
path: /var/lib/kubelet/pods
type: Directory
Теперь каждое *новое* bind-mount событие внутри
/var/lib/kubelet/pods мгновенно видно агенту - без рестартов.Что происходит под капотом
1. Kubelet видит
HostToContainer и пишет в CRI PROPAGATION_HOST_TO_CONTAINER. 2. CRI-shim (containerd/CRI-O) делает каталог
rslave. 3. Все будущие маунты, созданные kubelet’ом на хосте, автоматически “проливаются” в контейнер.
На заметку
*
Bidirectional опасен в мульти-тенант окружениях: контейнер может пролить маунты *на хост*. * В Kubernetes 1.10 было краткое время, когда дефолтом неожиданно стал
HostToContainer. Если админите древний кластер - проверьте (хотя боюсь вам уже ничего не поможет...). * Cilium тоже использует этот трюк чтобы следить за bpf мапами.
Полезные ссылки
• Документация
• CRI интерфейс
• man mount
#kubernetes #k8s #mountPropagation #linux #devops #storage
GitHub
Private mount propagation by jsafrane · Pull Request #62462 · kubernetes/kubernetes
This PR changes the default mount propagation from "rslave" (newly added in 1.10) to "private" (default in 1.9 and before). "rslave" as default...
👍12