Kubernetes Admission Webhook
Обзор
Casbin K8s-Gatekeeper — это admission webhook для Kubernetes, использующий Casbin для авторизации. Вы определяете модели и политики декларативно, чтобы разрешать или запрещать операции с любыми ресурсами Kubernetes — без написания пользовательского кода в вебхуке. Поддерживается сообществом Casbin: github.com/apache/casbin-k8s-gatekeeper.
Простой пример
Пример: запрет деплояментов, использующих образы с определённым тегом, только через конфигурацию:
Модель:
[request_definition]
r = obj
[policy_definition]
p = obj,eft
[policy_effect]
e = !some(where (p.eft == deny))
[matchers]
m = r.obj.Request.Namespace == "default" && r.obj.Request.Resource.Resource =="deployments" && \
contain(split(accessWithWildcard(${OBJECT}.Spec.Template.Spec.Containers , "*", "Image"),":",1) , p.obj)
Политика:
p, "1.14.1",deny
Здесь используется стандартный ACL-язык Casbin, который должен быть понятен, если вы прочитали вводные главы.
Casbin K8s-Gatekeeper предлагает несколько преимуществ:
- Простота использования — пишите ACL-конфигурации вместо объёмного кода
- Поддержка обновления конфигурации «на лету» без перезапуска плагина
- Гибкость — применяйте произвольные правила к любым ресурсам Kubernetes с помощью
kubectl gatekeeper - Упрощает реализацию admission webhook для Kubernetes — не нужно разбираться во внутренних механизмах вебхуков или писать код для них. Просто определите ограничения и напишите ACL на Casbin.
- Поддерживается сообществом — обращайтесь к нам с вопросами или по проблемам
1.1 Как работает Casbin K8s-Gatekeeper
K8s-Gatekeeper — это admission webhook для Kubernetes, который использует Casbin для применения пользовательских правил контроля доступа, предотвращая нежелательные операции с ресурсами Kubernetes.
Casbin — это эффективная библиотека управления доступом с открытым исходным кодом, поддерживающая различные модели авторизации. Подробности см. в разделе Обзор.
Admission webhooks в Kubernetes — это HTTP-колбэки, которые получают и обрабатывают запросы на допуск (admission requests). K8s-Gatekeeper является ValidatingAdmissionWebhook, который принимает или отклоняет такие запросы. Запросы на допуск — это HTTP-запросы, описывающие операции с ресурсами Kubernetes (например, создание или удаление деплоемента). Дополнительную информацию см. в документации Kubernetes.
1.2 Пример рабочего процесса
Когда кто-то создаёт деплоймент с pod'ом nginx (через kubectl или клиенты Kubernetes), Kubernetes генерирует запрос на допуск вида (в формате YAML):
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
selector:
matchLabels:
app: nginx
replicas: 1
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.14.1
ports:
- containerPort: 80
Этот запрос проходит через слои middleware, включая K8s-Gatekeeper. K8s-Gatekeeper обнаруживает все enforcer'ы Casbin, сохранённые в etcd Kubernetes (созданные и поддерживаемые пользователями через kubectl или предоставленный Go-клиент). Каждый enforcer содержит модель Casbin и политику. Запрос на допуск последовательно оценивается каждым enforcer'ом и должен пройти все из них, чтобы быть принятым.
(Если вы не знакомы с enforcer'ами, моделями или политиками Casbin, см. Начало работы).
Например, если администратор хочет заблокировать образ 'nginx:1.14.1', но разрешить 'nginx:1.3.1', он может создать enforcer с такой моделью и политикой (подробности создания и настройки приведены в последующих разделах):
Модель:
[request_definition]
r = obj
[policy_definition]
p = obj,eft
[policy_effect]
e = !some(where (p.eft == deny))
[matchers]
m = r.obj.Request.Namespace == "default" && r.obj.Request.Resource.Resource =="deployments" && \
access(r.obj.Request.Object.Object.Spec.Template.Spec.Containers , 0, "Image") == p.obj
Политика:
p, "nginx:1.13.1",allow
p, "nginx:1.14.1",deny
Создание enforcer'а с этой моделью и политикой приведёт к отклонению запроса на допуск, что помешает Kubernetes создать деплоймент.
2. Установка K8s-gatekeeper
Доступны три способа установки: External webhook, Internal webhook и Helm.
Эти способы установки предназначены только для оценки. Для продакшн-развёртываний изучите Главу 5. Расширенные настройки и примените необходимые изменения безопасности перед установкой.
2.1 Internal Webhook
2.1.1 Шаг 1: Сборка образа
Для развёртывания internal webhook K8s-gatekeeper работает как сервис Kubernetes. Соберите образ:
docker build --target webhook -t k8s-gatekeeper .
Это создаст локальный образ с именем 'k8s-gatekeeper:latest'.
For minikube users, run eval $(minikube -p minikube docker-env) before 'docker build'.
2.1.2 Шаг 2: Развёртывание сервисов и ресурсов
Выполните эти команды:
kubectl apply -f config/rbac.yaml
kubectl apply -f config/webhook_deployment.yaml
kubectl apply -f config/webhook_internal.yaml
Проверьте развёртывание командой kubectl get pods.
2.1.3 Шаг 3: Установка ресурсов CRD
Установите определения пользовательских ресурсов (Custom Resource Definitions):
kubectl apply -f config/auth.casbin.org_casbinmodels.yaml
kubectl apply -f config/auth.casbin.org_casbinpolicies.yaml
2.2 External Webhook
Для развёртывания external webhook K8s-gatekeeper работает вне кластера Kubernetes. Kubernetes требует HTTPS для admission webhooks. Мы предоставляем тестовые сертификаты и приватный ключ (не безопасны для продакшена). Для использования собственных сертификатов см. Главу 5. Расширенные настройки.
Предоставленный сертификат выпущен для 'webhook.domain.local'. Обновите файл hosts (например, /etc/hosts), чтобы направить 'webhook.domain.local' на IP-адрес, где работает K8s-gatekeeper.
Выполните:
go mod tidy
go mod vendor
go run cmd/webhook/main.go
kubectl apply -f config/auth.casbin.org_casbinmodels.yaml
kubectl apply -f config/auth.casbin.org_casbinpolicies.yaml
kubectl apply -f config/webhook_external.yaml
2.3 Установка через Helm
2.3.1 Шаг 1: Сборка образа
См. Глава 2.1.1.
2.3.2 Установка Helm
Запустите: helm install k8sgatekeeper ./k8sgatekeeper
3. Использование K8s-gatekeeper
3.1 Создание моделей и политик Casbin
Создавайте модели и политики с помощью kubectl или предоставленного Go-клиента.
3.1.1 Создание/обновление через kubectl
В K8s-gatekeeper модели Casbin хранятся как ресурсы CRD 'CasbinModel'. Определение находится в config/auth.casbin.org_casbinmodels.yaml.
Примеры см. в example/allowed_repo/model.yaml. Важные поля:
- metadata.name: Имя модели. Должно совпадать с именем связанного объекта CasbinPolicy, чтобы K8s-gatekeeper корректно их связал.
- spec.enable: Установите "false", чтобы отключить эту модель и её политику.
- spec.modelText: Строка, содержащая определение модели Casbin.
Политики Casbin хранятся как ресурсы CRD 'CasbinPolicy', определённые в config/auth.casbin.org_casbinpolicies.yaml.
Примеры см. в example/allowed_repo/policy.yaml. Важные поля:
- metadata.name: Имя политики. Должно совпадать с именем связанного объекта CasbinModel.
- spec.policyItem: Строка, содержащая определение политики Casbin.
Примените ваши файлы CasbinModel и CasbinPolicy:
kubectl apply -f <filename>
K8s-gatekeeper обнаруживает новые пары CasbinModel/CasbinPolicy в течение 5 секунд.
3.1.2 Создание/обновление через Go-клиент
Для сценариев, когда прямой доступ к узлам кластера через оболочку невозможен (например, при создании автоматизированных облачных платформ), мы предоставляем Go-клиент для управления ресурсами CasbinModel и CasbinPolicy.
Библиотека Go-клиента находится в pkg/client.
В client.go используйте эту функцию для создания клиента:
func NewK8sGateKeeperClient(externalClient bool) (*K8sGateKeeperClient, error)
Параметр externalClient указывает, работает ли K8s-gatekeeper внутри или вне кластера Kubernetes.
В model.go предоставлены функции для создания, удаления и изменения CasbinModel. Примеры использования см. в model_test.go.
В policy.go предоставлены функции для создания, удаления и изменения CasbinPolicy. Примеры использования см. в policy_test.go.
3.1.2 Тестирование K8s-gatekeeper
После создания модели и политики из example/allowed_repo, протестируйте с помощью:
kubectl apply -f example/allowed_repo/testcase/reject_1.yaml
Kubernetes должен отклонить этот запрос, указав webhook как причину отказа. Однако применение example/allowed_repo/testcase/approve_2.yaml должно пройти успешно.
4. Написание моделей и политик для K8s-gatekeeper
Убедитесь, что вы понимаете синтаксис моделей и политик Casbin перед продолжением. Если нет, сначала прочитайте раздел Get Started. Эта глава предполагает знакомство с моделями и политиками Casbin.
4.1 Определение запроса
При оценке запроса K8s-gatekeeper входными данными всегда является объект AdmissionReview на языке Go. Enforcer используется следующим образом:
ok, err := enforcer.Enforce(admission)
где admission — это объект AdmissionReview из официального Go API Kubernetes "k8s.io/api/admission/v1". Определение структуры доступно по ссылке: https://github.com/kubernetes/api/blob/master/admission/v1/types.go. Дополнительная документация: https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#webhook-request-and-response.
Для моделей K8s-gatekeeper секция request_definition должна всегда следовать этому формату:
[request_definition]
r = obj
Имя 'obj' можно изменить, если оно последовательно используется в секции [matchers].
4.2 Матчеры модели
Используйте функцию ABAC в Casbin для написания правил. Однако встроенный оценщик выражений Casbin не поддерживает по умолчанию индексацию карт/массивов или раскрытие массивов. K8s-gatekeeper предоставляет расширенные функции для решения этой задачи. Если вам нужна дополнительная функциональность, пожалуйста, откройте issue или отправьте pull request.
Для получения справочной информации о функциях Casbin см. Function.
Расширенные функции:
4.2.1 Расширенные функции
4.2.1.1 access
Функция access позволяет выполнять индексацию карт и массивов. См. example/allowed_repo/model.yaml:
[matchers]
m = r.obj.Request.Namespace == "default" && r.obj.Request.Resource.Resource =="deployments" && \
access(r.obj.Request.Object.Object.Spec.Template.Spec.Containers , 0, "Image") == p.obj
Здесь access(r.obj.Request.Object.Object.Spec.Template.Spec.Containers , 0, "Image") эквивалентно r.obj.Request.Object.Object.Spec.Template.Spec.Containers[0].Image, где r.obj.Request.Object.Object.Spec.Template.Spec.Containers является срезом (slice).
access также может вызывать функции без параметров, возвращающие одно значение. См. example/container_resource_limit/model.yaml:
[matchers]
m = r.obj.Request.Namespace == "default" && r.obj.Request.Resource.Resource =="deployments" && \
parseFloat(access(r.obj.Request.Object.Object.Spec.Template.Spec.Containers , 0, "Resources","Limits","cpu","Value")) >= parseFloat(p.cpu) && \
parseFloat(access(r.obj.Request.Object.Object.Spec.Template.Spec.Containers , 0, "Resources","Limits","memory","Value")) >= parseFloat(p.memory)
Здесь access(r.obj.Request.Object.Object.Spec.Template.Spec.Containers , 0, "Resources","Limits","cpu","Value") равно r.obj.Request.Object.Object.Spec.Template.Spec.Containers[0].Resources.Limits["cpu"].Value(), где r.obj.Request.Object.Object.Spec.Template.Spec.Containers[0].Resources.Limits является картой (map), а Value() — функцией без параметров, возвращающей одно значение.
4.2.1.2 accessWithWildcard
Чтобы проверить условия для всех элементов массива (например, «все элементы должны начинаться с 'aaa'») без использования циклов for, и спользуйте accessWithWildcard с раскрытием карты/среза.
Если a.b.c — это массив [aaa,bbb,ccc,ddd,eee], то accessWithWildcard(a,"b","c","*") вернет срез [aaa,bbb,ccc,ddd,eee]. Подстановка * раскрывает срез.
Поддерживаются несколько подстановок. Например, accessWithWildcard(a,"b","c","*","*") вернет [a.b.c[0][0], a.b.c[0][1], ..., a.b.c[1][0], a.b.c[1][1], ...].
4.2.1.3 Функции с переменным числом аргументов
Оценщик выражений Casbin автоматически раскрывает массивы как аргументы переменной длины. Используя эту возможность для раскрытия массивов/срезов/карт, некоторые функции принимают массивы/срезы:
- contain(): Accepts multiple parameters and returns whether any parameter (except the last) equals the last parameter.
- split(a,b,c...,sep,index): Returns
[splits(a,sep)[index], splits(b,sep)[index], splits(c,sep)[index], ...]. - len(): Returns the variable-length argument count.
- matchRegex(a,b,c...,regex): Returns whether all parameters (
a,b,c, ...) match the regex.
Пример из example/disallowed_tag/model.yaml:
[matchers]
m = r.obj.Request.Namespace == "default" && r.obj.Request.Resource.Resource =="deployments" && \
contain(split(accessWithWildcard(r.obj.Request.Object.Object.Spec.Template.Spec.Containers , "*", "Image"),":",1) , p.obj)
Если accessWithWildcard(r.obj.Request.Object.Object.Spec.Template.Spec.Containers , "*", "Image") возвращает ["a:b", "c:d", "e:f", "g:h"], операция split обрабатывает каждый элемент с поддержкой аргументов переменной длины, выбирая индекс 1 из каждого. Таким образом, split(accessWithWildcard(r.obj.Request.Object.Object.Spec.Template.Spec.Containers , "*", "Image"),":",1) возвращает ["b","d","f","h"]. Наконец, contain(split(accessWithWildcard(r.obj.Request.Object.Object.Spec.Template.Spec.Containers , "*", "Image"),":",1) , p.obj) проверяет, существует ли p.obj в списке ["b","d","f","h"].
4.2.1.2 Функции преобразования типов
- ParseFloat(): Converts integers to floats (required because comparisons need float type).
- ToString(): Converts objects to strings. The object must have an underlying string type (e.g.,
type XXX string). - IsNil(): Returns whether the parameter is nil.
5. Дополнительные настройки
5.1 Конфигурация сертификатов
Kubernetes требует, чтобы вебхуки использовали HTTPS. Доступны два варианта:
- Самоподписанные сертификаты (используются в примерах этого репозитория)
- Публично доверенные сертификаты
5.1.1 Самоподписанные сертификаты
Самоподписанные сертификаты используют центр сертификации (CA), который не является публично признанным. Вам необходимо настроить Kubernetes на доверие к этому CA.
В примерах репозитория используется пользовательский CA с закрытым ключом и сертификатом, хранящимися в config/certificate/ca.key и config/certificate/ca.crt. Сертификат вебхука config/certificate/server.crt выпущен этим CA для доменов "webhook.domain.local" (внешний вебхук) и "casbin-webhook-svc.default.svc" (внутренний вебхук).
Информация о CA передается в Kubernetes через файлы конфигурации вебхуков. Оба файла config/webhook_external.yaml и config/webhook_internal.yaml содержат поле "CABundle" с сертификатом CA, закодированным в base64.
Чтобы изменить сертификат/домен (например, при перемещении вебхука в другое пространство имен или изменении доменов):
-
Сгенерируйте новый CA:
-
Сгенерируйте закрытый ключ CA:
openssl genrsa -des3 -out ca.key 2048 -
Удалите защиту паролем:
openssl rsa -in ca.key -out ca.key
-
-
Сгенерируйте закрытый ключ сервера вебхука:
openssl genrsa -des3 -out server.key 2048openssl rsa -in server.key -out server.key -
Подпишите сертификат вебхука с помощью CA:
-
Скопируйте файл конфигурации OpenSSL вашей системы (найдите его с помощью
openssl version -a, обычно этоopenssl.cnf). -
Измените файл конфигурации:
-
В секции
[req]добавьте:req_extensions = v3_req -
В секции
[v3_req]добавьте:subjectAltName = @alt_names -
Добавьте в конец:
[alt_names]DNS.2=<Your desired domain>Примечание: Замените 'casbin-webhook-svc.default.svc' на фактическое имя вашего сервиса, если оно было изменено.
-
-
Создайте запрос на выпуск сертификата:
openssl req -new -nodes -keyout server.key -out server.csr -config openssl.cnf -
Подпишите сертификат с помощью CA:
openssl x509 -req -days 3650 -in server.csr -out server.crt -CA ca.crt -CAkey ca.key -CAcreateserial -extensions v3_req -extensions SAN -extfile openssl.cnf
-
-
Обновите поле 'CABundle' в файлах
config/webhook_external.yamlиconfig/webhook_internal.yaml, указав новый сертификат CA, закодированный в base64. -
Для развертываний через Helm внесите аналогичные изменения в чарты Helm.