[Перевод] 10 Kubernetes Security Context, которые необходимо понимать

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

a71923e8e51811b285d935607c0e2481.jpeg
  1. runAsNonRoot

  2. runAsUser / runAsGroup

  3. seLinuxOptions

  4. seccompProfile

  5. privileged / allowPrivilegeEscalation

  6. capabilities

  7. readonlyRootFilesystem

  8. procMount

  9. fsGroup / fsGroupChangePolicy

  10. sysctls

Подписывайтесь на телеграм-канал Mops DevOps, чтобы не пропустить лучшие статьи, видео и митапы!

Pod vs Container settings

Параметры Kubernetes securityContext определены как в PodSpec, так и в ContainerSpec, а область действия указывается в этой статье с помощью аннотаций [P] и / или [C] рядом с каждым из них. Обратите внимание, что, если параметр доступен и настроен в обеих областях, параметр контейнера будет иметь приоритет.

Теперь, давайте рассмотрим на настройки securityContext:

1. runAsNonRoot [P/C]

Несмотря на то, что контейнер использует namespaces и cgroups для ограничения своих процессов, всего один неверный параметр развертывания, предоставит этим процессам доступ к ресурсам на хосте. Если этот процесс выполняется от имени пользователя root, он имеет тот же доступ, что и учетная запись root хоста, к этим ресурсам. Кроме того, если для уменьшения ограничений (например, procMount или capabilities) используются другие настройки модуля или контейнера, наличие корневого UID увеличивает риски их использования. Если у вас нет веской причины, вам никогда не следует запускать контейнер с правами root.

Итак, что делать, если у вас есть образ для развертывания, использующий root?

Часто базовые образы уже созданы и доступны для пользователей, но их использование остается на усмотрение групп разработки или развертывания. Например, официальный образ Node.js поставляется с пользователем node с UID 1000, от имени которого вы можете работать, но они явно не устанавливают его для текущего пользователя в своем Dockerfile. Нам нужно будет либо настроить его во время выполнения с помощью параметра runAsUser, либо изменить текущего пользователя в образе с помощью отдельного файла Dockerfile. Первый предполагает, что UID 1000 может читать файлы смонтированные в appvolume, также не очень распространенный вариант выполнение приложения в отдельном томе. Вместо этого давайте рассмотрим пример использования производного файла Dockerfile для создания собственного образа.

Не слишком углубляясь в создание образов, предположим, что у нас есть готовый пакет npm. Вот минимальный Dockerfile для создания образа на основе node: slim и запуска от имени заданного пользователя node.

FROM node:slim
COPY --chown=node . /home/node/app/   # <--- Copy app into the home directory with right ownership
USER node                             # <--- Switch active user to "node”
WORKDIR /home/node/app                # <--- Switch current directory to app
ENTRYPOINT ["npm", "start"]           # <--- This will now exec as the "node” user instead of root

Команда USER, делает node пользователем по умолчанию внутри любого контейнера, запущенного с этого образа.

Вариант 2: Пользователь не определен в базовом образе

Итак, чтобы мы сделали, если бы в базовом образе node пользователь не был бы определен? В большинстве случаем мы просто создаем его в новом Dockerfile и используем. Давайте расширим предыдущий пример, чтобы сделать это:

FROM node:slim
RUN useradd somebody -u 10001 --create-home --user-group  # <--- Create a user
COPY --chown=somebody . /home/somebody/app/
USER somebody
WORKDIR /home/somebody/app
ENTRYPOINT ["npm", "start"]

Как видите, единственное изменение — это команада RUN, которая создает нового пользователя — синтаксис может варьироваться в зависимости от дистрибутива базового образа.

ПРИМЕЧАНИЕ: это отлично работает для node.js и npm, но для других инструментов может потребоваться изменить владельца других объектов файловой системы. Если у вас возникнут какие-либо проблемы, обратитесь к документации по вашему инструменту.

