Seccomp

Профили Seccomp для подов и контейнеров Kubernetes, размещение профилей на узлах через NCI и безопасное внедрение ограничений системных вызовов.

Общая информация

Seccomp (Secure computing mode) — механизм безопасности ядра Linux, который ограничивает системные вызовы из пользовательского пространства в ядро. В Kubernetes профили Seccomp применяются к подам и контейнерам, чтобы уменьшить поверхность атаки: процесс внутри контейнера получает только те возможности ядра, которые разрешены выбранным профилем.

Профиль Seccomp — это JSON-файл в формате, совместимом с OCI runtime specification. В нём задаются действие по умолчанию, списки системных вызовов и, при необходимости, условия по аргументам системных вызовов.

Основные действия профиля:

  • SCMP_ACT_ALLOW — разрешить вызов (белый список).
  • SCMP_ACT_ERRNO — вернуть процессу ошибку для заблокированного вызова.
  • SCMP_ACT_LOG — разрешить вызов, но записывать его в лог (аудит).
  • SCMP_ACT_KILL_PROCESS — завершить процесс.
  • SCMP_ACT_KILL_THREAD или SCMP_ACT_KILL — завершить поток.
  • SCMP_ACT_TRAP — отправить процессу сигнал SIGSYS.
  • SCMP_ACT_TRACE — уведомить трассирующий процесс.

Для универсальных профилей обычно используют SCMP_ACT_LOG, SCMP_ACT_ERRNO и SCMP_ACT_ALLOW.

Параметр defaultAction определяет, что делать с системными вызовами, которые не попали ни в одно правило. Например:

  • SCMP_ACT_LOG подходит для аудита: вызовы выполняются, но фиксируются в журнале.
  • SCMP_ACT_ERRNO подходит для жёсткого профиля: неразрешённые вызовы блокируются.

Логи блокировок и аудита смотрят на узле, где запущен Pod, в /var/log/messages или /var/log/syslog по ключевому слову SECCOMP либо по имени процесса.


Как Kubernetes применяет Seccomp

Профиль указывается в securityContext.seccompProfile. Его можно задать:

  • для всего Pod: spec.securityContext.seccompProfile;
  • для отдельного контейнера: spec.containers[*].securityContext.seccompProfile;
  • для init-контейнера: spec.initContainers[*].securityContext.seccompProfile;
  • для ephemeral-контейнера: spec.ephemeralContainers[*].securityContext.seccompProfile.

Профиль на уровне контейнера имеет приоритет над профилем на уровне Pod. Если контейнер не задаёт свой seccompProfile, он наследует профиль Pod.

Доступные значения seccompProfile.type:

Значение Поведение
RuntimeDefault Применяется профиль по умолчанию, который предоставляет container runtime. Такие профили рассчитаны на безопасные настройки без потери базовой работоспособности, но могут отличаться между CRI-O, containerd и разными версиями runtime.
Localhost Применяется локальный JSON-профиль с узла. На Linux профили размещаются относительно каталога /var/lib/kubelet/seccomp. Наличие файла проверяет container runtime при создании контейнера. Если файла нет, контейнер не создаётся и получает ошибку CreateContainerError.
Unconfined Нагрузка запускается без ограничений Seccomp.

Нельзя применить профиль Seccomp к Pod или контейнеру с privileged: true: privileged-контейнеры всегда запускаются как Unconfined.

В новых манифестах используйте поля securityContext.seccompProfile. Аннотации Seccomp, применявшиеся в старых версиях Kubernetes, не нужны для актуальной конфигурации.


Базовый вариант: RuntimeDefault

Для большинства нагрузок сначала стоит проверить профиль RuntimeDefault. Он включает ограничения, заданные container runtime, и не требует размещения отдельного JSON-файла на узлах.

apiVersion: v1
kind: Pod
metadata:
  name: runtime-default-pod
spec:
  securityContext:
    seccompProfile:
      type: RuntimeDefault
  containers:
  - name: app
    image: hashicorp/http-echo:1.0
    args:
    - "-text=hello"
    securityContext:
      allowPrivilegeEscalation: false

Если seccompDefault включён в конфигурации kubelet или kubelet запущен с флагом --seccomp-default, Pod без явно заданного профиля получает RuntimeDefault. Если это поведение не включено, Pod без seccompProfile запускается как Unconfined.

