Привет, хабравчане!

Слышали ли вы, что такое Kyverno и зачем он нужен? В этой статье расскажу и покажу на примерах, как мы его используем.

Меня зовут Макарий Балашов, я SRE в Ak Bars Digital. Наша команда занимается подготовкой и развитием инфраструктуры для команд разработки.

Что такое этот ваш Kyverno?

Kyverno — policy engine разработанный специально для Kubernetes.

Kyverno позволяет администраторам кластеров управлять специфическими конфигурациями среды независимо от конфигураций ресурсов, применять передовые методы настройки для своих кластеров. Kyverno можно использовать для сканирования существующих ресурсов на наличие best practises или для соблюдения их путем блокировки или изменения запросов API.

Вот небольшой список крутых фишек Kyverno:

  • policy как ресурсы kubernetes (yaml манифесты);

  • возможность валидировать, изменять или создавать ресурсы k8s;

  • проверка образов контейнеров для обеспечения безопасности цепочки поставок ПО;

  • проверка метадаты образов;

  • синхронизация конфигурации в Namespace’ах;

  • блокировка несовместимые ресурсы с помощью элементов управления доступом или сообщать о нарушениях политики;

  • управление политиками как кодом с помощью знакомых инструментов, таких как git и kustomize;

  • постоянно расширяемая библиотека готовых политик (уже 244).

В качестве альтернатив Kyverno можно выделить OPA/GatekeeperKubewarden и jsPolicy.

Про сравнение этих решений есть много информации в сети, например тут сравнивают Kyverno и Gatekeeper.

Зачем это нам?

В 1.21 PodSecurityPolicy стала депрекейтед фичей Kubernetes, а в 1.25 была вовсе удалена.

Перед нами стал вопрос, а как дальше жить? И мы решили потестить разные решения, выбор остановился на Kyverno. Нас она очень сильно порадовала и удовлетворила наши потребности, как замена PSP.

На фоне альтернатив Kyverno выделился большим функционалом сразу из коробки и простой лексикой (обычный yaml), что позволяет новым сотрудникам гораздо быстрее разобраться, как с ним работать.

Как она работает?

Kyverno работает как динамический контроллер допуска в кластере Kubernetes. Kyverno получает проверяющие и изменяющие вебхуки допуска от kube-apiserver. Применяет соответствующие политики для возврата результатов, которые применяют политики допуска или отклоняют запросы.

На рисунке показана верхнеуровневая архитектура работы Kyverno. Источник.
На рисунке показана верхнеуровневая архитектура работы Kyverno. Источник.

Для чего используем?

Изначально мы планировали использовать Kyverno только как замену PodSecurityPolicy. Начали с переписования уже существующих манифестов PSP в манифесты политик Kyverno c Validate правилами.

Validate Resources

Например, мы сразу хотели запретить запуск privilege контейнеров, так как это чревато тем, что под может получить доступ к ресурсам хоста и возможностям ядра.

Политика которую мы используем
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: disallow-privileged-containers
  annotations:
    policies.kyverno.io/title: Disallow Privileged Containers
    policies.kyverno.io/category: Pod Security Standards (Baseline)
    policies.kyverno.io/severity: medium
    policies.kyverno.io/subject: Pod
    kyverno.io/kyverno-version: 1.6.0
    kyverno.io/kubernetes-version: "1.22-1.23"
    policies.kyverno.io/description: >-
      Privileged mode disables most security mechanisms and must not be allowed. This policy
      ensures Pods do not call for privileged mode.      
spec:
  validationFailureAction: audit
  background: true
  rules:
    - name: privileged-containers
      match:
        any:
        - resources:
            kinds:
              - Pod
      validate:
        message: >-
          Privileged mode is disallowed. The fields spec.containers[*].securityContext.privileged
          and spec.initContainers[*].securityContext.privileged must be unset or set to `false`.          
        pattern:
          spec:
            =(ephemeralContainers):
              - =(securityContext):
                  =(privileged): "false"
            =(initContainers):
              - =(securityContext):
                  =(privileged): "false"
            containers:
              - =(securityContext):
                  =(privileged): "false"

Mutating Resources

Далее, мы решили сразу выставлять нужный нам securityContext для всех ресурсов, которые будут создаваться в нашем кластере. Вот что у нас получилось:

Политика для добавления ресурсам securityContext
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: add-default-securitycontext
  annotations:
    policies.kyverno.io/title: Add Default securityContext
    policies.kyverno.io/category: Sample
    policies.kyverno.io/subject: Pod
    policies.kyverno.io/description: >-
      A Pod securityContext entry defines fields such as the user and group which should be used to run the Pod.
      Sometimes choosing default values for users rather than blocking is a better alternative to not impede
      such Pod definitions. This policy will mutate a Pod to set default securityContext    