2. runAsUser / runAsGroup [P/C]

Образы контейнеров могут иметь определенного пользователя и/или группу, настроенную для запуска процесса. Это можно изменить с помощью параметров runAsUser и runAsGroup. Часто они устанавливаются вместе с монтированием тома, содержащим файлы с одинаковыми идентификаторами владения.

...
spec:
  containers:
  - name: web
    image: mycorp/webapp:1.2.3
  securityContext:
    runAsNonRoot: true
    runAsUser: 10001
...

Использование этих настроек представляет опасность, поскольку вы изменяете параметры во время запуска контейнера, эти параметры могут быть несовместимы с исходным образом. Например, официальный образ сервера jenkins/jenkins CI работает как группа: пользователь с именем jenkins: jenkins и все его файлы приложений принадлежат этому пользователю. Если мы настроим другого пользователя, он не запустится, потому что этого образ не содержит этого пользователя в файле /etc/passwd. Даже если бы это было так, скорее всего, возникнут проблемы с чтением и записью файлов, принадлежащих jenkins: jenkins. Вы можете в этом убедиться, выполнив простую команду Docker:

$ docker run --rm -it -u eric:eric jenkins/jenkins
docker: Error response from daemon: unable to find user eric: no matching entries in passwd file.

Как мы упоминали выше, это очень хорошая идея, чтобы процессы контейнера не запускались от имени пользователя root, но не стоит полагаться для этого на параметр runAsUser или runAsGroup. Что если кто-то удалить эти настройки? Не забудьте также установить для runAsNonRoot значение true.

3. seLinuxOptions [P/C]

SELinux — это система управления доступом к приложениям, процессам и файлам в системе Linux, настраиваемая через политики. Она реализует структуру модулей безопасности Linux в ядре Linux. SELinux основана на концепции меток и применяет эти метки ко всем элементам в системе, которые группируют элементы вместе. Эти метки известны как контекст безопасности — не путать с Kubernetes securityContext и состоят из пользователя, роли, типа и необязательного поля уровень — user: role: type: level.

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

Метки SELinux по умолчанию будут применяться средой выполнения контейнера при создании экземпляра контейнера. Параметр seLinuxOptions в securityContext позволяет применять пользовательские метки SELinux. Имейте в виду, что изменение маркировки SELinux для контейнера потенциально может позволить контейнерному процессу выйти из образа контейнера и получить доступ к файловой системе хоста.

По умолчанию метки SELinux будут применяться средой выполнения контейнера при создании экземпляра контейнера. Параметр seLinuxOptions в securityContext позволяет применять пользовательские метки SELinux. Имейте в виду, что изменение маркировки SELinux для контейнера потенциально может позволить контейнерному процессу выйти из образа контейнера и получить доступ к файловой системе хоста.

Обратите внимание, что эта функция будет применяться, только если операционная система хоста поддерживает SELinux.

4. seccompProfile [P/C]

Seccomp означает secure computing mode (безопасный режим вычислений) и является функцией ядра Linux, которая может ограничивать вызовы, которые конкретный процесс может делать из пользовательского пространства в ядро. Профиль seccomp — это определение JSON, обычно состоящее из набора системных вызовов и действия по умолчанию, предпринимаемого при возникновении одного из этих системных вызовов.

{
    "defaultAction": "SCMP_ACT_ERRNO",
    "architectures": [
        "SCMP_ARCH_X86_64",
        "SCMP_ARCH_X86",
        "SCMP_ARCH_X32"
    ],
    "syscalls": [
        {
            "name": "accept",
            "action": "SCMP_ACT_ALLOW",
            "args": []
        },
        {
            "name": "accept4",
            "action": "SCMP_ACT_ALLOW",
            "args": []
        },
        ...
    ]
}

Использован пример с сайта https://training.play-with-docker.com/security-seccomp/

Kubernetes предоставляет механизм для использования настраиваемых профилей через параметр seccompProfile в securityContext.

