Сегодня Docker является одним из самых популярных инструментов в области контейнеризации. В основе Docker заложена концепция использования контейнеров. С технической точки зрения контейнер — это процесс, наподобие тех, которые запускаются в операционных системах. Как и любой другой продукт, Docker подвержен различным атакам и уязвимостям. В этой статье будут рассмотрены методы, которые помогут повысить уровень безопасности контейнеров Docker.    По умолчанию Docker запускает контейнеры от имени root пользователя. Чем грозит использование root пользователя в запущенных контейнерах?  Если в приложении, которое работает в  контейнере, будет обнаружена уязвимость, то это может позволить злоумышленникам выйти из контейнера и выполнить различные действия с правами root на хостовой ОС. Тем самым вы создаете слишком привилегированную среду, которая дает злоумышленникам больше возможностей в случае взлома. Чтобы убедиться в этом, запустим пару разных контейнеров. И первый — образ с веб-сервером Nginx. Для этого используем команду: Далее осуществим вход в оболочку контейнера при помощи команды:   После того, как мы оказались в контейнере, можно заметить, что в строке приглашения к вводу отображается пользователь root и символ #. Также проверить, под каким пользователем мы авторизованы в системе, можно при помощи команды:   Далее запустим контейнер с образом легковесной операционной системы Alpine: Осуществим вход в оболочку Bourne shell (sh): И выполним команду id: Как и в случае с контейнером Nginx, можно увидеть, что мы подключились к контейнеру как root. В качестве решения при запуске контейнеров всегда указывайте имя обычного (непривилегированного пользователя). Для этого при запуске контейнеров необходимо использовать ключ -u (или длинную версию --user) в котором можно задать необходимого пользователя. Рассмотрим пример с запуском контейнера с Nginx который мы запускали ранее: Но теперь подключимся к созданному контейнеру при помощи exec и зададим пользователя nobody:   Как видно на скриншоте выше, приглашение к вводу содержит имя пользователя nobody и символ $. Это означает, что сессия запущена от имени обычного непривилегированного пользователя. Также указать конкретного пользователя можно и в Dockefile. Для этого используется инструкция USER:   Соберем образ при помощи команды: И запустим: Выполним команду id: Как можно заметить, мы подключились к контейнеру от имени ранее созданного пользователя — nonroot.   Docker использует механизм ядра Linux под названием capabilities — средства для управления привилегиями в операционных системах семейства Linux. Если вкратце — это атрибуты ядра Linux, которые предоставляют привилегии root пользователя процессам или исполняемым файлам. К таким привилегиям можно отнести право на изменение UID процесса, право монтировать файловые системы, изменять конфигурации MAC и т. д. С полным списком всех доступных привилегий можно ознакомиться по ссылке. При запуске контейнеров Docker можно как задать необходимые привилегии, так и выставить запрет на их использование. Самым безопасным способом является запрет на использование всех привилегий, а затем выдача разрешений только для необходимых привилегий. Для этого в Docker существуют следующие опции: --cap-drop — запрет на выполнение capabilities --cap-add — разрешение на выполнение capabilities Обе эти опции можно использовать сразу для одного контейнера. Рассмотрим на примере запуска контейнера с alpine:   В примере выше мы сначала создали запрет на выполнение любых привилегий в контейнере (опция --cap-drop со значением all), а потом разрешили выполнять привилегию CAP_SYS_ADMIN (--cap-add). Привилегия CAP_SYS_ADMIN, в частности, позволяет монтировать и размонтировать файловые системы.   Использование опции no-new-privileges позволит предотвратить повышения привилегий в контейнере путем использования setuid и setgid.  setuid — флаги с правами доступа, которые разрешают пользователям запускать исполняемые файлы с правами владельца исполняемого файла.  setgid — флаги с правами доступа, которые разрешают пользователям запускать исполняемые файлы с правами группы исполняемого файла.  Ознакомиться подробнее с данными правами доступа можно по ссылкам setuid и setgid. Перейдем к демонстрации. Соберем образ из следующего Dockerfile: В собранном образе создадим еще одну оболочку bash с именем setuidbash и назначим ей права root пользователя при помощи setuid. Это означает, что созданную оболочку будем использовать для получения root прав. Соберем образ:   Далее запустим контейнер: Так как в Dockerfile мы указали пользователя, то сессия запустилась от имени обычного непривилегированного пользователя с именем newuser. Теперь повысим наши привилегии до root, выполнив команду:   Ключ -p (privileged) означает, что запуск будет произведен как suid. Как можно увидеть на скриншоте выше, приглашение к вводу изменилось с символа $ (обычного пользователя) на символ # (root пользователь). Это означает, что получение root прав выполнилось успешно. Чтобы предотвратить повышение привилегий, контейнер необходимо запустить с опцией no-new-privileges: Теперь выполним команду /bin/setuidbash -p еще раз: Повышений привилегий не сработало. Если в контейнере нет надобности в записи и создании каких-либо файлов, то его стоит запускать в режиме «только для чтения» (read-only). При включенном режиме в контейнере нельзя будет создавать новые файлы и редактировать существующие. В плане безопасности это приведет к тому, что контейнер не сможет записывать или изменять какие-либо данные внутри себя. Проверим данный способ на практике. Для начала запустим контейнер с ОС ubuntu:   Перейдем в контейнер и создадим файл test.txt: Теперь запустим контейнер с опцией -- read-only: Подключимся к нему и попытаемся создать файл test.txt — чтобы проверить, как сработает опция:   Возникла ошибка touch: cannot touch 'test.txt': Read-only file system, которая сообщает, что файловая система запущена в режиме только для чтения.   По умолчанию для всех создаваемых контейнеров Docker использует свой bridge интерфейс под названием docker0, который всегда создается в системе вместе с установкой Docker:   Отдельно стоит выделить, что при использовании Docker-compose контейнеры подключаются к единой создаваемой сети, и все контейнеры доступны друг для друга. Все контейнеры, подключенные к интерфейсу docker0 будут взаимодействовать друг с другом. Однако для каждого контейнера необходимо создавать свою отдельную сеть и подключать к этой сети только те контейнеры, которые будут использоваться. Тем самым можно изолировать нужные контейнеры или вовсе отключить их от сети. Для создания сетей в Docker используется команда:   Где параметр driver может принимать одно из следующих значений: bridge; host; overlay; ipvlan; macvlan. Чтобы подключить контейнер к сети, необходимо использовать параметр --network и в качестве значения указать имя требуемой сети. Контейнеры Docker являются отличным вариантом для оперативной доставки программного обеспечения и позволяют существенно сократить использование как технических, так и финансовых ресурсов. Популярность Docker привлекла большое внимание со стороны злоумышленников. В этом контексте стоит уделить безопасности особое внимание. НЛО прилетело и оставило здесь промокод для читателей нашего блога: — 15% на все тарифы VDS (кроме тарифа Прогрев) — HABRFIRSTVDS.  Системный администратор