Перейти к основному контенту

Admission Webhook for K8s

1. Обзор и документы для Casbin K8s-Gatekeeper

Casbin K8s-GateKeeper - это веб-хук приема Kubernetes, который интегрирует Casbin в качестве инструмента контроля доступа. Используя Casbin K8s-GateKeeper, вы можете установить гибкие правила для авторизации или перехвата любой операции над ресурсами K8s, НЕ пиша код, а только несколько строк декларативных конфигураций моделей и политик Casbin, которые являются частью языка Casbin ACL (Access Control List).

Casbin K8s-GateKeeper разработан и поддерживается сообществом Casbin. Репозиторий этого проекта доступен здесь: https://github.com/casbin/k8s-gatekeeper

0.1 Простой пример

Например, вам не нужно писать код, но используйте следующие строки конфигурации для достижения этой функции: "Запретить использование изображений с некоторыми указанными тегами в любых развертываниях":

Model:

[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)

And Policy:

p, "1.14.1",deny

Это обычный язык Casbin ACL. Предположим, вы уже прочитали главы о них, это будет очень легко понять.

У Casbin K8s-Gatekeeper есть следующие преимущества:

  • Легко использовать. Написание нескольких строк ACL гораздо лучше, чем написание множества кода.
  • Он позволяет горячее обновление конфигураций. Вам не нужно выключать весь плагин, чтобы изменить конфигурации.
  • Он гибкий. Произвольные правила могут быть сделаны на любом ресурсе K8s, который можно исследовать с помощью kubectl gatekeeper.
  • Он упрощает реализацию веб-хука приема K8s, который очень сложен. Вам не нужно знать, что такое веб-хук приема K8s или как для него писать код. Все, что вам нужно сделать, это знать ресурс, на который вы хотите наложить ограничения, а затем написать Casbin ACL. Все знают, что K8s сложен, но используя Casbin K8s-Gatekeeper, вы можете сэкономить время.
  • Он поддерживается сообществом Casbin. Не стесняйтесь обращаться к нам, если что-то в этом плагине смущает вас или если вы столкнулись с какими-либо проблемами при его использовании.

1.1 Как работает Casbin K8s-Gatekeeper?

K8s-Gatekeeper - это веб-хук приема для K8s, который использует Casbin для применения произвольных правил контроля доступа, определенных пользователем, чтобы помочь предотвратить любую операцию в K8s, которую администратор не хочет.

Casbin - это мощная и эффективная библиотека контроля доступа с открытым исходным кодом. Он предоставляет поддержку для принудительного выполнения авторизации на основе различных моделей контроля доступа. Для получения дополнительной информации о Casbin см. Обзор.

Веб-хуки приема в K8s - это HTTP-обратные вызовы, которые получают 'запросы на прием' и делают с ними что-то. В частности, K8s-Gatekeeper - это специальный тип веб-хука для приема: 'ValidatingAdmissionWebhook', который может решить, принять или отклонить этот запрос на прием или нет. Что касается запросов на прием, это HTTP-запросы, описывающие операцию над указанными ресурсами K8s (например, создание/удаление развертывания). Для получения дополнительной информации о веб-хуках для приема см. официальную документацию K8s.

1.2 Пример, иллюстрирующий его работу