seccompProfile:
      type: Localhost
      localhostProfile: profiles/myprofile.json

Доступно три значения для поля type:

  • Localhost — в дополнительном параметре localhostProfile указан путь к профилю seccomp

  • Unconfined — профиль не применяется

  • RuntimeDefault — используется значение по умолчанию для среды выполнения контейнера (это значение по умолчанию, если тип не указан)

Вы можете применить эти настройки либо в PodSecurityContext, либо в securityContext. Если установлены оба контекста, то используются настройки на уровне контейнера в securityContext. Обратите внимание, что эти параметры актуальны для Kubernetes v1.19 — если вы развертываете более ранние версии, существует другой синтаксис; за подробностями и примерами обратитесь к документации на официальном сайте Kubernetes.

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

5. Избегайте Privileged Containers / Escalations [C]

Предоставление привилегированного статуса контейнеру опасно и обычно используется как более простой способ получения определенных разрешений. Среда выполнения контейнеров контролирует наличие флага privileged, предоставляет контейнеру все привилегии, но снимает ограничения, налагаемые cgroup. Она также может изменить Linux Security Module и позволить процессам внутри контейнера выйти из него.

Контейнеры обеспечивают на хосте изоляцию процессов, поэтому даже если контейнер работает от имени пользователя root, существуют функции, которые container runtime не предоставляет контейнеру. При установленном флаге privileged среда выполнения контейнеров предоставляет полный доступ к файловой системе хоста, что делает этот параметр чрезвычайно опасным с точки зрения безопасности.

Избегайте использования флага privileged и, если вашему контейнеру нужны дополнительные возможности, добавьте только те, которые вам нужны. Если вашему контейнеру не требуется управлять настройками системного уровня в ядре хоста, такими как доступ к оборудованию или конфигурация сетей, и ему нужен доступ к файловой системе хоста, то ему не нужен флаг privileged.

Для более глубокого изучения Privileged Containers рекомендуем статью Privileged Docker containers—do you really need them?

6. Linux kernel capabilities [C]

Capabilities — это разрешения на уровне ядра, которые позволяют гранулярно управлять разрешениями на вызовы ядра, вместо того, чтобы запускать все от имени пользователя root. Capabilities позволяют изменять права доступа к файлам, управлять сетевой подсистемой и выполняет общесистемные функции администрирования. Вы можете управлять Capabilities через KubernetessecurityContext. Отдельные сapabilities или список, разделенный запятыми, могут быть представлены в виде массива строк. Кроме того, вы можете использовать сокращение -all для добавления или удаления всех capabilities. Эта конфигурация передается в среду выполнения контейнеров и настраивает capabilities при создании контейнера. Если в securityContext нет раздела capabilities, тогда контейнер создается с набором capabilities по умолчанию, который предоставляет среда выполнения контейнера.

securityContext:
      capabilities:
        drop:
          - all
        add: ["MKNOD"]

Рекомендуется отказаться от всех capabilities, а затем добавить обратно только те, которые действительно нужны вашему приложению. Во многих случаях приложениям на самом деле не требуются какие-либо capabilities при нормальной работе. Вначале отключите все capabilities и отслеживайте сбои в журнале аудита, чтобы увидеть, какие из них были заблокированы.

Обратите внимание, что при перечислении capabilities в securityContext вы удаляете префикс CAP_, который ядро ​​использует в именах capabilities. Вы можете использовать утилиту capsh, она выводит информацию о включенных в контейнере capabilities в удобном формате о том, но оставляйте эту утилиту в итоговых контейнерах, так как это позволяет злоумышленнику легко определить, какие capabilities включены! Вы также можете проверить включенные capabilities в файле /proc/1/ status.

7. Запуск контейнеров с read-only filesystem [C]

