Kubernetes SecurityContext: разбор с примерами

Kubernetes SecurityContext: разбор с примерами


В этой заметке (продолжающей вчерашнюю тему) разбираемся, почему поды без root-доступа - это важно, и подробно рассматриваем параметры Kubernetes SecurityContext, которые позволяют принудительно настраивать запуск от non-root пользователя на уровне пода и контейнера.

Почему non-root поды?

Для начала давайте разберёмся, почему вообще стоит использовать только non-root поды.

Во-первых, это снижает риски безопасности. Запуск контейнеров от non-root пользователей уменьшает потенциальный ущерб от эксплуатации уязвимостей, поскольку процессы внутри контейнера не имеют root-доступа к хост-системе.

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

И наконец - соответствие требованиям. Во многих организациях и отраслях существуют политики и стандарты (например, PCI DSS), которые требуют запускать контейнеры от non-root пользователей, чтобы удовлетворять требованиям безопасности и регуляторов.

UID по умолчанию, назначаемый подам

По умолчанию Kubernetes назначает контейнерам в поде UID 0 (root), если вы явно не указали другого пользователя.

Вот пример, который показывает user ID контейнера в поде без какой-либо настройки безопасности — ни на уровне образа, ни на уровне пода.

$ kubectl exec -it flask-app -- sh
# whoami
root
Kubernetes поддерживает настройки на уровне всего кластера, которые позволяют принудительно запрещать запуск подов от root. Это можно реализовать с помощью PodSecurity Standards (PSS) или Pod Security Admission (PSA).

SecurityContext в Kubernetes

Чтобы запускать поды от non-root пользователей, для начала нужно разобраться с параметром SecurityContext.

SecurityContext поддерживает параметры runAsUser и runAsGroup, с помощью которых можно указать пользователя и группу, от имени которых должен работать контейнер.

При этом, если вы не хотите задавать конкретного пользователя, но всё же хотите принудительно запретить root, можно использовать параметр runAsNonRoot: true.

Например:

SecurityContext на уровне пода и контейнера

SecurityContext можно задавать как на уровне пода, так и на уровне контейнера.

Уровень пода:

Такой securityContext применяется ко всем контейнерам внутри пода. По сути, это настройки по умолчанию для каждого контейнера в этом поде.

Например:

Уровень контейнера:

Этот securityContext применяется к конкретному контейнеру. Он даёт больше гибкости, если у отдельных контейнеров есть разные требования. Настройки на уровне контейнера переопределяют параметры, заданные на уровне пода, но только для этого контейнера.

Образ контейнера с non-root пользователем

Теперь представим, что вы создали образ контейнера с non-root пользователем с UID 1001.

Что произойдёт, если в поде не указывать securityContext?

Контейнер запустится от пользователя, заданного при сборке образа. Например, если образ был настроен на запуск от пользователя appuser, то контейнер внутри пода будет работать именно от appuser.

Чтобы это проверить, я задеплоил non-root образ devopscube/flask-app-non-root:1.0, собранный для запуска от appuser, без какого-либо security context.

Результат подтверждает, что контейнер действительно работает от appuser.

kubectl exec -it flask-app-non-root -- sh

$ whoami
appuser

Итого, общая рекомендация такая:

  • Если образ корректно настроен с non-root пользователем - не указывайте runAsUser.
  • Если по требованиям (например, комплаенс) нужно принудительно задать конкретный UID независимо от образа - используйте runAsNonRoot или runAsUser, в зависимости от сценария.
если в namespace включён Pod Admission и разрешены только non-root поды, необходимо указать runAsNonRoot: true. В этом случае контейнер автоматически будет использовать user ID, заданный в образе контейнера.

Образ контейнера с root-пользователем

Теперь рассмотрим ситуацию, когда вы используете образ контейнера, рассчитанный на запуск от root (что по умолчанию встречается во многих образах).

Обычно это происходит, если в Dockerfile не задана инструкция USER или образ по умолчанию предполагает запуск от root.

В таком случае, если в securityContext включить runAsNonRoot: true, под завершится с ошибкой.

Ошибка возникает потому, что Kubernetes настроен на жёсткое соблюдение политики runAsNonRoot, но контейнер пытается запуститься от root, при том что в самом образе non-root пользователь не определён.

Смягчить это можно, добавив runAsUser с конкретным UID.

Но что если приложению внутри образа действительно нужны root-привилегии? Возьмём пример с обычным образом nginx и таким манифестом:

apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  securityContext:
    runAsNonRoot: true
    runAsUser: 1001
  containers:
  - name: app-container
    image: nginx
    imagePullPolicy: Always
    ports:
    - containerPort: 5000

Если задеплоить этот манифест, под уйдёт в состояние CrashLoopBackOff.

$ kubectl get po

NAME   READY   STATUS             RESTARTS        AGE
nginx  0/1     CrashLoopBackOff   4 (10s ago)     47s

Причина в том, что стандартный образ nginx изначально рассчитан на запуск от root, чтобы иметь возможность привязываться к привилегированным портам, таким как 80 (порты ниже 1024 требуют root-прав). После старта процесс nginx может сбросить привилегии до менее привилегированного пользователя, но стартовать он всё равно должен от root.

Поэтому, если вы хотите запускать nginx как non-root пользователя, нужно собрать кастомный образ nginx, рассчитанный на работу без root. Для подробностей можно посмотреть на Nginx Unprivileged Image.

Итог: non-root под будет работать без ошибок только в том случае, если процесс внутри контейнера не требует root-привилегий.

Если же он пытается изменять системные настройки, работать с файлами, принадлежащими root, или привязываться к привилегированным портам (например, < 1024), это приведёт к ошибкам.

Заключение

SecurityContext - это довольно обширная тема с большим набором возможностей.

Например:

  • Capabilities: позволяет добавлять или удалять Linux-capabilities. Удаление лишних возможностей уменьшает поверхность атаки контейнера.
  • seccompProfile: управляет тем, какие системные вызовы может использовать контейнер. Ограниченный профиль блокирует опасные или ненужные system call’ы.
  • AppArmor: модуль безопасности ядра Linux, реализующий Mandatory Access Control (MAC). Его можно подключать к подам.
  • allowPrivilegeEscalation: определяет, может ли процесс получить больше привилегий, чем его родительский процесс.
  • readOnlyRootFilesystem: при значении true корневая файловая система контейнера становится доступной только для чтения, что снижает риск изменения системных файлов злоумышленником.

Информация: все эти механизмы безопасности в Kubernetes задаются на уровне пода через его security context, но в итоге они транслируются в настройки контейнерного рантайма.

Именно контейнерный рантайм (например, containerd или CRI-O) отвечает за фактическую реализацию и применение этих мер безопасности.

Report Page