Глеб Гончаров

Системный администратор в ФанБоксе. Автоматизирую и сопровождаю инфраструктуру для проектов мобильных операторов.

Миграция базы данных OpenLDAP

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

Штука в том, что вспоминая как работать с LDAP, каждый раз полчаса-час ищу названия команд и опций в Интернете. Чтобы в следующий раз не тратить время, расскажу как перенести LDAP с одного сервера на другой на примере OpenLDAP и инсталляции ОС CentOS и RHEL.

На старом сервере

Остановите сервис.

$ sudo systemctl stop slapd

Сделайте резервную копию базы данных LDAP.

$ sudo slapcat -b "dc=example,dc=tld" -f /etc/openldap/slapd.conf -l /var/tmp/backup.ldif

Запустите сервис.

$ sudo systemctl start slapd

На новом сервере

Установите OpenLDAP.

$ sudo yum -y install openldap openldap-servers openldap-clients

Добавьте в автозапуск.

$ sudo systemctl enable slapd

Остановите сервис.

$ sudo systemctl stop slapd

Затем скопируйте конфигурации, сертификаты и приватные ключи со старого сервера.

Импортируйте базу данных.

$ sudo slapadd -b "dc=example,dc=tld" -f /etc/openldap/slapd.conf -l /var/tmp/backup.ldif

Укажите путь до конфигурационного файла в опции SLAPD_OPTIONS.

$ sudo vim /etc/sysconfig/slapd

SLAPD_OPTIONS="-f /etc/openldap/slapd.conf"
[...]

Запустите сервис.

$ sudo systemctl start slapd

Готово.

Атака ROBOT на протокол TLS

День назад стали известны детали нового метода атаки на криптографический протокол TLS. Злоумышленник может расшифровать трафик без знания приватного RSA-ключа уязвимого сервера. Основной причиной возникновения называют неполную реализацию мер защиты TLS в некоторых коммерческих и открытых продуктах.

В списке уязвимых проектов нет открытых библиотек OpenSSL, LibreSSL, GnuTLS и BoringSSL. Однако нельзя гарантировать, что мейнтейнер пакета не наложит дополнительный патч и в вашей системе не окажется уязвимое ПО. Или что вендор приложения не поставит уязвимую версию криптографической библиотеки в составе своего ПО. Например, авторы упоминают Facebook, где инженеры используют собственные патчи к OpenSSL.

Потому в любом случае важно убедиться, что с TLS в вашем приложении всё в порядке. Сделать это можно двумя способами: через веб-сервисы или же запустить проверочный сценарий. Например, проверка уже доступна в тестовой версии SSLLabs Qualys. Для тестирования внутренней инфраструктуры практичнее использовать скрипт robot-detect. В требованиях к работе авторы предлагают использовать версию Python не ниже третьей.

Запустите Docker-контейнер с Python:

$ docker run -it python:3.6 bash

Проверьте версию интерпретатора:

$ python --version
Python 3.6.3

Установите библиотеки для установки и сборки зависимых модулей:

$ apt update
$ apt install -y libmpc-dev libmpfr-dev libgmp3-dev python-gmpy2

Установите зависимости для скрипта:

$ pip install cryptography gmpy2

Склонируйте репозиторий

$ git clone https://github.com/robotattackorg/robot-detect

Запустите сценарий:

$ cd robot-detect/
$ python robot-detect example.tld

Готово, теперь можно проанализировать результаты. На момент публикации балансировщики сервиса Badoo были уязвимы к ROBOT:

$ python robot-detect badoo.com
Scanning host badoo.com ip 31.222.68.33 port 443
RSA N: 0x936b74aa14d8b17e919f972a48d801c45cf9cc9b30d324d5d07141209f4b7628c21e2811327b8ee7d5774d03e557c4dde291ed1b07afab346760261b5183b9cfb0feba7767488f5860ee982d7372f53c1f14494ff5a3f457a272b3d9d6fc1c82bdb8734410c65d001e8801304d122a278c1d18ede98d961266b5de5d53a22bcb51968f94f4d0dd2677a221774f59ed43c786e6b3524684e1b4ba5b618eba2b685d2580909dba50a227da5ace8659b5b38a453910c86d1b3a4c527af22feaa50dfaa26879b22ed3bf5818e9cf15ee87f217bc98225c76a6bd1a3dc08f84ee2debb81ee4ba3fa822ec58bbead3a9205ba197d9cca542aa2e282793b5e9b38c314b
RSA e: 0x10001
Modulus size: 2048 bits, 256 bytes
The oracle is weak, the attack would take too long
VULNERABLE! Oracle (weak) found on badoo.com/31.222.68.33, TLSv1.2, standard message flow: TLS alert 40 of length 14/TLS alert 40 of length 7 (TLS alert 40 of length 7 / TLS alert 40 of length 7 / TLS alert 40 of length 7)
Result of good request:                        TLS alert 40 of length 14
Result of bad request 1 (wrong first bytes):   TLS alert 40 of length 7
Result of bad request 2 (wrong 0x00 position): TLS alert 40 of length 7
Result of bad request 3 (missing 0x00):        TLS alert 40 of length 7
Result of bad request 4 (bad TLS version):     TLS alert 40 of length 7