Если ваш контейнер скомпрометирован и его файловая система доступна для чтения и записи, злоумышленник может изменить его конфигурацию, установить программное обеспечение и потенциально запустить другие эксплойты. Наличие файловой системы только для чтения помогает предотвратить подобные инциденты, ограничивая действия, которые может выполнить злоумышленник. Как правило, контейнеры не должны требовать записи в файловую систему контейнера. Если ваше приложение имеет данные с отслеживанием состояния, вы должны использовать внешний метод сохранения, например базу данных, volume или какую-либо другую службу. Кроме того, убедитесь, что все логи записываются в стандартный вывод и/или на сервер коллектор логов, где они обрабатываются централизованно.

8. procMount [C]

По умолчанию среда выполнения контейнеров маскирует определенные части файловой системы /proc изнутри контейнера, чтобы предотвратить возможные проблемы с безопасностью. Однако бывают случаи, когда к ним требуется доступ, особенно при использовании вложенных контейнеров, которые часто используются как часть процесса сборки в кластере. Есть только два допустимых параметра для этой записи: Default, который поддерживает стандартное поведение среды выполнения контейнера, или Unmasked, который удаляет все маскировки для файловой системы /proc.

Очевидно, вам следует использовать эти настройки, только если вы действительно знаете, что делаете. Если вы используете его для создания образов, проверьте последнюю версию инструмента сборки, так как многим он больше не нужен. Обновите и вернитесь к procMount по умолчанию, который актуален для используемого вами инструмента.

Наконец, если вам все же нужен доступ к замаскированным участкам /proc, сделайте это только для вложенного контейнера; никогда не открывайте файловую систему /proc вашей хост-системы контейнеру.

9. fsGroup / fsGroupChangePolicy [P]

Параметр fsGroup определяет группу, в которой Kubernetes будет изменять разрешения для всех файлов в томах, когда тома монтируются в Pod. Поведение также контролируется fsGroupChangePolicy, для которого может быть установлено значение onRootMismatch или Always. Если установлено значение onRootMismatch, разрешения будут изменены только в том случае, если они еще не соответствуют разрешениям корневого каталога контейнера.

Будьте осторожны при использовании fsGroup. Изменение группового владения всем томом может вызвать задержки запуска Pod для медленных и/или больших файловых систем. Это также может нанести ущерб другим процессам, которые совместно используют тот же том, если их процессы не имеют разрешений на доступ к новому GID. По этой причине некоторые поставщики общих файловых систем, таких как NFS, не реализуют эту функцию. Эти настройки не влияют на ephemeral volume.

10. sysctls [P]

Sysctls — это функция ядра Linux, которая позволяет администраторам изменять конфигурацию ядра. В хостовой операционной системе Linux они определяются с помощью /etc/sysctl.conf, а также могут быть изменены с помощью утилиты sysctl.

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

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

Примечание о securityContext в процессе запуска

Во многих случаях описанные здесь параметры безопасности сочетаются с контролем доступа на основе политик (policy-based admission control), чтобы гарантировать, что необходимые параметры действительно настроены перед запуском контейнеров в кластер. Комбинируя параметры securityContext с PodSecurityPolicy, вы можете гарантировать, что запускаются только контейнеры, которые соответсвуют политике, принудительно применения определенных параметров securityContext. Параметры securityContext также могут быть добавлены к конфигурации контейнера во время запуска с помощью динамического контроля допуска (Dynamic Admission Control) и использования mutating webhooks.

Заключение

Повышая безопасность при помощи параметров securityContext следует помнить о многом. При правильном использовании они являются очень эффективным инструментом, и мы надеемся, что этот список поможет вашим командам выбрать правильные варианты. Snyk может помочь вам с этим выбором, просканировав ваши yaml-файлы Kubernetes на предмет распространенных ошибок конфигурации.

Телеграм-канал Mops DevOps — анонсы вебинаров и конференций, полезные статьи и видео, а также регулярные скидки на обучение!

© Habrahabr.ru