company_banner

Азбука безопасности в Kubernetes: аутентификация, авторизация, аудит



    Рано или поздно в эксплуатации любой системы встаёт вопрос безопасности: обеспечения аутентификации, разделения прав, аудита и других задач. Для Kubernetes уже создано множество решений, которые позволяют добиться соответствия стандартам даже в весьма требовательных окружениях… Этот же материал посвящён базовым аспектам безопасности, реализованным в рамках встроенных механизмов K8s. В первую очередь он будет полезен тем, кто начинает знакомиться с Kubernetes, — как отправная точка для изучения вопросов, связанных с безопасностью.

    Аутентификация


    В Kubernetes есть два типа пользователей:

    • Service Accounts — аккаунты, управляемые Kubernetes API;
    • Users — «нормальные» пользователи, управляемые внешними, независимыми сервисами.

    Основное отличие этих типов в том, что для Service Accounts существуют специальные объекты в Kubernetes API (они так и называются — ServiceAccounts), которые привязаны к пространству имён и набору авторизационных данных, хранящихся в кластере в объектах типа Secrets. Такие пользователи (Service Accounts) предназначены в основном для управления правами доступа к Kubernetes API процессов, работающих в кластере Kubernetes.

    Обычные же Users не имеют записей в Kubernetes API: управление ими должно осуществляться внешними механизмами. Они предназначены для людей или процессов, живущих вне кластера.

    Каждый запрос к API привязан либо к Service Account, либо к User, либо считается анонимным.

    Аутентификационные данные пользователя включают в себя:

    • Username — имя пользователя (зависит от регистра!);
    • UID — машинно-читаемая строка идентификации пользователя, которая «более консистентна и уникальна, чем имя пользователя»;
    • Groups — список групп, к которым принадлежит пользователь;
    • Extra — дополнительные поля, которые могут быть использованы механизмом авторизации.

    Kubernetes может использовать большое количество механизмов аутентификации: сертификаты X509, Bearer-токены, аутентифицирующий прокси, HTTP Basic Auth. При помощи этих механизмов можно реализовать большое количество схем авторизации: от статичного файла с паролями до OpenID OAuth2.

    Более того, допускается использование нескольких схем авторизации одновременно. По умолчанию в кластере используются:

    • service account tokens — для Service Accounts;
    • X509 — для Users.

    Вопрос про управление ServiceAccounts выходит за рамки данной статьи, а желающим подробнее ознакомиться с этим вопросом рекомендую начать со страницы официальной документации. Мы же рассмотрим подробнее вопрос работы сертификатов X509.

    Сертификаты для пользователей (X.509)


    Классический способ работы с сертификатами предполагает:

    • генерацию ключа:

      mkdir -p ~/mynewuser/.certs/
      openssl genrsa -out ~/.certs/mynewuser.key 2048
    • генерацию запроса на сертификат:

      openssl req -new -key ~/.certs/mynewuser.key -out ~/.certs/mynewuser.csr -subj "/CN=mynewuser/O=company"
    • обработку запроса на сертификат при помощи ключей CA кластера Kubernetes, получение сертификата пользователя (для получения сертификата нужно использовать учетную запись, имеющую доступ к ключу центра сертификации кластера Kubernetes, который по умолчанию находится в /etc/kubernetes/pki/ca.key):

      openssl x509 -req -in ~/.certs/mynewuser.csr -CA /etc/kubernetes/pki/ca.crt -CAkey /etc/kubernetes/pki/ca.key -CAcreateserial -out ~/.certs/mynewuser.crt -days 500
    • создание конфигурационного файла:
      • описание кластера (укажите адрес и расположение файла сертификата CA конкретной инсталляции кластера):

        kubectl config set-cluster kubernetes --certificate-authority=/etc/kubernetes/pki/ca.crt --server=https://192.168.100.200:6443
      • или — как нерекомендуемый вариант — можно не указывать корневой сертификат (тогда kubectl не будет проверять корректность api-server кластера):

        kubectl config set-cluster kubernetes  --insecure-skip-tls-verify=true --server=https://192.168.100.200:6443
      • добавление юзера в конфигурационный файл:

        kubectl config set-credentials mynewuser --client-certificate=.certs/mynewuser.crt  --client-key=.certs/mynewuser.key
      • добавление контекста:

        kubectl config set-context mynewuser-context --cluster=kubernetes --namespace=target-namespace --user=mynewuser
      • назначение контекста по умолчанию:

        kubectl config use-context mynewuser-context

    После указанных выше манипуляций, в файле .kube/config будет создан конфиг вида:

    apiVersion: v1
    clusters:
    - cluster:
        certificate-authority: /etc/kubernetes/pki/ca.crt
        server: https://192.168.100.200:6443
      name: kubernetes
    contexts:
    - context:
        cluster: kubernetes
        namespace: target-namespace
        user: mynewuser
      name: mynewuser-context
    current-context: mynewuser-context
    kind: Config
    preferences: {}
    users:
    - name: mynewuser
      user:
        client-certificate: /home/mynewuser/.certs/mynewuser.crt
        client-key: /home/mynewuser/.certs/mynewuser.key

    Для облегчения переноса конфига между учетными записями и серверами полезно отредактировать значения следующих ключей:

    • certificate-authority
    • client-certificate
    • client-key

    Для этого можно закодировать указанные в них файлы при помощи base64 и прописать их в конфиге, добавив в название ключей суффикс -data, т.е. получив certificate-authority-data и т.п.

    Сертификаты с kubeadm


    С релизом Kubernetes 1.15 работа с сертификатами стала значительно проще благодаря альфа-версии её поддержки в утилите kubeadm. Например, вот как теперь может выглядеть генерация конфигурационного файла с ключами пользователя:

    kubeadm alpha kubeconfig user --client-name=mynewuser --apiserver-advertise-address 192.168.100.200

    NB: Требуемый advertise address можно посмотреть в конфиге api-server, который по умолчанию расположен в /etc/kubernetes/manifests/kube-apiserver.yaml.

    Результирующий конфиг будет выведен в stdout. Его нужно сохранить в ~/.kube/config учетной записи пользователя или же в файл, указанный в переменной окружения KUBECONFIG.

    Копнуть глубже


    Для желающих тщательнее разобраться в описанных вопросах:


    Авторизация


    Аутентифицированная учетная запись по умолчанию не имеет прав на действия в кластере. Для предоставления разрешений в Kubernetes реализован механизм авторизации.

    До версии 1.6 в Kubernetes применялся тип авторизации, называемый ABAC (Attribute-based access control). Подробности о нём можно найти в официальной документации. В настоящее время этот подход считается устаревшим (legacy), однако вы всё ещё можете использовать его одновременно с другими типами авторизации.

    Актуальный же (и более гибкий) способ разделения прав доступа к кластеру называется RBAC (Role-based access control). Он был объявлен стабильным с версии Kubernetes 1.8. RBAC реализует модель прав, в которой запрещено всё, что не разрешено явно.
    Чтобы включить RBAC, нужно запустить Kubernetes api-server с параметром --authorization-mode=RBAC. Параметры выставляются в манифесте с конфигурацией api-server, которая по умолчанию находится по пути /etc/kubernetes/manifests/kube-apiserver.yaml, в секции command. Впрочем, по умолчанию RBAC и так включен, поэтому скорее всего беспокоиться об этом не стоит: убедиться в этом можно по значению authorization-mode (в уже упомянутом kube-apiserver.yaml). К слову, среди его значений могут оказаться и другие типы авторизации (node, webhook, always allow), но их рассмотрение оставим за рамками материала.

    К слову, мы уже публиковали статью с достаточно подробным рассказом о принципах и особенностях работы с RBAC, поэтому далее ограничусь кратким перечислением основ и примеров.

    Для управления доступом в Kubernetes через RBAC используются следующие сущности API:

    • Role и ClusterRole — роли, которые служат для описания прав доступа:
    • Role позволяет описать права в рамках пространства имён;
    • ClusterRole — в рамках кластера, в том числе к кластер-специфичным объектам типа узлов, non-resources urls (т.е. не связанных с ресурсами Kubernetes — например, /version, /logs, /api*);
    • RoleBinding и ClusterRoleBinding — служит для привязки Role и ClusterRole к пользователю, группе пользователей или ServiceAccount.

    Сущности Role и RoleBinding являются ограниченными namespace’ом, т.е. должны находиться в пределах одного пространства имен. Однако RoleBinding может ссылаться на ClusterRole, что позволяет создать набор типовых разрешений и управлять доступом с их помощью.

    Роли описывают права при помощи наборов правил, содержащих:

    • группы API — см. официальную документацию по apiGroups и вывод kubectl api-resources;
    • ресурсы (resources: pod, namespace, deployment и т.п.);
    • глаголы (verbs: set, update и т.п.).
    • имена ресурсов (resourceNames) — для случая, когда нужно предоставить доступ к какому-то определённому ресурсу, а не ко всем ресурсам этого типа.

    Более подробный разбор авторизации в Kubernetes можно найти на странице официальной документации. Вместо этого (а точнее — в дополнение к этому) приведу примеры, которые иллюстрируют её работу.

    Примеры сущностей RBAC


    Простая Role, позволяющая получать список и статус pod’ов и следить за ними в пространстве имен target-namespace:

    apiVersion: rbac.authorization.k8s.io/v1
    kind: Role
    metadata:
      namespace: target-namespace
      name: pod-reader
    rules:
    - apiGroups: [""]
      resources: ["pods"]
      verbs: ["get", "watch", "list"]

    Пример ClusterRole, что позволяет получать список и статус pod’ов и следить за ними во всем кластере:

    apiVersion: rbac.authorization.k8s.io/v1
    kind: ClusterRole
    metadata:
      # секции "namespace" нет, так как ClusterRole задействует весь кластер
      name: pod-reader
    rules:
    - apiGroups: [""]
      resources: ["pods"]
      verbs: ["get", "watch", "list"]

    Пример RoleBinding, что позволяет пользователю mynewuser «читать» pod’ы в пространстве имен my-namespace:

    apiVersion: rbac.authorization.k8s.io/v1
    kind: RoleBinding
    metadata:
      name: read-pods
      namespace: target-namespace
    subjects:
    - kind: User
      name: mynewuser # имя пользователя зависимо от регистра!
      apiGroup: rbac.authorization.k8s.io
    roleRef:
      kind: Role # здесь должно быть “Role” или “ClusterRole”
      name: pod-reader # имя Role, что находится в том же namespace,
                       # или имя ClusterRole, использование которой
                       # хотим разрешить пользователю
      apiGroup: rbac.authorization.k8s.io

    Аудит событий


    Схематично архитектуру Kubernetes можно представить следующим образом:

    image

    Ключевой компонент Kubernetes, отвечающий за обработку запросов, — api-server. Все операции над кластером проходят через него. Подробнее об этих внутренних механизмах можно почитать в статье «Что происходит в Kubernetes при запуске kubectl run?».

    Аудит системы — интересная фича в Kubernetes, которая по умолчанию выключена. Она позволяет логировать все обращения к Kubernetes API. Как легко догадаться, через этот API производятся все действия, связанные с контролем и изменением состояния кластера. Хорошее описание её возможностей можно (как обычно) найти в официальной документации K8s. Далее я постараюсь изложить тему более простым языком.

    Итак, чтобы включить аудит, нам нужно передать контейнеру в api-server три обязательных параметра, подробнее о которых рассказано ниже:

    • --audit-policy-file=/etc/kubernetes/policies/audit-policy.yaml
    • --audit-log-path=/var/log/kube-audit/audit.log
    • --audit-log-format=json

    Помимо этих трёх необходимых параметров, существует множество дополнительных настроек, относящихся к аудиту: от ротации логов до описаний webhook. Пример параметров ротации логов:

    • --audit-log-maxbackup=10
    • --audit-log-maxsize=100
    • --audit-log-maxage=7

    Но останавливаться подробнее на них не будем — найти все детали можно в документации по kube-apiserver.

    Как уже упоминалось, все параметры выставляются в манифесте с конфигурацией api-server (по умолчанию /etc/kubernetes/manifests/kube-apiserver.yaml), в секции command. Вернемся к 3 обязательным параметрам и разберём их:

    1. audit-policy-file — путь до YAML-файла с описанием политики (policy) аудита. К его содержимому мы ещё вернёмся, а пока замечу, что файл должен быть доступен для чтения процессом api-server’а. Поэтому необходимо смонтировать его внутрь контейнера, для чего можно добавить следующий код в соответствующие секции конфига:

        volumeMounts:
          - mountPath: /etc/kubernetes/policies
            name: policies
            readOnly: true
        volumes:
        - hostPath:
            path: /etc/kubernetes/policies
            type: DirectoryOrCreate
          name: policies
    2. audit-log-path — путь до файла лога. Путь также должен быть доступен процессу api-server’a, поэтому аналогично описываем его монтирование:

        volumeMounts:
          - mountPath: /var/log/kube-audit
            name: logs
            readOnly: false
        volumes:
        - hostPath:
            path: /var/log/kube-audit
            type: DirectoryOrCreate
          name: logs
    3. audit-log-format — формат лога аудита. По умолчанию это json, но доступен и устаревший текстовый формат (legacy).

    Политика аудита


    Теперь об упомянутом файле с описанием политики логирования. Первое понятие audit policy — это level, уровень логирования. Они бывают следующими:

    • None — не логировать;
    • Metadata — логировать метаданные запроса: пользователя, время запроса, целевой ресурс (pod, namespace и т.п.), тип действия (verb) и т.п.;
    • Request — логировать метаданные и тело запроса;
    • RequestResponse — логировать метаданные, тело запроса и тело ответа.

    Последние два уровня (Request и RequestResponse) не логируют запросы, которые не обращались к ресурсам (обращения к так называемым non-resources urls).

    Также все запросы проходят через несколько стадий:

    • RequestReceived — этап, когда запрос получен обработчиком и ещё не передан дальше по цепочке обработчиков;
    • ResponseStarted — заголовки ответа отправлены, но перед отправкой тела ответа. Генерируется для длительных запросов (например, watch);
    • ResponseComplete — тело ответа отправлено, больше информации отправляться не будет;
    • Panic — события генерируются, когда обнаружена нештатная ситуация.

    Для пропуска каких-либо стадий можно использовать omitStages.

    В файле политики мы можем описать несколько секций с разными уровнями логирования. Применяться будет первое подходящее правило, найденное в описании policy.

    Демон kubelet отслеживает изменение манифеста с конфигурацией api-server и при обнаружении таковых перезапускает контейнер с api-server. Но есть важная деталь: изменения в файле policy будут им игнорироваться. После внесения изменений в файл policy потребуется перезапустить api-server вручную. Поскольку api-server запущен как static pod, команда kubectl delete не приведёт к его перезапуску. Придется вручную сделать docker stop на kube-master’ах, где изменена политика аудита:

    docker stop $(docker ps | grep k8s_kube-apiserver | awk '{print $1}')

    При включении аудита важно помнить, что на kube-apiserver повышается нагрузка. В частности, увеличивается потребление памяти для хранения контекста запросов. Запись в лог начинается только после отправки заголовка ответа. Также нагрузка зависит от конфигурации политики аудита.

    Примеры политик


    Разберём структуру файлов policy на примерах.

    Вот простой файл policy, чтобы логировать всё на уровне Metadata:

    apiVersion: audit.k8s.io/v1
    kind: Policy
    rules:
    - level: Metadata

    В policy можно указывать перечень пользователей (Users и ServiceAccounts) и групп пользователей. Например, вот так мы будем проигнорировать системных пользователей, но логировать всё остальное на уровне Request:

    apiVersion: audit.k8s.io/v1
    kind: Policy
    rules:
      - level: None
        userGroups:
          - "system:serviceaccounts"
          - "system:nodes"
        users:
          - "system:anonymous"
          - "system:apiserver"
          - "system:kube-controller-manager"
          - "system:kube-scheduler"
      - level: Request

    Также есть возможность описывать целевые:

    • пространства имен (namespaces);
    • глаголы (verbs: get, update, delete и прочие);
    • ресурсы (resources, а именно: pod, configmaps и т.п.) и группы ресурсов (apiGroups).

    Обратите внимание! Ресурсы и группы ресурсов (группы API, т.е. apiGroups), а также их версии, установленные в кластере, можно получить при помощи команд:

    kubectl api-resources
    kubectl api-versions

    Следующий audit policy приведён в качестве демонстрации лучших практик в документации Alibaba Cloud:

    apiVersion: audit.k8s.io/v1beta1
    kind: Policy
    # Не логировать стадию RequestReceived
    omitStages:
      - "RequestReceived"
    rules:
      # Не логировать события, считающиеся малозначительными и не опасными:
      - level: None
        users: ["system:kube-proxy"]
        verbs: ["watch"]
        resources:
          - group: "" # это api group с пустым именем, к которому относятся
                      # базовые ресурсы Kubernetes, называемые “core”
            resources: ["endpoints", "services"]
      - level: None
        users: ["system:unsecured"]
        namespaces: ["kube-system"]
        verbs: ["get"]
        resources:
          - group: "" # core
            resources: ["configmaps"]
      - level: None
        users: ["kubelet"]
        verbs: ["get"]
        resources:
          - group: "" # core
            resources: ["nodes"]
      - level: None
        userGroups: ["system:nodes"]
        verbs: ["get"]
        resources:
          - group: "" # core
            resources: ["nodes"]
      - level: None
        users:
          - system:kube-controller-manager
          - system:kube-scheduler
          - system:serviceaccount:kube-system:endpoint-controller
        verbs: ["get", "update"]
        namespaces: ["kube-system"]
        resources:
          - group: "" # core
            resources: ["endpoints"]
      - level: None
        users: ["system:apiserver"]
        verbs: ["get"]
        resources:
          - group: "" # core
            resources: ["namespaces"]
      # Не логировать обращения к read-only URLs:
      - level: None
        nonResourceURLs:
          - /healthz*
          - /version
          - /swagger*
      # Не логировать сообщения, относящиеся к типу ресурсов “события”:
      - level: None
        resources:
          - group: "" # core
            resources: ["events"]
      # Ресурсы типа Secret, ConfigMap и TokenReview могут содержать  секретные данные,
      # поэтому логируем только метаданные связанных с ними запросов
      - level: Metadata
        resources:
          - group: "" # core
            resources: ["secrets", "configmaps"]
          - group: authentication.k8s.io
            resources: ["tokenreviews"]
      # Действия типа get, list и watch могут быть ресурсоёмкими; не логируем их
      - level: Request
        verbs: ["get", "list", "watch"]
        resources:
          - group: "" # core
          - group: "admissionregistration.k8s.io"
          - group: "apps"
          - group: "authentication.k8s.io"
          - group: "authorization.k8s.io"
          - group: "autoscaling"
          - group: "batch"
          - group: "certificates.k8s.io"
          - group: "extensions"
          - group: "networking.k8s.io"
          - group: "policy"
          - group: "rbac.authorization.k8s.io"
          - group: "settings.k8s.io"
          - group: "storage.k8s.io"
      # Уровень логирования по умолчанию для стандартных ресурсов API
      - level: RequestResponse
        resources:
          - group: "" # core
          - group: "admissionregistration.k8s.io"
          - group: "apps"
          - group: "authentication.k8s.io"
          - group: "authorization.k8s.io"
          - group: "autoscaling"
          - group: "batch"
          - group: "certificates.k8s.io"
          - group: "extensions"
          - group: "networking.k8s.io"
          - group: "policy"
          - group: "rbac.authorization.k8s.io"
          - group: "settings.k8s.io"
          - group: "storage.k8s.io"
      # Уровень логирования по умолчанию для всех остальных запросов
      - level: Metadata


    Другой хороший пример audit policy — профиль, используемый в GCE.

    Для оперативного реагирования на события аудита есть возможность описать webhook. Этот вопрос раскрыт в официальной документации, оставлю его за рамками данной статьи.

    Итоги


    В статье дан обзор механизмов базового обеспечения безопасности в кластерах Kubernetes, позволяющих создавать персонифицированные учетные записи пользователям, разделять их права, а также регистрировать их действия. Надеюсь, он пригодится тем, кто столкнулся с такими вопросами в теории или уже на практике. Рекомендую также ознакомиться со списком других материалов по теме безопасности в Kubernetes, что приведен в «P.S.», — возможно, среди них вы найдёте нужные подробности по актуальным для вас проблемам.

    P.S.


    Читайте также в нашем блоге:

    • +32
    • 5,3k
    • 8
    Флант
    468,43
    Специалисты по DevOps и Kubernetes
    Поделиться публикацией

    Комментарии 8

      0

      А есть рекомендации по пользователям в рамках обычной разработки (деплой на кластер с локальной машины) и CI/CD? Не могу определиться лучше давать дженкинсу/гитлабу/… отдельного пользователя (а может нескольких для разных этапов) или делать от имени пользователя, запушившего в репозитории изменения? Второе вроде правильней с точки зрения расследования инцидентов. Но пока до конца не понимаю как просто сделать аутентификацию сквозную

        +2
        Тут в первую очередь нужно определиться с тем, что вы хотите добится, а потом уже подбирать инструменты для реализации задачи. Если вам нужно расследование инцидентов, то у вас есть репозиторий, в котором можно посмотреть кто какие изменения запушил, и гитлаб, в котором сохраняются результаты прогона пайплайнов. Если вы хотите предотвратить какие-то инциденты, правильнее будет выделить проекту отдельного пользователя с доступом к фиксированному числу нэймспэйсов, в которые он может деплоиться (для обеспечения безопасности такого решения лучше всего выделить отдельный раннер под каждый конкретный проект). Смысл заведения пользователей каждому разработчику есть когда разработчики работают с кластером самостоятельно напрямую, минуя гитлаб или дженкинс (смотрят логи, ходят в поды, деплоят/изменяют вручную).
          0

          Спасибо. В какие-то неймспейсы планируется деплой напрямую, в какие-то (продакшен) исключительно через CI/CD. Там и через пуш в гит, и через запуск задач в дженкинсу Расследования нужны только в продакшен.

            0
            Значит вам стоит сделать все указанные выше действия, плюс включить аудит (как минимум) для продовых нэймспэйсов, если есть суперпользователи, у которых полные права в кластере (в примерах к статье описаны правила для включения фильтрации аудита по неймспэйсу, плюс обратите внимание на отключения логирования секретных данных, — это может быть существенно).
            От себя хочу заметить, что в методологии DevOps нет цели разобраться и наказать сотрудника, допустившего ошибку. Это заменяется целью организовать работу таким образом, чтобы ошибки были в принципе не возможны.
              0

              Тут скорее требования безопасности прежде всего, очень чувствительные данные ходят и хочется показать аудиту, что у нас всё, что касается прода, логируется.

              0
              В какие-то неймспейсы планируется деплой напрямую

              Такое лучше совсем не разрешать)
              Смысл заведения пользователей каждому разработчику есть когда разработчики работают с кластером самостоятельно напрямую

              Тут скорее имелось ввиду, что разработчики иногда пытаются попасть с контейнер, чтобы выполнить там ту или иную консольную команду. Ну и попав в кластер, могут получить некий доступ, который им не был предназначен. Оставить какие либо не «хорошие» артефакты. Вот это и нужно логировать.
                0
                Такое лучше совсем не разрешать)

                Неймспейс типа development исключительно для задач разработки. Для каждого деплоя делать коммит и пуш очень не хочется, особенно учитывая, что мы в целом настоятельно рекомендуем не перезаписывать историю после пуша без крайней необходимости и уведомления всех.

                  0
                  Для каждого деплоя делать коммит и пуш очень не хочется

                  Но вам же нужно собрать контейнер, для того чтобы задеплоить. И тут билдсервер может лучше подходить для данной задачи, как минимум из-за доступных ресурсов, и ширины канала. При «правильно» настроенном CICD, коммитнуть и увидеть результат уже задеплоенным, будет быстрее, чем делать это локально с последующим ручным запуском.
                  что мы в целом настоятельно рекомендуем не перезаписывать историю после пуша без крайней необходимости и уведомления всех.

                  Тут кажется, что вам нужно сменить ваш gitflow. Изменять историю в «статичных» ветках типа master/develop, нужно запретить, сделав их protected. Разработчикам нужно пользоваться фича ветками. И когда работа готова, менять историю (вероятно речь про склейку коммитов) и делать пул/мерж реквесты в те самые ветки. Так история будет всегда читаемой и не будет конфликтов у разработчиков.

        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

        Самое читаемое