- PVSM.RU - https://www.pvsm.ru -
— Сударь, каким образом вас взломали?
— Не образом, а контейнером.
Старинный анекдот
Все лишние компоненты компьютерной системы могут оказаться источником совершенно необязательных уязвимостей. Поэтому образы контейнеров должны по возможности содержать только то, что нужно приложению. И их размер имеет значение не только с точки зрения удобства дистрибуции, но также стоимости владения и безопасности. В этой статье мы поговорим о методах минимизации размера и поверхности атаки образов Docker, а также об инструментах их сканирования на предмет наличия уязвимостей.
Тот, кто хоть немного работал с Docker, наверняка слышал про образ alpine [1]. Он создан на основе дистрибутива Alpine Linux [2], который по сравнению, например, с Debian или Ubuntu при размере базового образа в 5 Мб оставляет взломщикам гораздо меньше возможностей для атаки. Если приложение сможет работать в alpine, это будет отличным способом оптимизации.
А что насчет бинарных файлов? Может ли приложение работать автономно? Если да, то есть основания рассчитывать на дополнительное уменьшение размера. В качестве базового для таких образов, как Debian и Ubuntu, обычно используется scratch
, но в нем также может заработать приложение на golang. Gianluca Arbezzano создал репозиторий [3] с готовыми бинарными файлами минимального размера. Давайте попробуем linux_386.
curl -SsL https://github.com/gianarb/micro/releases/download/1.0.0/micro_1.0.0_linux_386 > micro
Мы можем включить этот бинарный файл в scratch-образ с помощью вот такого Dockerfile:
FROM scratch
ADD ./micro /micro
EXPOSE 8000
CMD ["/micro"]
docker build -t micro-scratch .
docker run -p 8000:8000 micro-scratch
В итоге нам удалось запустить http-приложение в образе размером всего 5 Мб, то есть мы уменьшили его более чем в два раза по сравнению 12 Мб, которые занимает образ, созданный на основе alpine.
Scratch-образ невозможно использовать с любыми приложениями, но ради снижения накладных расходов стоит попытаться.
Для Ruby в качестве базового можно использовать ruby:2.3-alpine [4]. Ruby в нем устанавливается из исходников, а не из пакета Alpine. С учетом семантического версионирования [5] релиз 2.3 будет получать обновления безопасности напрямую от разработчиков.
В противном случае пришлось бы самостоятельно устанавливать Ruby из исходников и отслеживать выход новых версий или использовать пакет из состава Alpine и следить за его обновлением силами разработчиков дистрибутива.
Если для базового образа выпущено обновление безопасности, необходимо обновить те образы, которые на нем основаны. Здесь могут помочь уведомления MicroBadger [6], которые можно отправлять, например, в Slack, как это делают ребята из Microscaling для официальных образов alpine и ruby.
Они также используют оповещения для автоматического запуска процедуры сборки/пересборки в случае изменения базовых образов. Такая функциональность есть и в Docker Hub, но Microscaling утверждает, что MicroBadger лучше, так как может использоваться с любой системой, поддерживающей веб-хуки (например, CI или сканером безопасности).
Одним из ключевых отличий виртуальных машин от контейнеров является использование последними ядра основной системы. По умолчанию контейнеры Docker запускаются с привилегиями root, что может привести к серьезным проблемам в случае прорыва изоляции, так как запущенный под рутом скомпрометированный контейнер может получить root-доступ к основной системе.
Однако риски можно уменьшить, запустив контейнер под обычным пользователем. Вот как это сделать для Rails-приложения:
# Создаем рабочий каталог
WORKDIR /app
# Копируем код Rails-приложения в образ
COPY . ./
# Создаем обычного пользователя, устанавливаем права и меняем юзера
RUN addgroup rails && adduser -D -G rails rails
&& chown -R rails:rails /app
USER rails
Помимо непосредственно хранения реестры контейнеров могут сканировать загружаемые в них образы на наличие уязвимостей. Например, Docker проводит сканирование безопасности [7] официальных, а также пользовательских образов, загруженных в Docker Cloud.
Для сканирования безопасности образов реестр Quay.io использует Clair [8] — продукт с открытым исходным кодом от CoreOS. Совсем недавно в Clair была добавлена [9] поддержка Alpine, что на самом деле очень здорово. Будем надеяться, что эта функциональность скоро будет доступна и в Quay. Помимо Clair существуют сканеры TwistLock [10] и Aqua [11], но в большинстве случаев за их использование надо платить.
Clair — это приложение на Golang, которое реализует набор HTTP API для выгрузки, загрузки и анализа образов. Данные об уязвимостях загружаются из различных источников, таких как Debian Security Tracker [12] или RedHat Security Data [13], и сохраняются в Postgres. Clair работает по принципу статического анализатора, поэтому, чтобы просканировать контейнер, его не надо запускать — проверяется лишь файловая система образа.
docker run -it -p 5000:5000 registry
С помощью этой команды мы запустили собственный реестр, чтобы использовать его в качестве источника образов для сканирования. Давайте попробуем загрузить в него образ micro
от Gianluca Arbezzano [3]:
docker pull gianarb/micro:1.0.0
docker tag gianarb/micro:1.0.0 localhost:5000/gianarb/micro:1.0.0
docker push localhost:5000/gianarb/micro:1.0.0
Далее установим Clair.
mkdir $HOME/clair-test/clair_config
cd $HOME/clair-test
curl -L https://raw.githubusercontent.com/coreos/clair/v1.2.2/config.example.yaml -o clair_config/config.yaml
curl -L https://raw.githubusercontent.com/coreos/clair/v1.2.2/docker-compose.yml -o docker-compose.yml
Пропишите в $HOME/clair_config/config.yml
ваши настройки подключения к базе данных postgresql://postgres:password@postgres:5432?sslmode=disable
Для запуска Postgres и Clair нужно выполнить следующую команду:
docker-compose up
Чтобы облегчить процедуру тестирования, воспользуемся CLI под названием Hyperclair (это клиент для работы с Clair). Ниже приведены команды для Mac OS (если вы используете другую ОС, см. https://github.com/wemanity-belgium/hyperclair/releases [14]):
curl -SSl https://github.com/wemanity-belgium/hyperclair/releases/download/0.5.2/hyperclair-darwin-386 > ~/hyperclair
chmod 755 ~/hyperclair
Теперь у нас в ~/hyperclair есть исполняемый файл:
~/hyperclair pull localhost:5000/gianarb/micro:1.0.0
~/hyperclair push localhost:5000/gianarb/micro:1.0.0
~/hyperclair analyze localhost:5000/gianarb/micro:1.0.0
~/hyperclair report localhost:5000/gianarb/micro:1.0.0
Сгенерированный отчет выглядит вот так:
Поскольку Ruby является интерпретируемым языком, при использовании написанного на нем приложения у нас набирается приличное количество зависимостей. Надо установить все Ruby-гемы, которые нужны для нашего приложения, и все пакеты операционной системы, необходимые для этих гемов.
Предположим, что сканирование выявило критические уязвимости в libxml2 и libxslt. Это buildtime-зависимости гема Nokogiri, который является XML- и JSON-парсером. С целью увеличения производительности этот гем использует написанные на Си расширения, требующие компиляции. Но после того как гем установлен, libxml2 и libxslt больше не нужны.
Давайте удалим все buildtime-зависимости:
# Кеш для установки гемов
WORKDIR /tmp
ADD Gemfile* /tmp/
# Обновляем и устанавливаем все необходимые пакеты
# В конце удаляем используемые для сборки пакеты и apk-кеш
RUN apk update && apk upgrade &&
apk add --no-cache $RUBY_PACKAGES &&
apk add --no-cache --virtual build-deps $BUILD_PACKAGES &&
bundle install --jobs 20 --retry 5 &&
apk del build-deps
За счет кеширования Gemfile и Gemfile.lock в /tmp [15] команда bundle install
запустится только в случае изменения Gemfile. В противном случае будет использован кеш Docker. Такая оптимизация позволяет уменьшить время выполнения и нагрузку на сеть, которые при установке гемов могут быть достаточно велики.
Заметьте, что команда run
многострочная, и поэтому в образ добавляется только один слой. Необходимые для сборки пакеты устанавливаются с ключом --virtual, и их легко удалить после завершения процесса.
С точки зрения безопасности контейнеров крайне важно пересобирать их каждый раз, когда появляется обновление самого образа или одного из тех, что лежат в его основе. Автоматизация этой процедуры может быть основана на привязке к git-репозиторию: в этом случае сборка запускается после создания нового коммита в отслеживаемой ветке. Как было упомянуто выше, сборку можно также запустить по событию изменения базового образа.
В случае Ruby ситуация упрощается, так как мы можем взять те же самые файлы Dockerfile, которые использовали в процессе создания. Для программ на Go сначала нужно скомпилировать бинарный файл, а затем уже добавлять его в образ. Локально для этого можно использовать makefile.
Альтернативным вариантом будет компиляция бинарного файла по событию в docker-контейнере. Рекомендую посмотреть на пару golang-builder-образов от CenturyLinkLabs [16] и Prometheus [17].
Для запуска процесса сборки можно использовать сборочные хуки (build hooks), которые также удобны для добавления в образы динамических метаданных [18].
Итак, мы коротко рассмотрели способы минимизации образов Docker для приложений на Go и Ruby, научились запускать контейнеры под обычным пользователем, настроили сканирование безопасности с помощью Clair и немного поговорили об автоматической пересборке. Надеюсь, эти простые шаги помогут повысить безопасность ваших контейнеров Docker. На этом пока все. Спасибо за внимание!
Список источников:
Автор: Centos-admin.ru
Источник [21]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/sistemnoe-administrirovanie/238755
Ссылки в тексте:
[1] alpine: https://hub.docker.com/_/alpine/
[2] Alpine Linux: https://alpinelinux.org/
[3] репозиторий: https://github.com/gianarb/micro/releases/tag/1.0.0
[4] ruby:2.3-alpine: https://microbadger.com/images/ruby:2.3-alpine
[5] семантического версионирования: http://semver.org/
[6] уведомления MicroBadger: https://microbadger.com/
[7] сканирование безопасности: https://docs.docker.com/docker-cloud/builds/image-scan/
[8] Clair: https://github.com/coreos/clair
[9] добавлена: https://github.com/coreos/clair/pull/272
[10] TwistLock: https://www.twistlock.com/
[11] Aqua: https://www.aquasec.com/
[12] Debian Security Tracker: https://security-tracker.debian.org/tracker
[13] RedHat Security Data: https://www.redhat.com/security/data/metrics/
[14] https://github.com/wemanity-belgium/hyperclair/releases: https://github.com/wemanity-belgium/hyperclair/releases
[15] кеширования Gemfile и Gemfile.lock в /tmp: https://medium.com/@fbzga/how-to-cache-bundle-install-with-docker-7bed453a5800#.5vyqf8x9d
[16] CenturyLinkLabs: https://github.com/CenturyLinkLabs/golang-builder
[17] Prometheus: https://github.com/prometheus/golang-builder
[18] добавления в образы динамических метаданных: https://medium.com/microscaling-systems/labelling-automated-builds-on-docker-hub-f3d073fb8e1#.e64qpdd1q
[19] https://medium.com/microscaling-systems/dockerfile-security-tuneup-166f1cdafea1#.a24qq9tv7: https://medium.com/microscaling-systems/dockerfile-security-tuneup-166f1cdafea1#.a24qq9tv7
[20] http://gianarb.it/blog/about-your-images-security-tips: http://gianarb.it/blog/about-your-images-security-tips
[21] Источник: https://habrahabr.ru/post/320552/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best
Нажмите здесь для печати.