Безопасное развертывание ElasticSearch сервера

от автора

После успешного перехода c MongoDB полнотекстового поиска на ElasticSearch, мы успели запустить несколько новых сервисов работающих на Elastic’е, расширение для браузера и в общем и целом, я был крайне доволен миграцией.

Но в бочке меда, оказалась одна ложка дегтя — примерно через месяц после конфигурации и успешной работы, LogEntries / NewRelic в один голос закричали о том, что сервер поиска не отвечает. После логина на дешбоард Digital Ocean’a, я увидел письмо от поддержки, что сервер был приостановлен в связи с большим исходящим UPD трафиком, что скорее всего свидетельствовало о том, что сервер скомрометирован.

DigitalOcean предоставил линк на инструкции, что надо делать в таком случае. Но самое интересно было в комментариях, почти все кто пострадал от атак в последние время, имели развернутый ElasticSeach кластер с открытым 9200 портом. Злоумышленники пользовались уязвимостями Java и ES, получали доступ к серверу и первращали его в составную часть какой нибудь bot-сети.

Мне предстояло восстановить сервер с нуля, но в этот раз я не буду таким наивным, сервер будет надежно защищен. Я опишу свой сетап использующий Node.js, Dokku / Docker, SSL.

Почему так?

Не смотря на всю мощь ElasticSearch, в нем не предусмотрено никаких внутренних средств защиты и авторизации, все нужно делать самому. Тут есть хорошая статья на эту тему.

Злоумышленники (скорее всего) пользуются уязвимостью динамических скриптов эластика, поэтому — если они не используются (как в моем случае) их рекомендуют отключать.

И наконец, открытый 9200 порт это как приманка, его нужно закрыть.

Какой будет план?

Мой план был такой — поднять «чистый» Digital Ocean дроплет, развернуть Elastic Search внутри Docker контейнера (даже если инстанс будет скомпрометирован, все что нужно будет сделать, перезапустить контейнер), закрыть 9200/9300 для доступа из вне и сервить весь трафик к эластику через Node.js прокси сервер, с простой моделью авторизации, через «shared secret».

Поднимаем новый дроплет

DigitalOcean предоставляет заранее подготовленный образ с Dokku/Docker на борту на Ubuntu 14, поэтому имеет смысл сразу выбрать его. Как обычно, поднятие новой машины занимает пару десятков секунд и мы готовы к работе.

image

Разворачиваем ElasticSearch в контейнере

Первое что нам нужно, это Docker образ с ElasticSearch. Несмотря на то, что для Dokku существуют несколько плагинов, я решил пойти путем самостоятельной установки, так мне показалось будет проще с конфигурацией.

Образ для Elastic’а уже готов и тут есть хорошие инструкции по его применению.

$ docker pull docker pull dockerfile/elasticsearch 

Как только образ загрузится, мы должны приготовить том, который будет внешним для работающего контейнера (даже в том случае, если контейнер остановится и будут перезапущен, данные будут хранится на файловой системе хоста).

$ cd / $ mkdir elastic 

В этом фолдере мы создадим конфигурационный файл, elasticsearch.yml. В моем случае он очень простой, у меня кластер из одной машины, поэтому меня удовлетворяют все настройки по умолчанию. Но, как было сказано выше, небходимо отключить динамические скрипты.

$ nano elasticsearch.yml 

Который будет состоять только из одной строчки,

script.disable_dynamic: true 

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

docker run --name elastic -d -p 127.0.0.1:9200:9200 -p 127.0.0.1:9300:9300 -v /elastic:/data dockerfile/elasticsearch /elasticsearch/bin/elasticsearch -Des.config=/data/elasticsearch.yml 

Обратите внимание на, -p 127.0.0.1:9200:9200, тут мы «привязываем» использование 9200 только с localhost. Я потратил несколько часов в попытках конфигурации iptables и закрытия 9200/9300 портов, безрезультатно. Благодаря помощи darkproger and @kkdoo все заработало как надо.

-v /elastic:/data will маппит том контейрера /data в локальный /elastic.

Проксирующий Node.js сервер

