Рано или поздно в эксплуатации любой системы встаёт вопрос безопасности: обеспечения аутентификации, разделения прав, аудита и других задач. Для 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
- описание кластера (укажите адрес и расположение файла сертификата CA конкретной инсталляции кластера):
После указанных выше манипуляций, в файле .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;
- хорошая статья от Bitnami, в которой вопрос сертификатов затронут с практической стороны.
- общая документация по аутентификации в Kubernetes.
Авторизация
Авторизованная учетная запись по умолчанию не имеет прав на действия в кластере. Для предоставления разрешений в 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, отвечающий за обработку запросов, — 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 обязательным параметрам и разберём их:
-
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
-
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
-
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.
Читайте также в нашем блоге:
- «33+ инструмента для безопасности Kubernetes»;
- «Введение в сетевые политики Kubernetes для специалистов по безопасности»;
- «Понимаем RBAC в Kubernetes»;
- «9 лучших практик по обеспечению безопасности в Kubernetes»;
- «11 способов (не) стать жертвой взлома в Kubernetes».
Автор: Oleg Voznesensky