Профилактика в работе с Docker-контейнерами - Академия Selectel
В панель

Профилактика в работе с Docker-контейнерами

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

Изображение записи

Сетевое взаимодействие контейнеров

Контейнеры не были бы таким удобным инструментом, если бы не могли взаимодействовать друг с другом и внешними ресурсами. Поэтому стоит уделить внимание сетевым настройкам. 

В этом разделе мы не будем сильно погружаться в детали организации сети в Docker — об этом можно почитать в документации. Рассмотрим некоторые рекомендации для безопасности на уровне сети. 

Не используйте bridge docker0

Docker использует сетевые драйверы, которые задействуют свои определенные параметры: подсеть, шлюз по умолчанию и другое. Драйвер bridge docker0 запущен по умолчанию, если не указано ничего другого. Он позволяет беспрепятственно общаться контейнерам внутри выделенной сети, подключенным к одному и тому же мосту. А также обеспечивает изоляцию от внешних контейнеров, которые не подключены к такому же мосту на одном хосте демона Docker. 

Но важно понимать, что подключение к мосту по умолчанию влечет за собой риски: не связанные между собой контейнеры могут начать взаимодействовать друг с другом. Плюс эта сеть по умолчанию уязвима для ARP-спуфинга и MAC-flooding, так как к ней не применяется фильтрация. 

Создайте собственный мост и подключите к нему только нужные приложения. Так вы повысите изолированность по сравнению с мостом по умолчанию. Более того, такие контейнеры уже могут использовать DNS-имена для общения в пределах сети, а не только IP-адреса. А еще в процессе работы контейнера можно быстро подключать и отключать его от пользовательских сетей — без перезапуска, который включен по умолчанию. 

Подробнее о создании собственного сетевого моста можно прочитать в официальной документации.

Не используйте network namespace хоста

Аналогично user namespace, необходимо ограничивать и network namespace, чтобы не использовать лишний раз сеть хоста. Здесь та же идея, что и с docker 0: нужно изолировать контейнер от хоста и других контейнеров, иначе он может получить доступ к сетевым интерфейсам и ресурсам хоста. Более того — будет способен открывать порты с номером меньше 1024. 

Традиционно в системе Linux для привязки порта процесс должен быть запущен пользователем root, иметь setUID root или CAP_NET_BIND_SERVICE. Поэтому для работы может понадобиться привилегированный доступ. Кроме того, контейнер может совершать неожиданные действия — например, завершать работу хоста. Чтобы этого избежать, просто не используйте опцию —network=host.

Рассмотрим пример: запустим контейнер с длительной операцией sleep. Оказывается, этот процесс виден из другого контейнера, запущенного с опцией —pid=host:


    dockerenjoyer@ubuntu:~$ docker run --name sleep --rm -d alpine sleep 1000 
dockerenjoyer@ubuntu:~$ docker run --pid=host --name alpine --rm -it alpine sh 
# ps | grep sleep
390431 root  	0:00 sleep 1000
390655 root  	0:00 grep sleep

Больше всего удивляет, что команда kill -9  из второго контейнера позволяет прервать выполнение процесса sleep в первом! Посмотрите сами:


    / # kill -9 390431
/ # ps | grep sleep
390708 root  	0:00 grep sleep

Публикуйте порты только для хоста

Публикация портов контейнера по умолчанию — небезопасное занятие. Они становятся доступными не только для хоста Docker, но и внешнего мира.

Docker слушает все сетевые интерфейсы, так как привязывает опубликованные порты к адресу 0.0.0.0. Но чаще трафик ожидается только на одном из них, поэтому подобный подход повышает риск атаки. 

Рекомендую при запуске контейнера привязать порты к конкретным интерфейсам на хосте. Например, вот так:


    docker run -d  -p 192.168.100.4:48153:80 nginx

Таким образом, только хост с локальным адресом 192.168.100.4 будет иметь доступ к опубликованному порту контейнера.

Есть и другой вариант. Можно настроить default binding address для публикации портов, чтобы они были доступны только хосту по умолчанию. Для этого вы можете указать адрес loopback (127.0.0.1), настроив параметр ip в конфигурационном файле daemon.json.

Привилегии контейнеров

Не используйте флаг —privileged

Избегайте работы с привилегированными контейнерами с флагом —privileged. Он предоставляет им практически все возможные права и противоречит опции —cap-drop=ALL. Почему это опасно —  рассмотрим на примере. Попробуем запустить контейнер с флагом —privileged и повторим действия:


    # docker run -it --privileged -p 127.0.0.1:3306:3306  --name mariadb -e MARIADB_ROOT_PASSWORD=superpass mariadb sh
9f58ac625a705cb56e25050bf67b0b631f5fd9b865bb5d7bae3aee30d50011a2
# whoami
root
# cd /root
# ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
	link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
26: eth0@if27: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default
	link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
# ip link delete eth0
# ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
	link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00

Видим, что привилегированному пользователю удалось удалить интерфейс. Допускаю, что злоумышленник пойдет дальше хулиганства внутри контейнера. 

Теперь попробуем запустить контейнер от имени обычного пользователя. Посмотрим в уже работающем контейнере, какие там есть юзеры. Видим пользователя по умолчанию с id 999 — от него и запустим:


    # cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
_apt:x:100:65534::/nonexistent:/usr/sbin/nologin
mysql:x:999:999::/home/mysql:/bin/sh