Тестировать можно не только веб-сервисы. Например, с SMTP-серверами Яндекса всё в хорошо:

$ python robot-detect smtp.yandex.ru -p 465
Scanning host smtp.yandex.ru ip 213.180.204.38 port 465
RSA N: 0x932395aca65c5083b8190c21269dbc472346b4fdc5edc74f99c33ed8f53485cb4348fd2cd80be6e0ba45afb27bee22178cdf22a927e7144acd86f8ade728e60334951ecf3ce28ab86edd4542fed64b0f7f68aca28601aeaa9b1e43bf667bff29e439f2998e65c1b7d1de9a8088b7233ad984c3f004109a43020782ae7be8f6c26f1f1f13ed80862e43312d0fdc4253dbc0363ea29a068c225accd1c7a092777ab16043474ef8a43b011148e0d86b0d4aa028da70f4b6f386085676aa420c2c88d982c0a6b5a8a79cced7b7e4c7c3c02932ef4156c813c973137d50b40b37c1d476c8775533a886c82e9ac1de22ebc421e7dcad45ebca2459f057043638829853
RSA e: 0x10001
Modulus size: 2048 bits, 256 bytes
Identical results (No data received from server), retrying with changed messageflow
Identical results (Timeout waiting for alert), no working oracle found
NOT VULNERABLE!

Подробнее об уязвимости и способах её эксплуатации можно прочесть в отчёте исследователей Hanno Böck, Juraj Somorovsky и Craig Young. Помогите узнать о проблеме специалистам, занятых разработкой и сопровождением веб-приложений.

Чиним Kafka после сбоя при перераспределении разделов

Мы в ФанБоксе используем Kafka для надёжной очереди сообщений. Kafka помогает нам масштабировать обработку событий между компонентами большой системы.

Пару недель назад инженер ошибся, указав несуществующий идентификатор брокера в кластере во время перераспределении разделов топика по репликам.

Совет: делегируйте управление топиками Kafka тем, кто её настраивает и сопровождает.

$ kafkacat -L -b localhost:9092 -t events
[...]
  topic "events" with 5 partitions:
    partition 2, leader 1008, replicas: 1007,1008, isrs: 1008,1007
    partition 4, leader 1005, replicas: 1005,1006, isrs: 1006,1005
    partition 1, leader 1007, replicas: 1007,1008, isrs: 1008,1007
    partition 3, leader 1006, replicas: 10005,1009,1006, isrs: 1006,1009
    partition 0, leader 1006, replicas: 1009,1006, isrs: 1006,1009

Миграция зависла на середине: ни откат, ни перезапуск, ни повторное перераспределение по другим брокерам не помогали:

$ ./bin/kafka-reassign-partitions.sh --zookeeper zk1,zk2,zk3/kafka --reassignment-json-file reassign.json --verify
Status of partition reassignment:
Reassignment of partition [events,2] completed successfully
Reassignment of partition [events,0] completed successfully
Reassignment of partition [events,4] completed successfully
Reassignment of partition [events,3] is still in progress
Reassignment of partition [events,1] completed successfully

В тот момент сбойный брокер перестал считать метрики, а агент мониторинга стал забирать нули из JMX-интерфейса. Несмотря на то, что всего один раздел оказался без лидера, работа кластера была нарушена.

Очевидное решение полностью пересоздать топик не подходит: сообщения поступают и обрабатываются круглосуточно без возможности выделить окно для простоя. Иными словами, мы не хотим терять данные.

Кроме того, удаляя топик нельзя предсказать работу консьюмеров и продьюсеров. Поведение приложений зависит от реализации клиентских библиотек (преимущественно СПО), а потому плохо предсказуемо.

Сработал такой трюк

Снимаем действующую задачу в Zookeeper:

$ zookeeper-client
> rmr /kafka/admin/reassign_partitions
> quit

Устанавливаем брокер Kafka на свободный сервер и добавляем его в кластер под несуществующим id.

$ [sudo] yum -y install kafka
$ [sudo] cat << EOF > /var/lib/kafka/meta.properties
version=0
broker.id=10005
EOF
$ [sudo] chown kafka:kafka /var/lib/kafka/meta.properties

Дожидаемся окончания переназначения разделов и проверяем статус задачи:

$ ./bin/kafka-reassign-partitions.sh --zookeeper zk1,zk2,zk3/kafka --reassignment-json-file reassign.json --verify
Status of partition reassignment:
Reassignment of partition [events,2] completed successfully
Reassignment of partition [events,0] completed successfully
Reassignment of partition [events,4] completed successfully
Reassignment of partition [events,3] completed successfully
Reassignment of partition [events,1] completed successfully