spec:
  background: false
  rules:
  - name: add-default-securitycontext-containers
    match:
      any:
      - resources:
          kinds:
          - Pod
    preconditions:
      all:
      - key: "{{request.operation}}"
        operator: In
        value:
          - CREATE
          - UPDATE
    mutate:
      foreach:
      - list: "request.object.spec.containers"
        patchStrategicMerge:
          spec:
            securityContext:
              runAsUser: 10001
              runAsGroup: 10001
              fsGroup: 10001
              seccompProfile:
                type: RuntimeDefault
            containers:
            - name: "{{ element.name }}"
              securityContext:
                runAsUser: 10001
                runAsGroup: 10001
                capabilities:
                  drop:
                    - ALL
                runAsNonRoot: true
                allowPrivilegeEscalation: false
								privileged: false
                seccompProfile:
                  type: RuntimeDefault
  - name: add-default-securitycontext-initContainers
    match:
      any:
      - resources:
          kinds:
          - Pod
    preconditions:
      all:
      - key: "{{request.operation}}"
        operator: In
        value:
          - CREATE
          - UPDATE
      - key: "{{ request.object.spec.initContainers[] || '' | length(@) }}"
        operator: GreaterThanOrEquals
        value: 1
    mutate:
      foreach:
      - list: "request.object.spec.initContainers"
        patchStrategicMerge:
          spec:
            securityContext:
              runAsUser: 10001
              fsGroup: 10001
              seccompProfile:
                type: RuntimeDefault
            initContainers:
            - name: "{{ element.name }}"
              securityContext:
                runAsUser: 10001
                runAsGroup: 10001
                capabilities:
                  drop:
                    - ALL
                runAsNonRoot: true
                allowPrivilegeEscalation: false
								privileged: false
                seccompProfile:
                  type: RuntimeDefault

Generate Resources

Как пример использования generate-политик сразу приходит в голову копирование секрета с кредами от private registry (dockerconfigjson) в каждый Namespace.

Мы также нашли следующее применение — копирование секрета с самоподписанным рутовым сертификатом, который генерирует cert-manager, в каждый namespace для последующей инъекции при помощи другой политики в контейнеры. Обратите внимание на synchronize, который позволяет Kyverno автоматически обновлять содержимое всех созданных ею ресурсами при изменении изначального. В нашем случае — это обновление сертификата во всех секретах после автоматического перевыпуска cert-manager’ом.

Копирование секрета по Namespac’ам
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: clone-cert
  annotations:
    policies.kyverno.io/title: Clone Certificate's secret
    policies.kyverno.io/category: Cert-Manager
    policies.kyverno.io/subject: Certificate, Namespace
    policies.kyverno.io/description: >-
      Clone certificate's secret to every namespace
spec:
  background: true
  rules:
  - name: clone-cert
    match:
      any:
      - resources:
          kinds:
          - Namespace
    exclude:
      any:
      - resources:
          namespaces:
          - "kube-system"
          - "kube-public"
          - "default"
    generate:
      synchronize: true
      apiVersion: v1
      kind: Secret
      name: base-cert
      namespace: "{{request.object.metadata.name}}"
      clone:
        namespace: cert-manager
        name: base-cert

Пример политики, которой делаем инъекцию
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: add-certificates-volume
  annotations:
    policies.kyverno.io/title: Add Certificates as a Volume
    policies.kyverno.io/category: Sample
    policies.kyverno.io/subject: Pod,Volume
    kyverno.io/kyverno-version: 1.5.2
    kyverno.io/kubernetes-version: "1.21"
    policies.kyverno.io/minversion: 1.5.0
    pod-policies.kyverno.io/autogen-controllers: DaemonSet,Deployment,Job,StatefulSet
    policies.kyverno.io/description: >-
      In some cases you would need to trust custom CA certificates for all the containers of a Pod.
      It makes sense to be in a Secret so that you can automount them by only setting an annotation.
      This policy adds a volume to all containers in a Pod containing the certificate if the annotation
      called `inject-certs` with value `enabled` is found.      
spec:
  background: true
  rules:
  - name: add-ssl-certs
    match:
      any:
      - resources:
          kinds:
          - Pod
    exclude:
      any:
      - resources:
          namespaces:
          - "kube-system"
          - "kube-public"
          - "default"
    preconditions:
      all:
      - key: "{{request.operation}}"
        operator: In
        value:
          - CREATE
          - UPDATE
    mutate:
      foreach:
      - list: "request.object.spec.containers" 
        patchStrategicMerge:
          spec:
            containers:
            - name: "{{ element.name }}"
              volumeMounts:
              - name: ssl-certs
                mountPath: /etc/ssl/certs
            volumes:
            - name: ssl-certs
              secret:
                secretName: base-cert

Verify Image

Также, при помощи Kyverno и Sigstore Cosign можно проверять и подписывать образы контейнеров. Мы только внедряем эти фичи. Подборку статей по этой теме можно посмотреть в  телеграмм канале k8s (in)security

Заключение

Kyverno великолепен, потому что позволяет нам последовательно управлять нашими кластерами на уровне кластера без реального кода. Недопустимые ресурсы могут быть заблокированы с всплывающими сообщениями об ошибках для пользователей. Неправильно настроенные ресурсы могут быть исправлены на лету, а новые ресурсы могут быть динамически созданы. С этим инструментом мы получили очень приятный опыт управления кластерами Kubernetes. И будем дальше его использовать.

Ссылки, которые могут быть вам полезны, если захотите использовать Kyverno в своих проектах:

Комментарии (3)


  1. D1abloRUS
    17.01.2023 18:51
    +1

    Ох уж эта yaml разработка...

    Хватило вот у кого то фантазии взять нормальный язык


    1. r0binak
      17.01.2023 22:56

      Как по мне, Kyverno один из самых удобных Policy Engine, только потому что правила там описываются декларативно в YAML. Kubernetes Native всё-таки)


      1. D1abloRUS
        17.01.2023 23:23

        Так то клиент yaml в json конвертит... но суть не в этом, снова скрестили ужа с ежом, синтаксис такой, что без доки это невозможно не читать, не писать