Теперь нужно запустить проксирующий Node.js сервер, который будет сервить трафик от/к localhost:9200 во внеший мир, безопасно. Я сделал маленький проект, основанный на http-proxy, названный elastic-proxy, он очень простой и вполне может быть переиспользанным в других проектах.

$ git clone https://github.com/likeastore/elastic-proxy $ cd elastic-proxy 

Сам код сервера,

var http = require('http'); var httpProxy = require('http-proxy'); var url = require('url');  var config = require('./config'); var logger = require('./source/utils/logger');  var port = process.env.PORT || 3010; var proxy = httpProxy.createProxyServer();  var parseAccessToken = function (req) {   var request = url.parse(req.url, true).query;   var referer = url.parse(req.headers.referer || '', true).query;    return request.access_token || referer.access_token; };  var server = http.createServer(function (req, res) {   var accessToken = parseAccessToken(req);    logger.info('request: ' + req.url + ' accessToken: ' + accessToken + ' referer: ' + req.headers.referer);    if (!accessToken || accessToken !== config.accessToken) {       res.statusCode = 401;       return res.end('Missing access_token query parameter');   }    proxy.web(req, res, {target: config.target}); });  server.listen(port, function () {   logger.info('Likeastore Elastic-Proxy started at: ' + port); }); 

Он проксирирует все реквесты и «пропускает» лишь те, которые указывают access_token как параметр запроса. access_token конфигурируется на сервере, через переменную окружения PROXY_ACCESS_TOKEN.

Так сервер уже сконфигурирован для Dokku, то все что остается сделать, это «пушуть» исходники и Dokku развернет новый сервис.

$ git push master production 

После деплоймента, идем на сервер и конфигурируем токен доступа,

$ dokku config proxy set PROXY_ACCESS_TOKEN="your_secret_value" 

Я также хотел, чтобы все шло через SSL, с Dokku этого очень легко добиться, копируем server.crt и server.key в /home/dokku/proxy/tls.

Перезапускаем прокси, чтобы применить последние изменения, убедимся что все ок, перейдя по ссылке https://search.likeastore.com — если все хорошо, он выдаст:

Missing access_token query parameter 

Связываем контейнеры Proxy и ElasticSeach

Нам нужно связать два контейнера между собой, первый с Node.js прокси, второй собственно с ElasticSearch. Мне очень понравился dokku-link плагин, который делает как раз, то что нужно. Установим его,

$ cd /var/lib/dokku/plugins $ git clone https://github.com/rlaneve/dokku-link 

И после установки связываем прокси с эластиком,

$ dokku link proxy elastic 

После этого прокси нужно будет еще раз перезапустить. Если все хорошо, то перейдя по ссылке https://proxy.yourserver.com?access_token=your_secret_value, мы увидем ответ от ElasticSearch,

{   status: 200,   name: "Tundra",   version: {       number: "1.2.1",       build_hash: "6c95b759f9e7ef0f8e17f77d850da43ce8a4b364",       build_timestamp: "2014-06-03T15:02:52Z",       build_snapshot: false,       lucene_version: "4.8"   },   tagline: "You Know, for Search" } 

Подстраиваем клиент

Осталось сконфигурировать клиент таким образом, чтобы на все реквесты к серверу он передавал access_token. Для Node.js приложения это выглядит вот так,

var client = elasticsearch.Client({   host: {       protocol: 'https',       host: 'search.likeastore.com',       port: 443,       query: {           access_token: process.env.ELASTIC_ACCESS_TOKEN       }   },   requestTimeout: 5000 }); 

Теперь можно перезапустить приложение, убедится что все работает как нужно… и выдохнуть.

Послесловие

Данный сетап, сработал (и работает сейчас) для Likeastore на отлично. Однако с течением времени, я увидел некий overhead, данного подхода. Скорее всего, можно избавится от проксируещего сервера, и сконфигурировать nginx c basic-authorization, с upstream в доккер контейнер, также с поддержкой SSL.

Также, хорошей идей, наверняка будет держать Elastic в private network, и все реквесты к нему делать через API приложения. Это может быть не очень удобно с точки зрения разработки, но более надежно с точки зрения безопасности.

ЗЫ. Это пересказ на русском моего поста из личного блога.

ссылка на оригинал статьи http://habrahabr.ru/company/likeastore/blog/231917/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *