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

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

Рано чи пізно в експлуатації будь-якої системи постає питання безпеки: забезпечення автентифікації, поділу прав, аудиту та інших завдань. Для Kubernetes вже створено безліч рішень, які дозволяють досягти відповідності стандартам навіть у дуже вимогливих оточеннях… Цей же матеріал присвячений базовим аспектам безпеки, реалізованим у рамках вбудованих механізмів K8s. Насамперед він буде корисним тим, хто починає знайомитися з Kubernetes, як відправна точка для вивчення питань, пов'язаних з безпекою.

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

У Kubernetes є два типи користувачів:

  • Сервісні облікові записи - Облікові записи, керовані Kubernetes API;
  • користувачів - «Нормальні» користувачі, керовані зовнішніми, незалежними сервісами.

Основна відмінність цих типів у тому, що для Service Accounts існують спеціальні об'єкти в Kubernetes API (вони так і називаються ServiceAccounts), які прив'язані до простору імен та набору авторизаційних даних, що зберігаються в кластері в об'єктах типу Secrets. Такі користувачі (Service Accounts) призначені переважно управління правами доступу до Kubernetes API процесів, які у кластері Kubernetes.

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

Кожен запит до API прив'язаний або до Service Account, або до User, або вважається анонімним.

Аутентифікаційні дані користувача включають:

  • ім'я користувача - Ім'я користувача (залежить від регістру!);
  • UID — машинно-читаний рядок ідентифікації користувача, який «консистентніший і унікальніший, ніж ім'я користувача»;
  • груп - Список груп, до яких належить користувач;
  • 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

З релізом Кубернети 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 (Рольовий контроль доступу). Він був оголошений стабільним з версії Кубернети 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;
  • ресурси (ресурси: pod, namespace, deployment і т.п.);
  • дієслова (Дієслова: 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-сервер. Усі операції над кластером проходять крізь нього. Докладніше про ці внутрішні механізми можна почитати у статті «Що відбувається в 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);
  • дієслова (Дієслова: get, update, delete та інші);
  • ресурси (ресурси, А саме: 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, що наведено в «PS», — можливо, серед них ви знайдете потрібні подробиці щодо актуальних для вас проблем.

PS

Читайте також у нашому блозі:

Джерело: habr.com

Додати коментар або відгук