Например, когда кто-то хочет создать развертывание, содержащее под, запускающий nginx (с использованием kubectl или клиентов K8s), K8s сгенерирует запрос на прием, который (если перевести в формат 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

Этот запрос пройдет через процесс всех промежуточных программ, показанных на картинке, включая наш K8s-Gatekeeper. K8s-Gatekeeper может обнаружить все обработчики Casbin, хранящиеся в etcd K8s, который создается и поддерживается пользователем (через kubectl или предоставляемый нами Go-клиент). Каждый обработчик содержит модель Casbin и политику Casbin. Запрос на прием будет обрабатываться каждым обработчиком, по очереди, и только после прохождения всех обработчиков запрос может быть принят этим K8s-Gatekeeper.

(Если вы не понимаете, что такое обработчик Casbin, модель или политика, см. этот документ: Начало работы).

Например, по какой-то причине администратор хочет запретить появление изображения 'nginx:1.14.1', разрешая 'nginx:1.3.1'. Можно создать обработчик, содержащий следующее правило и политику (Мы объясним, как создать обработчик, что такое эти модели и политики, и как их писать в следующих главах).

Model:

[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

Policy:

p, "nginx:1.13.1",allow
p, "nginx:1.14.1",deny

Создав обработчик, содержащий указанную выше модель и политику, предыдущий запрос на прием будет отклонен этим обработчиком, что означает, что K8s не создаст это развертывание.

2 Установка K8s-gatekeeper

Доступны три метода установки K8s-gatekeeper: внешний веб-хук, внутренний веб-хук и Helm.

заметка

Примечание: Эти методы предназначены только для того, чтобы пользователи могли опробовать K8s-gatekeeper, и они не являются безопасными. Если вы хотите использовать его в производственной среде, пожалуйста, убедитесь, что вы прочитали Глава 5. Расширенные настройки и внесите все необходимые изменения перед установкой.

2.1 Внутренний веб-хук

2.1.1 Шаг 1: Создание образа

Для метода внутреннего веб-хука сам веб-хук будет реализован в виде службы внутри Kubernetes. Чтобы создать необходимую службу и развертывание, вам нужно создать образ K8s-gatekeeper. Вы можете создать свой собственный образ, выполнив следующую команду:

docker build --target webhook -t k8s-gatekeeper .

Эта команда создаст локальный образ под названием 'k8s-gatekeeper:latest'.

заметка

Примечание: Если вы используете minikube, выполните eval $(minikube -p minikube docker-env) перед запуском 'docker build'.

2.1.2 Шаг 2: Настройка служб и развертываний для K8s-gatekeeper

Выполните следующие команды:

kubectl apply -f config/rbac.yaml
kubectl apply -f config/webhook_deployment.yaml
kubectl apply -f config/webhook_internal.yaml

Это запустит K8s-gatekeeper, и вы можете подтвердить это, выполнив kubectl get pods.

2.1.3 Шаг 3: Установка ресурсов CRD для K8s-gatekeeper

Выполните следующие команды:

kubectl apply -f config/auth.casbin.org_casbinmodels.yaml 
kubectl apply -f config/auth.casbin.org_casbinpolicies.yaml

2.2 Внешний веб-хук

Для метода внешнего веб-хука K8s-gatekeeper будет работать вне Kubernetes, и Kubernetes будет обращаться к K8s-gatekeeper, как к обычному веб-сайту. У Kubernetes есть обязательное требование, что веб-хук приема должен быть HTTPS. Для целей тестирования K8s-gatekeeper мы предоставили набор сертификатов и закрытый ключ (хотя это не безопасно). Если вы предпочитаете использовать свой собственный сертификат, обратитесь к Главе 5. Расширенные настройки для инструкций по настройке сертификата и закрытого ключа.

Сертификат, который мы предоставляем, выдан для 'webhook.domain.local'. Так что, измените хост (например, /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 Установка K8s-gatekeeper через 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 Создание/Обновление модели и политики Casbin через 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", эта модель (а также объект CasbinPolicy, связанный с этой моделью) будет игнорироваться.
  • spec.modelText: строка, содержащая текст модели Casbin.

Политика Casbin хранится в другом ресурсе CRD под названием 'CasbinPolicy', определение которого можно найти в config/auth.casbin.org_casbinpolicies.yaml.

Примеры находятся в example/allowed_repo/policy.yaml. Обратите внимание на следующие поля:

  • metadata.name: имя политики. Это имя ДОЛЖНО быть таким же, как имя объекта CasbinModel, связанного с этой политикой, чтобы K8s-gatekeeper мог связать их и создать принудительное применение.
  • spec.policyItem: строка, содержащая текст политики модели Casbin.

После создания ваших собственных файлов CasbinModel и CasbinPolicy используйте следующую команду для их применения:

kubectl apply -f <filename>

Как только пара CasbinModel и CasbinPolicy создана, K8s-gatekeeper сможет обнаружить ее в течение 5 секунд.

3.1.2 Создание/обновление модели и политики Casbin с помощью предоставленного нами go-клиента

Мы понимаем, что могут быть ситуации, когда неудобно использовать оболочку для выполнения команд непосредственно на узле кластера K8s, например, когда вы создаете автоматическую облачную платформу для вашей корпорации. Поэтому мы разработали go-клиент для создания и поддержания CasbinModel и CasbinPolicy.

Библиотека go-клиента находится в pkg/client.

В client.go мы предоставляем функцию для создания клиента.

func NewK8sGateKeeperClient(externalClient bool) (*K8sGateKeeperClient, error) 

Параметр externalClient определяет, работает ли K8s-gatekeeper внутри кластера K8s или нет.

В model.go мы предоставляем различные функции для создания, удаления и изменения CasbinModel. Вы можете узнать, как использовать эти интерфейсы в model_test.go.

В policy.go мы предоставляем различные функции для создания, удаления и изменения CasbiPolicy. Вы можете узнать, как использовать эти интерфейсы в policy_test.go.

3.1.2 Проверьте, работает ли K8s-gatekeeper

Предположим, вы уже создали точную модель и политику в example/allowed_repo. Теперь попробуйте следующую команду:

kubectl apply -f example/allowed_repo/testcase/reject_1.yaml

Вы должны обнаружить, что K8s отклонит этот запрос и укажет, что веб-хук был причиной отклонения этого запроса. Однако, когда вы попытаетесь применить example/allowed_repo/testcase/approve_2.yaml, он будет принят.

4. Как написать модель и политику с K8s-gatekeeper

Прежде всего, убедитесь, что вы знакомы с основным синтаксисом моделей и политик Casbin. Если вы не знакомы, сначала прочтите раздел Get Started. В этой главе мы предполагаем, что вы уже понимаете, что такое модели и политики Casbin.

4.1 Определение запроса модели

Когда K8s-gatekeeper авторизует запрос, входом всегда является объект: объект Go запроса на прием. Это означает, что принудительное применение всегда будет использоваться так:

ok, err := enforcer.Enforce(admission)

где admission - это объект AdmissionReview, определенный официальным go api K8s '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 предоставляет различные 'функции Casbin' в качестве расширений для реализации этих функций. Если вы все еще обнаруживаете, что ваши требования не могут быть удовлетворены этими расширениями, не стесняйтесь начать обсуждение проблемы или создать запрос на включение изменений.

Если вы не знакомы с функциями Casbin, вы можете обратиться к Function для получения дополнительной информации.

Вот функции расширения:

4.2.1 Функции расширения

4.2.1.1 доступ

Доступ используется для решения проблемы, что Casbin не поддерживает индексацию в картах или массивах. Пример 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 - это срез.

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 - это карта, а Value() - простая функция, которая не имеет параметров и возвращает одно значение.

4.2.1.2 accessWithWildcard

Иногда у вас может возникнуть такое требование: все элементы в массиве должны иметь префикс "aaa". Однако Casbin не поддерживает циклы 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(): принимает несколько параметров и возвращает, равен ли любой параметр (кроме последнего параметра) последнему параметру.
  • split(a,b,c...,sep,index): возвращает срез, который содержит [splits(a,sep)[index], splits(b,sep)[index], splits(a,sep)[index], ...].
  • len(): возвращает длину переменного числа аргументов.
  • matchRegex(a,b,c...,regex): возвращает, соответствуют ли все заданные параметры (a, b, c, ...) заданному регулярному выражению.

Вот пример в 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"], поскольку splits поддерживает аргументы переменной длины и выполняет операцию разделения для каждого элемента, элемент с индексом 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(): Преобразует целое число в число с плавающей точкой (это необходимо, потому что любое число, используемое в сравнении, должно быть преобразовано в число с плавающей точкой).
  • ToString(): Преобразует объект в строку. Этот объект должен иметь базовый тип строки (например, объект типа XXX, когда есть утверждение type XXX string).
  • IsNil(): Возвращает, является ли параметр nil.

5. Расширенные настройки

5.1 О сертификатах

В Kubernetes (k8s) обязательно использование HTTPS для веб-хука. Есть два способа достижения этого:

  • Использование самоподписанных сертификатов (примеры в этом репозитории используют этот метод)
  • Использование обычного сертификата

5.1.1 Самоподписанные сертификаты

Использование самоподписанного сертификата означает, что сертификат выдается не одним из известных удостоверяющих центров (CA). Поэтому вы должны сообщить k8s об этом CA.

В настоящее время пример в этом репозитории использует самодельный CA, чьи закрытый ключ и сертификат хранятся в config/certificate/ca.crt и config/certificate/ca.key соответственно. Сертификат для веб-хука - это config/certificate/server.crt, который выдан самодельным CA. Домены этого сертификата - "webhook.domain.local" (для внешнего веб-хука) и "casbin-webhook-svc.default.svc" (для внутреннего веб-хука).

Информация о CA передается в k8s через файлы конфигурации веб-хука. Оба config/webhook_external.yaml и config/webhook_internal.yaml имеют поле под названием "CABundle", которое содержит строку, закодированную в base64, сертификата CA.

Если вам нужно изменить сертификат/домен (например, если вы хотите поместить этот веб-хук в другое пространство имен k8s при использовании внутреннего веб-хука, или если вы хотите изменить домен при использовании внешнего веб-хука), следует выполнить следующие процедуры:

  1. Создать новый CA:

    • Сгенерируйте закрытый ключ для поддельного УЦ:

      openssl genrsa -des3 -out ca.key 2048
    • Удалите защиту паролем закрытого ключа:

      openssl rsa -in ca.key -out ca.key
  2. Сгенерируйте закрытый ключ для сервера веб-хука:

    openssl genrsa -des3 -out server.key 2048
    openssl rsa -in server.key -out server.key
  3. Используйте самостоятельно созданный УЦ для подписи сертификата для веб-хука:

    • Скопируйте файл конфигурации openssl вашей системы для временного использования. Вы можете узнать местоположение файла конфигурации, запустив openssl version -a, обычно он называется openssl.cnf.

    • В файле конфигурации:

      • Найдите параграф [req] и добавьте следующую строку: req_extensions = v3_req

      • Найдите параграф [v3_req] и добавьте следующую строку: subjectAltName = @alt_names

      • Добавьте следующие строки в файл:

        [alt_names]
        DNS.2=<The domain you want>

        Примечание: Замените 'casbin-webhook-svc.default.svc' на реальное имя вашего сервиса, если вы решите изменить имя сервиса.

    • Используйте измененный файл конфигурации для создания файла запроса на сертификат:

      openssl req -new -nodes -keyout server.key -out server.csr -config openssl.cnf
    • Используйте самодельный УЦ для ответа на запрос и подписи сертификата:

      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
  4. Замените поле 'CABundle': В обоих config/webhook_external.yaml и config/webhook_internal.yaml есть поле под названием "CABundle", которое содержит строку, закодированную в base64, сертификата УЦ. Обновите это поле новым сертификатом.

  5. Если вы используете helm, аналогичные изменения должны быть применены к helm charts.

5.1.2 Легальные сертификаты

Если вы используете легальные сертификаты, вам не нужно проходить все эти процедуры. Удалите поле "CABundle" в config/webhook_external.yaml и config/webhook_internal.yaml, и измените домен в этих файлах на домен, которым вы владеете.