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

Азбука безопасности в 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: secret-reader
rules:
- apiGroups: [""]
  resources: ["secrets"]
  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 можно представить следующим образом:

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

Ключевой компонент 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.

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

Источник: habr.com

Добавить комментарий