Для внедрения RuntimeDefault по умолчанию включайте настройку поэтапно: сначала на части узлов, затем проверяйте запуск и основные сценарии работы приложений. Некоторые нагрузки могут требовать менее строгих ограничений. Для таких случаев используйте один из вариантов: явно указать Unconfined, подготовить отдельный Localhost-профиль или запускать нагрузку на узлах без seccompDefault.

Проверить фактически применённый профиль можно средствами container runtime, например через crictl inspect на узле.


Создание локального профиля через NCI

Для кастомного профиля создайте JSON-файл и разместите его на Linux-узлах. В платформе «Штурвал» это выполняется с помощью ресурса NCI (NodeConfigItem). Подробнее о создании и применении NCI — на странице Конфигурация узлов.

Создать NCI можно через импорт манифеста или в интерфейсе: АдминистрированиеКонфигурация узлов. При создании объекта выберите в селекторе узлов kubernetes.io/os: linux, в настраиваемых разделах выберите Файлы и директории, создайте директорию и разместите в ней JSON-файл профиля.

Ниже пример NCI, который создаёт каталог /var/lib/kubelet/seccomp/ и файл /var/lib/kubelet/seccomp/myprofile.json.

apiVersion: node.shturval.tech/v1beta2
kind: NodeConfigItem
metadata:
  name: seccomp-profile
spec:
  files:
  - path: /var/lib/kubelet/seccomp/
    type: directory
    owner: root
    group: root
    mode: "0755"
  - path: /var/lib/kubelet/seccomp/myprofile.json
    type: file
    owner: root
    group: root
    mode: "0644"
    content: |-
      {
        "defaultAction": "SCMP_ACT_LOG",
        "architectures": ["SCMP_ARCH_X86_64", "SCMP_ARCH_X86", "SCMP_ARCH_X32"],
        "syscalls": [
          {
            "names": ["arch_prctl", "sched_yield", "futex", "write", "mmap", "exit_group", "madvise", "rt_sigprocmask", "getpid", "gettid", "tgkill", "rt_sigaction", "read", "getpgrp"],
            "action": "SCMP_ACT_ALLOW"
          },
          {
            "names": ["add_key", "keyctl", "ptrace"],
            "action": "SCMP_ACT_ERRNO"
          }
        ]
      }            
  nodeconfigselector:
    kubernetes.io/os: linux
  priority: 100

После применения NCI проверьте, что профиль появился на нужных узлах:

kubectl get nci seccomp-profile -o yaml
kubectl get nodes --selector kubernetes.io/os=linux

При необходимости проверьте файл непосредственно на узле средствами администрирования ОС. Kubernetes не хранит содержимое Localhost-профиля в Pod: при создании контейнера runtime ищет файл на том узле, куда был назначен Pod.


Запуск нагрузок с профилем Seccomp

Чтобы применить профиль, укажите type: Localhost и имя файла в localhostProfile. Значение localhostProfile задаётся относительно /var/lib/kubelet/seccomp.

Если файл расположен как /var/lib/kubelet/seccomp/myprofile.json, укажите:

securityContext:
  seccompProfile:
    type: Localhost
    localhostProfile: myprofile.json

Если файл расположен во вложенном каталоге /var/lib/kubelet/seccomp/profiles/myprofile.json, укажите localhostProfile: profiles/myprofile.json.

Пример Pod с локальным профилем на уровне контейнера:

apiVersion: v1
kind: Pod
metadata:
  name: nettool
  namespace: namespacename
  labels:
    run: nettool
spec:
  containers:
  - name: nettool
    image: r.shturval.tech/network-multitool:alpine-extra
    securityContext:
      seccompProfile:
        type: Localhost
        localhostProfile: myprofile.json

Пример Pod, где общий профиль задан на уровне Pod, а отдельный контейнер переопределяет его:

apiVersion: v1
kind: Pod
metadata:
  name: seccomp-priority
spec:
  securityContext:
    seccompProfile:
      type: RuntimeDefault
  containers:
  - name: app
    image: hashicorp/http-echo:1.0
    args:
    - "-text=hello"
  - name: custom
    image: hashicorp/http-echo:1.0
    args:
    - "-text=custom"
    securityContext:
      seccompProfile:
        type: Localhost
        localhostProfile: myprofile.json

