### Проблема 1. Выполнение контейнеров по умолчанию от имени суперпользователя
Если в образе контейнера не прописан не суперпользователь или не указано, что нужно запускать не от имени пользователя по умолчанию, то по умол- чанию контейнер будет выполняться от имени суперпользователя. И, как легко убедиться (если вы не настроили пространства имен пользователей), он окажется суперпользователем не только внутри контейнера, но и на хост-компьютере.
**Решение**
1. Переопределение идентификатора пользователя
Переопределить идентификатор пользователя в среде выполнения можно, указав идентификатор пользователя для контейнера.
В runc для этого необходимо модифицировать файл config.json внутри комплекта. Попробуйте, например, поменять process.user.uid следующим образом:
'''
...
"process": {
"terminal": true,
"user": {
"uid": 5000,
... }
... }
'''
Переопределить настройки пользователя можно с помощью опции --user, вот так:
'''
$ docker run -it --user 5000 ubuntu bash
'''
### Проблема 2. Требование выполнения от имени суперпользователя внутри контейнера
Многие часто используемые образы контейнеров инкапсулируют распро- страненное программное обеспечение, которое изначально было предназна- чено для запуска непосредственно на сервере. Стандартный контейнер Nginx был настроен для выполнения по умолчанию от имени суперпользователя. Если вы запустите контейнер nginx и посмотрите на работающие в нем процессы, то увидите, что главный процесс запущен от имени суперпользователя.
Вполне логично, что при выполнении на сервере код nginx запускается от имени суперпользователя. По умолчанию он принимает запросы на обычном веб-порте 80. А для открытия портов с номером меньше 1024 необходима привилегия CAP_NET_BIND_SERVICE, и самый простой способ гарантировать ее наличие — запускать nginx от имени суперпользователя. Но в контейнере подобное требование особого смысла не имеет, ведь там проброс портов означает, что код nginx может прослушивать на любом порте с пробросом на порт 80 (при необходимости) на хосте.
**Решение**
Сборка образа Nginx, выполняемого от имени несуперпользователя.
Еще одна причина, по которой образы контейнеров иногда требуют запуска от имени суперпользователя: им иногда нужно устанавливать программное обеспечение с помощью таких систем управления пакетами, как yum и apt. Логично выполнять это во время сборки образа контейнера, но в Dockerfile можно включить вслед за установкой шаг с командой USER, чтобы запускать образ от имени несуперпользователя.
Я настоятельно рекомендую вам не разрешать контейнерам устанавливать дополнительное программное обеспечение во время выполнения. И тому есть несколько причин.
* Нерациональность: в случае установки всего необходимого ПО во время сборки достаточно сделать это один раз, а не повторять всю процедуру при каждом создании нового экземпляра контейнера.
* Пакеты, устанавливаемые во время выполнения, не были просканированы на уязвимости.
* Вероятно, еще худший, чем отсутствие сканирования на уязвимости, хотя в чем-то родственный факт: при установке во время выполнения сложно отследить, какие точно версии пакетов установлены во всех раз- личных запущенных экземплярах контейнеров. Поэтому, даже если у вас появится информация о какой-то уязвимости, вы не будете знать, какие контейнеры необходимо удалить и развернуть заново с исправлениями.
* В зависимости от приложения в случае установки во время сборки контейнер можно запустить с доступом только для чтения (с помощью опции --read-only команды docker run или путем установки в значение true поля ReadOnlyRootFileSystem объекта PodSecurityPolicy Kubernetes), что значительно усложнит для злоумышленника установку кода.
* Добавление пакетов во время выполнения означает, что контейнер не рассматривается как неизменяемый.
Везде, где возможно, старайтесь запускать код своих приложений от име- ни не суперпользователей или задействовать для него пространства имен пользователей, чтобы суперпользователь внутри контейнера отличался от супер- пользователя хоста. Один из удобных на практике способов применения пространств имен — контейнеры, не требующие полномочий суперпользователя, если ваша система их поддерживает.
### Флаг --privileged и привилегии
Docker и прочие среды выполнения контейнеров позволяют указывать опцию --privileged при запуске контейнера. Многие считают, что флаг --privileged эквивалентен выполнению контей- нера от имени суперпользователя, но вы ведь уже знаете, что контейнеры выполняются от имени суперпользователя по умолчанию. Так какими же еще полномочиями этот флаг наделяет контейнер?
Ответ: хотя в Docker процесс выполняется от имени идентификатора поль- зователя root по умолчанию, значительная группа обычных привилегий пользователя root Linux не предоставляется ему автоматически.
С помощью утилиты capsh можно легко посмотреть, какие привилегии полу- чает контейнер, сначала в контейнере без флага --privileged, а затем — с ним.
Данный набор по умолчанию включает CAP_SYS_ADMIN, и один этот флаг при- вилегии предоставляет доступ к огромному множеству привилегированных действий, включая операции с пространствами имен и монтирование фай- ловых систем.
Флаг --privileged появился в Docker ради функциональности «Docker в Docker», широко используемой для сборки утилит и систем CI/CD, ко- торые работают в виде контейнеров, требующих доступа к демону Docker для сборки с помощью Docker образов контейнеров.
Даже если у вас есть серьезные причины запускать контейнеры с флагом --privileged, я рекомендую вам описать средства контроля или по крайней мере аудита использования его только для тех контейнеров, которым он действительно нужен. Вместо него лучше при возможности задавать от- дельные привилегии.
Для трассировки событий cap_capable можно воспользоваться утилитой Tracee, которая отображает привилегии, требуемые у ядра заданным контейнером.
Выяснив, какие привилегии необходимы контейнеру, вы можете, следуя принципу минимума полномочий, указать в среде выполнения лишь необ- ходимые. Рекомендуемый подход: убрать все привилегии, а затем добавить обратно только нужные, вот так:
$ docker run --cap-drop=all --cap-add=<прив1> --cap-add=<прив2> <образ> ...
### Монтирование каталогов с конфиденциальными данными
С помощью опции -v можно примонтировать каталог хоста к контейнеру и получить из контейнера доступ к нему. При этом ничто не мешает вам примонтировать к контейнеру корневой каталог хоста.
Взломавший этот контейнер злоумышленник оказывается суперпользова- телем на хосте и получает полный доступ ко всей файловой системе хоста.
Монтирование всей файловой системы — пример скорее патологический, но существует и множество других примеров разной степени изощренности.
* Монтирование /etc позволит модифицировать файл /etc/passwd хоста из- нутри контейнера, а также нарушать работу заданий cron, init и systemd.
* Монтирование /bin и тому подобных каталогов, например /usr/bin или /usr/sbin, позволит контейнеру записывать исполняемые файлы в каталог хоста — в том числе перезаписывать уже существующие исполняемые файлы.
* Монтирование каталогов журналов хоста к контейнеру может открыть для злоумышленника возможность изменять журналы, а значит, и ликвидировать следы его мерзких деяний на этом хосте.
* В среде Kubernetes монтирование /var/log открывает доступ ко всей файловой системе хоста любому пользователю, у которого есть доступ к команде kubectl logs. А все потому, что файлы журналов контейнера представляют собой символические ссылки из каталога /var/log на про- чие места файловой системы, но ничто не мешает контейнеру создать символическую ссылку на любой другой файл.
### Монтирование сокета Docker
В среде Docker всю работу, по существу, выполняет процесс-демон Docker. При запуске утилиты командной строки docker он отправляет демону ин- струкции через сокет Docker, расположенный в /var/run/docker.sock. Любой, кто может записывать данные в этот сокет, может и отправлять инструкции демону Docker. Демон выполняется от имени суперпользователя и легко мо- жет произвести для вас сборку и запуск любого программного обеспечения,включая — как вы видели — запуск контейнера от имени суперпользователя на хосте. Следовательно, доступ к сокету Docker, по существу, эквивалентен доступу с полномочиями суперпользователя на хосте.
Сокет Docker очень часто монтируют в утилитах CI наподобие Jenkins, где он необходим для отправки Docker инструкций по запуску сборки образов, в виде составной части конвейера. Это вполне допустимое его применение, которое, однако, создает потенциальное уязвимое место на радость злоумышленнику. Пользователь, у которого есть возможность изменять Jenkinsfile, может заста- вить Docker выполнять нужные ему команды, в том числе предоставляющие пользователю доступ к кластеру с полномочиями суперпользователя. Поэтому крайне не рекомендуется задействовать при промышленной эксплуатации конвейер CI/CD, монтирующий сокет Docker.
### Совместное использование пространств имен контейнером и его хостом
Иногда возникают основания для того, чтобы контейнер использовал не- которые из тех же пространств имен, что и его хост. Например, допустим, процессу в контейнере Docker нужен доступ к информации о процессе с хоста. В Docker подобную возможность можно получить с помощью па- раметра --pid=host.
Напомню, что все контейнеризованные процессы видны на хосте; следова- тельно, совместное использование пространства имен процессов с контей- нером позволяет ему видеть другие контейнеризованные процессы.
### Вспомогательные контейнеры
Вспомогательному контейнеру (sidecar container) умышленно предоставля- ется доступ к одному из пространств имен контейнера приложения в целях делегирования ему какой-либо функциональности данного приложения. В микросервисной архитектуре встречается функциональность, которую необходимо повторно использовать во всех микросервисах системы, поэтому нередко подобную функциональность помещают в образы вспомогательных контейнеров для удобства повторного использования. Ниже представлены несколько распространенных примеров.
* Вспомогательные контейнеры типа service mesh берут на себя сетевую функциональность контейнера приложения. Service mesh может, напри- мер, обеспечить использование во всех сетевых подключениях взаимного протокола TLS. Делегирование этой функциональности вспомогательному контейнеру означает, что всегда, когда контейнер развертывается вместе со вспомогательным, он будет устанавливать защищенные TLS-соединения; и каждой команде разработчиков не нужно тратить время и включать эту возможность в код приложения.
* Вспомогательные контейнеры наблюдения позволяют задавать цели и настройки журналирования, трассировки и сбора метрик.
* Вспомогательные контейнеры безопасности позволяют контролировать, какие исполняемые файлы и сетевые подключения разрешаются внутри контейнера приложения.