Дождавшись окончания, исправляем опечатку в JSON схеме миграции и удаляем разделы с нового брокера:

./bin/kafka-reassign-partitions.sh --zookeeper zk1,zk2,zk3/kafka --reassignment-json-file reassign.json --execute

После выполнения задачи удаляем временный брокер из кластера:

Остановить Kafka также можно сигналом SIGTERM рабочему процессу.

$ [sudo] service kafka stop
$ [sudo] yum -y remove kafka

Готово. Проблемный раздел достался другой доступной реплике, работа восстановлена. Хорошим подспорьем в этой истории были графики в Grafana: без них эта проблема могла бы ещё долго оставаться незамеченной. По этой теме рекомендую посмотреть слайды с презентации Gwen Shapira о мониторинге Kafka.

Поддержка ведомых устройств в шине SPI Raspberry Pi

На днях решил научиться собирать метрики с отечественного монитора качества воздуха MasterKit MT8060, чтобы посчитать оптимальную частоту проветривания помещений в офисе. До этого мой опыт работы ограничивался лабораторными работами в университете, потому решил собрать первый прототип на Raspberry Pi 1 model B со знакомым окружением Linux Raspbian.

В Raspberry Pi работают с шиной SPI несколькими способами:

Третий способ удобнее, если вы пишете программу на языке отличном от C. Например, библиотека spidev, periph и gobot используют его для чтения и записи SPI.

В ходе исследования пришёл к неутешительному выводу, что реализовать задуманное будет непросто. Причиной тому отсутствие поддержки SPI slave в ядре Linux для Broadcom BCM2835, позволяющей хосту выступать в роли ведомого.

Этим вопросом задавались и другие пользователи чипа в 2015 году. Предположу, что если вопрос с поддержкой не был решён за три года, то прождать можно ещё долго. Моих же умений недостаточно для разработки своего модуля ядра или изменения кода существующего.

Возможно реализовать протокол в пространстве пользователя и управлять GPIO вручную. К сожалению, такой подход будет неэффективным из-за высокого потребления CPU от постоянного поллинга сигнала на пинах.

Ещё можно воспользоваться наработками Тайлера Никольса по использованию SPI в Raspberry Pi без Linux. Однако решение кажется излишне расточительным для чтения трёх метрик в секунду.

Выходит, практичнее приобрести микроконтроллер на Arduino и запрограммировать его. Или, если вы ещё выбираете датчик, возьмите MasterKit 8057S с поддержкой вывода метрик по USB. Для него даже есть пример приложения под Linux.

X-Accel-Redirect для POST-запросов в NGINX

Ещё об одном нестандартном использовании NGINX. Рассмотрим гипотетический веб-проект для меломанов. Сайт доступен только для авторизованных пользователей: те слушают треки и оставляют комментарии под ними. Сам сервис не хранит ни музыку, ни сообщения — этим заведует контент-провайдер, трафик к которому проксируется по HTTP.

Чтобы запретить гостям слушать музыку без регистрации, запросы к трекам обрабатывает приложение, присылая в ответ X-Accel-Redirect на internal локейшен до контент-провайдера. Веб-сервер получит заголовок, найдёт локейшен, выполнит HTTP-запрос и вернёт результат клиенту.

Особенность NGINX такова, что тот при работе с X-Accel-Redirect выполнит GET-запрос вне зависимости от метода начального запроса. Скажем, если отправить POST-запрос с новым комментарием, то сообщение будет отброшено, а до апстрима дойдут только заголовки.

Решить эту проблему можно несколькими способами. Например, передавать POST как GET, помещая тело запроса в кастомный заголовок X-Accel-Post-Body. Вот так:

server {
  listen 80;

  location / {
    proxy_pass http://app.example.tld;
  }

  location /content {
    rewrite_by_lua_block {
        if ngx.header["X-Accel-Post-Body"] ~= nil then
          ngx.req.set_method(ngx.HTTP_POST)
          ngx.req.set_body_data(ngx.header["X-Accel-Post-Body"])
        end
    }

    internal;
    proxy_pass http://provider.example.tld/content/;
  }
}

Или форсировать метод POST с помощью директивы proxy_method. В этом случае придётся описать два локейшена, чтобы не передавать через POST запросы для GET.

server {
  listen 80;

  location / {
    proxy_pass http://app.example.tld;
  }

  location /content_get/ {
    internal;
    proxy_pass http://provider.example.tld/content/;    
  }

  location /content_post/ {
    internal;
    proxy_method POST;
    proxy_pass http://provider.example.tld/content/;
  }
}

На практике удобнее proxy_method. В первом случае, чтобы передать большое тело запроса в заголовке, потребуется увеличить large_client_header_buffers и изменить приложение, научив работать с кастомным заголовком. Также NGINX должен поддерживать Lua или Perl — стандартными средствами описать такое поведение не получится.