В интерфейсе при создании или редактировании Pod или Deployment профиль настраивается в блоке Security context на уровне Pod или контейнера. Для локального профиля выберите использование настроенного профиля и укажите имя файла.


Создание профиля по результатам аудита

Для прикладных контейнеров безопаснее двигаться от наблюдения к ограничению:

  1. Создайте аудит-профиль с defaultAction: SCMP_ACT_LOG.
  2. Запустите нагрузку с этим профилем.
  3. Выполните основные пользовательские и служебные сценарии приложения.
  4. Соберите системные вызовы из журналов узла.
  5. Создайте профиль с defaultAction: SCMP_ACT_ERRNO и разрешите только необходимые вызовы через SCMP_ACT_ALLOW.
  6. Проверьте запуск, readiness/liveness-пробы, сетевые обращения, работу с файлами, graceful shutdown и обновление версии приложения.

Минимальный аудит-профиль:

{
  "defaultAction": "SCMP_ACT_LOG"
}

Пример строгого профиля, который по умолчанию блокирует вызовы и разрешает только перечисленные:

{
  "defaultAction": "SCMP_ACT_ERRNO",
  "defaultErrnoRet": 38,
  "architectures": [
    "SCMP_ARCH_X86_64",
    "SCMP_ARCH_X86",
    "SCMP_ARCH_X32"
  ],
  "syscalls": [
    {
      "names": [
        "accept4",
        "bind",
        "brk",
        "close",
        "epoll_ctl",
        "epoll_wait",
        "exit",
        "exit_group",
        "futex",
        "getpid",
        "gettid",
        "mmap",
        "munmap",
        "openat",
        "read",
        "rt_sigaction",
        "rt_sigprocmask",
        "socket",
        "write",
        "writev"
      ],
      "action": "SCMP_ACT_ALLOW"
    }
  ]
}

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


Диагностика

Симптом Возможная причина Что проверить
Pod создан, но контейнер не стартует с CreateContainerError Для Localhost указан несуществующий файл профиля Проверьте localhostProfile, наличие файла на узле и путь относительно /var/lib/kubelet/seccomp.
Контейнер уходит в CrashLoopBackOff Профиль блокирует системный вызов, необходимый процессу Посмотрите события Pod и журналы узла, временно включите аудит через SCMP_ACT_LOG, расширьте список разрешённых вызовов.
Профиль не ограничивает контейнер Контейнер запущен с privileged: true или указан Unconfined Проверьте securityContext.privileged и фактически применённый профиль через инструменты runtime.
Разные результаты на разных узлах На узлах отличаются файлы профиля, container runtime или версии runtime Проверьте NCI, размещение файла на каждом целевом узле и версию runtime.

Команды для первичной проверки:

kubectl describe pod <pod-name> -n <namespace>
kubectl get pod <pod-name> -n <namespace> -o yaml
kubectl get events -n <namespace> --field-selector involvedObject.name=<pod-name>

Для локальных профилей дополнительно проверьте, на какой узел назначен Pod:

kubectl get pod <pod-name> -n <namespace> -o wide

Рекомендации

  1. Начинайте с RuntimeDefault. Это базовое усиление безопасности без сопровождения собственного JSON-профиля.
  2. Используйте Localhost для критичных и хорошо изученных нагрузок. Кастомный профиль полезен, когда требуется явно ограничить набор системных вызовов конкретного приложения.
  3. Идите от аудита к блокировке. Сначала соберите системные вызовы с SCMP_ACT_LOG, затем переходите к SCMP_ACT_ERRNO и SCMP_ACT_ALLOW.
  4. Задавайте профиль на уровне Pod, если он общий для всех контейнеров. Переопределяйте профиль на уровне контейнера только для исключений.
  5. Не используйте Unconfined без причины. Этот режим отключает ограничения Seccomp для нагрузки.
  6. Не рассчитывайте на Seccomp для privileged-контейнеров. Контейнер с privileged: true всегда работает как Unconfined.
  7. Тестируйте профиль на полном сценарии работы приложения. Слишком жёсткий профиль может заблокировать легитимные вызовы уже после успешного старта контейнера.
  8. Контролируйте одинаковость профилей на узлах. Для Localhost файл должен быть доступен на каждом узле, куда может попасть Pod.
  9. Учитывайте различия runtime. Профиль RuntimeDefault и поддержка отдельных действий Seccomp могут отличаться между container runtime и их версиями.