Видим, что обычный пользователь не может перейти в каталог root и удалить интерфейсы:


    # docker run -u 999 -p 127.0.0.1:3306:3306  --name mariadb -e MARIADB_ROOT_PASSWORD=superpass -d mariadb
f726199bbffd090dc916d15f432835c79a6c587850f423c866174379c17738e0
# docker exec -ti mariadb sh
$ whoami
mysql
$ cd /root/
sh: 2: cd: can't cd to /root/
$ ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
	link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
28: eth0@if29: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default
	link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
$ ip link delete eth0
RTNETLINK answers: Operation not permitted

Запретите новые привилегии

Рекомендую ограничить добавление новых привилегий после того, как контейнер был создан. Это можно сделать с помощью команды —security-opt=no-new-privileges:


    docker run -it --security-opt=no-new-privileges:true imagename

Если контейнер запускается от имени non-root пользователя (так и должно быть), то запрет на новые привилегии снизит риски того, что злоумышленник их получит.

Сбросьте привилегии по умолчанию

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

Вот шаблон, по которому добавляются все привилегии:


    docker run --cap-drop=all --cap-add=<привилегия_1> --cap-add=<привилегия_2> <образ> …

Монтирование каталогов

С помощью опции -v можно примонтировать каталог хоста к контейнеру. Пространство имен последнего «расширяется», но какой ценой с точки зрения безопасности… Рассмотрим основные рекомендации, как делать не стоит.

  • Не монтируйте полностью файловую систему root. Это крайне опасно с точки зрения безопасности.
  • Не монтируйте /etc. Он позволяет модифицировать /etc/passwd хоста изнутри контейнера, а еще может нарушить работу заданий cron, init и systemd.
  • Без необходимости не монтируйте /bin и подобные ему каталоги (/usr/bin или /usr/sbin). Они позволят контейнеру записывать исполняемые файлы в каталог хоста и даже перезаписывать уже существующие.
  • Не монтируйте каталоги журналов хоста к контейнеру. В противном случае злоумышленник сможет изменять журналы и ликвидировать следы своих действий.
  • Не монтируйте такие чувствительные каталоги, как /dev, /proc и /sys, чтобы избежать несанкционированного доступа к устройствам, процессам и другим системным ресурсам.
  • Будьте внимательны к тому, какие права доступа предоставляете на смонтированный каталог.  Ограничьтесь только теми, что вам необходимы.

Профили безопасности

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

Seccomp (Secure Computing mode, безопасный режим вычислений) — это механизм ограничения набора системных вызовов, разрешенных приложению. У каждого процесса может быть свой профиль, который представляет из себя «белый список». Он также позволяет ограничить действия, доступные в контейнере, и по умолчанию включен для Docker. Стандартный профиль Seccomp обеспечивает адекватный режим работы контейнеров и отключает около 44 системных вызовов из более чем 300.

Когда вы запускаете контейнер, активируется профиль по умолчанию, который вы можете переопределить в —security-opt. Подробнее о профиле Seccomp в Docker можно прочитать в документации

AppArmor (Application Armor, броня приложения) — один из нескольких модулей безопасности Linux (Linux security modules, LSM), который можно включить в ядре. В AppArmor профиль можно связать с исполняемым файлом и на языке привилегий описать, что для него доступно, а что — нет. 

Аналогично Seccomp, Docker использует профиль по умолчанию при запуске контейнеров, но его можно переопределить в параметре —security-opt. Подробнее о работе с AppArmor можно прочитать в документации.

SELinux (Security-Enhanced Linux, Linux с улучшенной безопасностью) — еще один тип LSM. Позволяет ограничивать доступные действия с файлами и другими процессами. 

SELinux управляет доступом на основе меток процессов и объектов системы, что типично для MAC. Взаимодействие между политикой SELinux и Docker сосредоточено на двух проблемах: защите хоста и контейнеров друг от друга. В Docker по умолчанию установлен флаг -selinux-enabled.

Все механизмы безопасности предназначены для низкоуровневого управления поведением процессов. Генерация профилей — непростая задача, ведь даже незначительное изменение приложения может превратиться в «путешествие на 20 минут». Если у вас нет возможности этим заниматься самостоятельно, то стандартных Seccomp и AppArmor для Docker будет достаточно.

Ограничение потребления системных ресурсов

По умолчанию у Docker-контейнеров нет никаких ограничений по системным ресурсам. Они могут использовать столько, сколько позволяет планировщик ядра хоста. Но есть способы,  контролировать объем памяти или CPU для контейнеров. 

СGroup, или контрольные группы, — это механизмы Linux, на которых также базируется контейнеризация, как и на пространствах имен. Они позволяют контролировать доступ к таким ресурсам, как CPU, ОЗУ, и дискам I/O для каждого контейнера.


Кроме того, изначально контейнер ассоциируется с выделенной cgroup. Но если есть опция —cgroup-parent, вы подвергаете ресурсы хоста риску DoS-атаки, поскольку разрешаете снимаете ограничения между хостом и контейнером.

Рекомендую ограничивать количество ресурсов при запуске контейнера. Например, вот так:


    --memory=”400m”
--memory-swap=”1g”
--cpus=0.5
--restart=on-failure:5
--ulimit nofile=5
--ulimit nproc=5

Подробнее про ограничения на потребление ресурсов читайте в официальной документации.

Заключение

Теперь вы знаете основы безопасности в Docker-контейнерах. Они обязательно вам пригодятся, поэтому сохраните материал в закладки. Используйте его как ультимативный гайд-шпаргалку.

Читайте также: