Изоляция процессов и минимизация привилегий: Linux namespaces и seccomp
В статье разберемся, как снизить риски из-за слишком больших полномочий процессов. Материал будет полезен начинающим специалистам, которые работают с контейнерами, виртуализацией и управлением ресурсами.
По статистике, каждые 39 секунд в мире происходит кибератака. Задумайтесь об этом на мгновение. А теперь представьте, что процессы на сервере работают как супергерои, готовые к борьбе с внешними угрозами. Но только если они не захотят стать злодеями. Что если один из процессов решит, что ему по силам взломать систему и получить root-права? Сценарий не такой уж фантастический, как может показаться.
Эскалация привилегий — одна из самых серьезных угроз. При ней уязвимость позволяет злоумышленнику получить доступ к системным ресурсам, нарушая всю безопасность системы. И эти угрозы не исчезают. Они становятся только более изощренными и масштабными. Вот почему изоляция процессов и минимизация их привилегий так важны для безопасности.
Linux namespaces
Когда процессы в Linux имеют доступ ко всем системным ресурсам, это может создавать проблемы. Хорошо бы их отделить и друг от друга, и от тех частей системы, которые им все равно не нужны. Вот тут-то и пригодится инструмент namespace.
Он создает изолированные «песочницы» для процессов, каждая из которых функционирует независимо от других. В результате каждая группа процессов может иметь собственное представление о системных ресурсах.
В современных версиях ядра Linux, таких как 6.12.1, существует восемь типов «песочниц», каждая из которых играет свою роль в изоляции ресурсов и безопасности системы. Чтобы изолировать процессы с помощью Linux-namespace, необходимо использовать команду unshare или другие утилиты, такие как ip для сетевой изоляции. Основная идея — это создание нового пространства для процессов, которые должны работать независимо друг от друга. Итак, перейдем к пошаговой инструкции.
PID namespace
Это пространство отвечает за изоляцию дерева процессов. Другими словами, каждый процесс видит только то, что происходит внутри его «песочницы». А вот доступа к происходящему на уровне хоста у него нет. Создадим такой процесс.
# Создаем новый PID namespace и запускаем bash в нем
sudo unshare --pid --fork --mount-proc /bin/bash
В этом случае процессы, запущенные внутри пространства, будут изолированы и не смогут вмешиваться в работу других процессов на хосте. Команды вроде ps aux покажут только процессы, запущенные в нем.
# Проверяем список процессов в новом namespace
ps aux
В результате увидим, что у процесса будет собственный список других процессов в новом пространстве, но не на хосте.
NET namespace
С помощью NET namespace можно создать изолированную сеть для процесса. Это полезно для создания контейнеров, каждый из которых будет иметь собственный сетевой интерфейс, IP-адреса и маршруты. Пример создания нового сетевого пространства:
# Создаем новый сетевой namespace
sudo ip netns add mynetns
# Запускаем bash в новом namespace
sudo ip netns exec mynetns bash
# Проверяем сетевые интерфейсы в новом namespace
ip link show
В новом пространстве будет виден только локальный интерфейс. Сетевые интерфейсы, настроенные на хосте, не будут доступны.
Если у вас есть несколько приложений, которые должны работать с независимыми сетями, вы можете использовать NET namespace для создания изолированных сетевых пространств, например, для тестирования приложений с разными IP-адресами.
Пример: запускаем два приложения, каждое в своем сетевом пространстве, чтобы они не могли взаимодействовать друг с другом:
# Создаем два разных сетевых namespace
sudo ip netns add app1_net
sudo ip netns add app2_net
# Запускаем приложение в первом namespace
sudo ip netns exec app1_net /bin/bash
# Запускаем приложение во втором namespace
sudo ip netns exec app2_net /bin/bash
Каждое приложение будет работать в своей изолированной сети. Это предотвращает конфликт между сетевыми интерфейсами.
MNT namespace
MNT namespace позволяет изолировать друг от друга файловые системы, создавая уникальные точки монтирования для каждого процесса. Пример:
# Создаем новый MNT namespace
sudo unshare --mount /bin/bash
# Монтируем файловую систему в новом namespace
mount -t tmpfs tmpfs /mnt
# Проверяем точки монтирования
mount | grep /mnt
После выполнения этих команд появится отдельная точка монтирования, которая не будет видна процессам на уровне хоста.
UTS namespace
UTS namespace позволяет изолировать имя хоста и домен. Это полезно, когда нужно, чтобы процессы в изолированном пространстве имели разные имена хоста. Пример:
# Создаем новый UTS namespace
sudo unshare -u /bin/bash
# Меняем имя хоста
hostname newname
# Проверяем имя хоста
hostname
В этом случае имя хоста будет изменено только в пределах пространства, не затрагивая имя хоста на уровне всей системы.
IPC namespace
Используется для изоляции механизмов межпроцессного взаимодействия, таких как очереди сообщений, семафоры и общая память. Процессы внутри одного IPC namespace не могут взаимодействовать с процессами в других пространствах.
# Создаем новый IPC namespace
sudo unshare --ipc /bin/bash
# Проверяем текущие IPC-ресурсы
ipcs
# Попробуем создать очереди сообщений (это будет ограничено только для процессов в новом IPC namespace)
msgctl(IPC_PRIVATE, IPC_CREAT, 0666)
Это помогает изолировать различные компоненты системы или контейнеров, ограничивая их доступ к общим ресурсам.
USER namespace
Позволяет изолировать идентификаторы пользователей и групп (UID/GID). Это дает возможность создавать контейнеры, где процессы работают с различными правами, не затрагивая хост-систему. Такой подход полезен для повышения безопасности, так как контейнеры могут иметь собственные UID и GID, даже если они принадлежат той же системе. Создадим новый USER namespace:
# Создаем новый USER namespace с маппингом UID и GID
sudo unshare --user --map-root-user /bin/bash
# Проверяем UID и GID в новом namespace
id
CGROUP namespace
Используется для изоляции виртуальной файловой системы cgroup, которая управляет выделением ресурсов (например, CPU, память и сеть) для групп процессов. Это важно для контроля над ресурсами, особенно в контейнерных средах или для разделения нагрузки между различными процессами.
# Создаем новый cgroup для ограничения памяти
sudo cgcreate -g memory:/mygroup
# Ограничиваем память для группы процессов
echo 100M > /sys/fs/cgroup/memory/mygroup/memory.limit_in_bytes
# Запускаем процесс в этом cgroup
sudo cgexec -g memory:mygroup /bin/bash
# Проверяем, что ограничения памяти применяются
cat /sys/fs/cgroup/memory/mygroup/memory.limit_in_bytes
Этот пример показывает, как ограничить память для группы процессов с использованием cgroup.
Time namespace
Time namespace используется для изоляции временных параметров, таких как системное время или настройки временной зоны. Процессы внутри одного Time namespace могут иметь собственное время, не влияя на время хостовой системы. Это особенно полезно для тестирования приложений или работы с контейнерами, требующими отдельного временного окружения.
# Создаем новый Time namespace
sudo unshare --time /bin/bash
# Проверяем текущее время
date
# Изменяем время в новом namespace
date --set="2025-01-01 00:00:00"
# Убедимся, что изменения касаются только текущего namespace
date
Time namespace помогает в следующих сценариях:
- Эмулирование будущих или прошлых дат для тестирования приложений.
- Настройка временных зон для различных контейнеров.
- Предотвращение влияния изменений системного времени на хостовую систему.
Изоляция процессов позволяет не только минимизировать риски, связанные с эскалацией привилегий. Благодаря ей также можно повысить эффективность управления ресурсами, выделяя их конкретным процессам или приложениям, не влияя на систему в целом. Изоляция, как правило, получается достаточно гибкой, поэтому ее удобно применять в контейнерных средах и приложениях с микросервисной архитектурой.
Seccomp
Seccomp (Secure Computing Mode) — это инструмент безопасности ядра Linux. Он играет важную роль в минимизации привилегий и снижении поверхности атаки на уровне операционной системы, позволяя ограничить доступ процессов к системным вызовам. Этот механизм помогает изолировать процессы, блокируя системные вызовы, которые могут быть использованы для эксплуатации уязвимостей. Тем самым он повышает общую безопасность системы.
В основе работы seccomp три составляющих: фильтрация системных вызовов, режим работы и профили seccomp. При этом Seccomp действует как фильтр между приложением и ядром, проверяя каждый системный вызов, который пытается выполнить процесс. Если системный вызов не разрешен, он блокируется, предотвращая выполнение небезопасных операций и минимизируя риски для системы.
Режимов работы два. В строгом режиме разрешен минимальный набор системных вызовов, которые процесс может выполнить. Он используется для максимально ограниченной работы процессов. В режиме фильтрации можно настраивать правила для конкретных системных вызовов, давая более гибкий контроль над операциями, которые может выполнить процесс.
Профили seccomp — это JSON-файлы, которые определяют, какие системные вызовы разрешены для выполнения, а какие — нет. Эти профили позволяют точно настроить фильтрацию в соответствии с требованиями безопасности.
Пример использования seccomp:
# Применение профиля seccomp для ограничения системных вызовов
sudo seccomp-bpf -t /path/to/seccomp-profile.json /bin/bash
В этом примере процесс будет использовать профиль seccomp, который ограничивает его действия только теми системными вызовами, которые разрешены в конфигурации профиля.
А вот если необходимо заблокировать конкретные системные вызовы, например clock_nanosleep, можно использовать следующий профиль:
{
"defaultAction": "SCMP_ACT_ALLOW",
"architectures": [
"SCMP_ARCH_X86_64",
"SCMP_ARCH_X86",
"SCMP_ARCH_X32"
],
"syscalls": [
{
"names": ["clock_nanosleep"],
"action": "SCMP_ACT_ERRNO"
}
]
}
Этот профиль блокирует системный вызов clock_nanosleep, но разрешает все остальные. Такой подход помогает ограничить небезопасные операции, при этом позволяет процессам выполнять необходимые действия.
Блокировка небезопасных системных вызовов с помощью seccomp помогает значительно уменьшить поверхность атаки, защищая систему от эксплуатации уязвимостей и других угроз. Инструмент также позволяет точно указывать, какие системные вызовы могут быть выполнены, а какие должны быть заблокированы. Это дает полный контроль над операциями процессов.
Интеграция Linux namespace и seccomp
Очевидно, сочетание namespace и seccomp значительно усиливает безопасность системы, создавая многоуровневую защиту для контейнеров и изолированных процессов. Например, namespace позволяет создать независимые и безопасные среды для процессов, а seccomp ограничивает доступ к системным вызовам и предотвращает выполнение опасных операций.
Но как подружить два инструмента? Давайте рассмотрим на конкретных примерах.
Настройка контейнера в Kubernetes с использованием namespace и seccomp
Пример конфигурации в Kubernetes, где применяется seccomp для ограничения системных вызовов, а также используется пространство имен для изоляции контейнера:
apiVersion: v1
kind: Pod
metadata:
name: secure-pod
spec:
securityContext:
seccompProfile:
type: Localhost
localhostProfile: profiles/custom-seccomp.json
containers:
- name: app
image: myapp:latest
securityContext:
allowPrivilegeEscalation: false
В этом примере используется пользовательский профиль seccomp, который ограничивает доступ к системным вызовам, и отключается возможность повышения привилегий для контейнера.
Создание изолированной среды с помощью namespace и применение seccomp
Пример Bash-скрипта, который создает изолированную среду с использованием Linux namespace и применяет профиль seccomp, предоставляющий доступ только к безопасным системным вызовам:
#!/bin/bash
# Создаем временную директорию для изолированной среды
ROOTFS=$(mktemp -d)
# Копируем необходимые файлы в изолированную среду
cp /bin/bash $ROOTFS
cp /bin/ls $ROOTFS
mkdir $ROOTFS/lib $ROOTFS/lib64
cp /lib/x86_64-linux-gnu/libc.so.6 $ROOTFS/lib
cp /lib64/ld-linux-x86-64.so.2 $ROOTFS/lib64
# Создаем профиль seccomp
cat << EOF > seccomp_profile.json
{
"defaultAction": "SCMP_ACT_ERRNO",
"architectures": ["SCMP_ARCH_X86_64"],
"syscalls": [
{ "name": "read", "action": "SCMP_ACT_ALLOW" },
{ "name": "write", "action": "SCMP_ACT_ALLOW" },
{ "name": "exit", "action": "SCMP_ACT_ALLOW" },
{ "name": "exit_group", "action": "SCMP_ACT_ALLOW" },
{ "name": "execve", "action": "SCMP_ACT_ALLOW" }
]
}
EOF
# Запускаем изолированную среду с применением namespace и seccomp
unshare --mount --uts --ipc --net --pid --fork --user --map-root-user \
--mount-proc -R $ROOTFS \
/usr/bin/seccomp-tools dump \
/bin/bash -c "echo 'Вы находитесь в изолированной среде'; /bin/ls; /bin/bash" \
--seccomp-bpf seccomp_profile.json
# Очищаем временную директорию
rm -rf $ROOTFS
Для запуска этого скрипта потребуются права суперпользователя и установленная утилита seccomp-tools. Обратите внимание, что этот пример демонстрирует базовую изоляцию и ограничение системных вызовов. В реальных сценариях может потребоваться более сложная настройка в зависимости от конкретных требований безопасности.
Этот скрипт создает изолированную среду, применяет профиль seccomp, разрешая только системные вызовы read, write и exit, и затем запускает процесс внутри этого изолированного пространства.
Интеграция в Docker
В Docker можно интегрировать seccomp и Linux namespace для повышения безопасности контейнера. Пример Dockerfile и команды для запуска контейнера с применением профиля seccomp:
FROM ubuntu:20.04
RUN apt-get update && apt-get install -y python3
# Копируем пользовательский профиль seccomp
COPY custom-seccomp.json /etc/docker/seccomp/custom-profile.json
# Запускаем приложение с ограниченными привилегиями
USER nobody
CMD ["python3", "-c", "print('Hello from secure container')"]
FROM ubuntu:20.04
RUN apt-get update && apt-get install -y python3
# Копируем пользовательский профиль seccomp
COPY custom-seccomp.json /etc/docker/seccomp/custom-profile.json
# Запускаем приложение с ограниченными привилегиями
USER nobody
CMD ["python3", "-c", "print('Hello from secure container')"]
Для запуска контейнера с применением профиля seccomp:
docker run --security-opt seccomp=/etc/docker/seccomp/custom-profile.json myapp
В этом примере контейнер запускается с применением профиля seccomp, который ограничивает доступ к небезопасным системным вызовам, обеспечивая дополнительную защиту.
Настройка изоляции процессов с использованием Linux namespace и seccomp
Теперь давайте рассмотрим пример, как можно настроить изоляцию процессов с помощью Linux-namespace и seccomp для обеспечения безопасности.
Шаг 1: Создание изолированного процесса с помощью unshare
Сначала мы создадим новый процесс в отдельном PID namespace, чтобы он не видел процессы на хосте:
sudo unshare --pid --fork --mount-proc /bin/bash
Шаг 2: Применение фильтра seccomp
После создания мы применяем seccomp для ограничения доступных системных вызовов. В данном случае мы разрешаем только безопасные вызовы: read, write и exit — а все остальные блокируем. Это минимизирует риски, связанные с уязвимостями, и защищает процесс от потенциальных атак:
#include <seccomp.h>
int main() {
scmp_filter_ctx ctx;
ctx = seccomp_init(SCMP_ACT_KILL); // Запрещаем все системные вызовы по умолчанию
// Разрешаем безопасные системные вызовы
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(read), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit), 0);
// Применяем фильтр
seccomp_load(ctx);
// Запуск оболочки
execl("/bin/bash", "/bin/bash", NULL);
return 0;
}
Этот код создает фильтр seccomp с использованием libseccomp, что является более предпочтительным методом для управления системными вызовами. Вы можете скомпилировать и запустить его внутри контейнера или изолированного процесса.
Шаг 3: Запуск изолированного процесса
Второй шаг с кодом на C уже включает запуск оболочки, что автоматически инициирует процесс. Однако если вы хотите просто запускать процесс в изолированном пространстве, для этого достаточно использовать команду в вашем скрипте или через unshare (в случае с Bash):
/bin/bash
Этот процесс будет работать только с теми системными вызовами, которые разрешены в профиле seccomp, и не будет иметь доступа к другим процессам или ресурсам на хосте.
Заключение
Использование Linux-namespace и seccomp — это не просто защита, а мощный щит для предотвращения атак. Namespace изолирует процессы, а seccomp блокирует доступ к более чем 200 системным вызовам, из которых более 50 считаются высокорисковыми для безопасности. В Docker эти настройки снижают вероятность успешной эксплуатации уязвимостей на 95%.
По сути, это как строить защиту для вашего дома: namespace — это стены, разделяющие различные комнаты, а seccomp — это система безопасности, которая блокирует нежелательные действия, даже если злоумышленник каким-то образом окажется за дверью.