Підвищуємо безпеку контейнерів Docker



— Пане, яким чином вас зламали?
— Не спосіб, а контейнером.
Старовинний анекдот
Всі зайві компоненти комп'ютерної системи можуть виявитися джерелом зовсім необов'язкових вразливостей. Тому образи контейнерів повинні містити тільки те, що потрібно додатком. І їх розмір має значення не тільки з точки зору зручності дистрибуції, а також вартості володіння і безпеки. У цій статті ми поговоримо про методи мінімізації розміру і поверхні атаки образів Docker, а також про інструменти їх сканування на предмет наявності вразливостей.
Той, хто хоч трохи працював з Docker, напевно, чув про образ подорож. Він створений на основі дистрибутива Alpine Linux, який порівняно, наприклад, з Debian або Ubuntu при розмірі базового образу в 5 Мб залишає зломщикам набагато менше можливостей для атаки. Якщо додаток зможе працювати в alpine, це буде відмінним способом оптимізації.
А що щодо бінарних файлів? Може додаток працювати автономно? Якщо так, то є підстави розраховувати на додаткове зменшення розміру. В якості базового для таких образів, як Debian і Ubuntu, зазвичай використовується
scratch
, але в ньому також може заробити на додаток golang. Gianluca Arbezzano створив репозиторій з готовими бінарними файлами мінімального розміру. Давайте спробуємо 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. Ruby у ньому встановлюється з исходников, а не з пакету Alpine. З урахуванням семантичного версионирования реліз 2.3 буде отримувати оновлення безпеки безпосередньо від розробників.
В іншому випадку довелося б самостійно встановлювати Ruby з вихідних і відслідковувати вихід нових версій або використовувати пакет зі складу Alpine і стежити за його оновленням силами розробників дистрибутива.
Повідомлення і веб-хуки
Якщо для базового образу випущено оновлення безпеки, необхідно оновити ті образи, що на ньому базуються. Тут можуть допомогти повідомлення MicroBadger, які можна надсилати, наприклад, в 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 проводить сканування безпеки офіційних, а також користувальницьких образів, завантажених в Docker Cloud.
Для сканування безпеки образів реєстру Quay.io використовує Clair — продукт з відкритим вихідним кодом від CoreOS. Зовсім недавно в Clair була додана підтримка Alpine, що насправді дуже здорово. Будемо сподіватися, що ця функціональність скоро буде доступна і в Quay. Крім Clair існують сканери TwistLock і Aqua, але в більшості випадків їх використання треба платити.
Clair — це додаток на Golang, яке реалізує набір HTTP API для вивантаження, завантаження і аналізу образів. Дані про уразливість завантажуються з різних джерел, таких як Debian Security Tracker або RedHat Data Security, і зберігаються в Postgres. Clair працює за принципом статичного аналізатора, тому, щоб просканувати контейнер, його не треба запускати — перевіряється лише файлова система образу.
docker run -it -p 5000:5000 registry

За допомогою цієї команди ми запустили власний реєстр, щоб використовувати його в якості джерела образів для сканування. Давайте спробуємо завантажити в нього образ
micro
від Gianluca Arbezzano:
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):
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

Згенерований звіт виглядає ось так:


Видалення потенційно вразливих складальних залежностей Rails-додатки
Оскільки 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 команда
bundle install
запуститься тільки в разі зміни Gemfile. В іншому випадку буде використано кеш Docker. Така оптимізація дозволяє зменшити час виконання і навантаження на мережу, які при установці гемов можуть бути досить великі.
Зауважте, що команда
run
багаторядкова, і тому в образ додається тільки один шар. Необхідні для складання пакети встановлюються з ключем --virtual, і їх легко видалити після завершення процесу.
Автоматизована зборка
З точки зору безпеки контейнерів вкрай важливо перезбирати їх кожен раз, коли з'являється оновлення самого образу чи одного з тих, що лежать у його основі. Автоматизація цієї процедури може бути заснована на прив'язці до git-репозиторія: в цьому випадку складання запускається після створення нового коміта у відстежуваних гілці. Як було згадано вище, збірку можна також запустити події зміни базового образу.
У разі Ruby ситуація спрощується, так як ми можемо взяти ті ж самі файли Dockerfile, які використовували в процесі створення. Для програм на Go спочатку потрібно скомпілювати бінарний файл, а потім вже додавати його в образ. Локально для цього можна використовувати makefile.
Альтернативним варіантом буде компіляція бінарного файлу події в docker-контейнері. Рекомендую подивитися на пару golang-builder-образів від CenturyLinkLabs і Prometheus.
Для запуску процесу складання можна використовувати складальні хуки (build hooks), які також зручні для додавання в образи динамічних метаданих.


Висновок
Отже, ми коротко розглянули способи мінімізації образів Docker для додатків на Go і Ruby, навчилися запускати контейнери під звичайним користувачем, налаштували сканування безпеки з допомогою Clair і трохи поговорили про автоматичною пересборке. Сподіваюся, ці прості кроки допоможуть підвищити безпеку ваших контейнерів Docker. На цьому поки все. Спасибі за увагу!
Список джерел:
  1. https://medium.com/microscaling-systems/dockerfile-security-tuneup-166f1cdafea1#.a24qq9tv7
  2. http://gianarb.it/blog/about-your-images-security-tips
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.