Русский
Русский
English
Статистика
Реклама

Nginx

Перевод Тест пропускной способности ASP.NET Core 5.0 в Kestrel, IIS, Nginx и Caddy

05.06.2021 18:10:05 | Автор: admin
Начиная с версии 2.2. ASP.NET Core поддерживает режим внутрипроцессного размещения приложения (InProcess) в IIS, направленный на улучшение производительности кода. Рик Страл написал статью, в которой подробно исследовал эту тему. С тех пор прошло три года, теперь платформа ASP.NET Core добралась до версии 5.0. Как это повлияло на производительность ASP.NET Core-проектов на различных серверах?



Результаты исследования Рика Страла


Рик Страл в своей статье занимался тестирование ASP.NET Core-кода на Windows в Kestrel и в IIS (в режимах InProcess и OutOfProcess). Его интересовало количество запросов в секунду, обрабатываемых системой. В результате он пришёл к выводу о том, что первое место по производительности получает использование IIS в режиме InProcess, второе Kestrel, третье IIS в режиме OutOfProcess.

Обзор эксперимента


Рик не провёл испытания, позволяющие выявить различия в выполнении ASP.NET Core-кода на Windows- и на Linux-серверах. А вопрос о том, что в 2021 году лучше выбрать для проектов, основанных на ASP.NET Core 5.0, интересует многих из тех, кого я знаю. Поэтому я решил, используя подход к тестированию, похожий на тот, которым пользовался Рик, узнать о том, сколько запросов в секунду может обработать ASP.NET Core 5.0-приложение на Windows и на Linux.

Тестовое окружение


Так как Windows 10, Ubuntu Desktop и другие настольные операционные системы не в полной мере отражают особенности сопоставимых с ними серверных дистрибутивов, я решил выбрать для тестов серверные версии соответствующих ОС. Это были свежеустановленные копии систем с последними патчами, перед выполнением тестов они были один раз перезагружены.

Windows-сервер:


  • Провайдер: Microsoft Azure East Asia Region.
  • ОС: Windows Server 2019 Data Center.
  • Характеристики системы: B2S / 2 vCPU, 4GB RAM, Premium SSD.
  • Окружение: IIS с поддержкой статического и динамического сжатия контента, отсутствие интеграции с ASP.NET 3.5 или 4.x, на сервере установлена платформа ASP.NET Core 5.0.2 Runtime.


Сведения о Windows-сервере

Linux-сервер


  • Провайдер: Microsoft Azure East Asia Region.
  • ОС: Ubuntu Server 20.04 LTS.
  • Характеристики системы: B2S / 2 vCPU, 4GB RAM, Premium SSD.
  • Окружение: включено использование BBR, установлены Nginx, Caddy и ASP.NET Core 5.0.2 Runtime.


Сведения о Linux-сервере

Инструменты для проведения тестов


Рик пользовался West Wind Web Surge, но этот инструмент доступен только на платформе Windows, а это нас не устроит. Я решил воспользоваться опенсорсным кросс-платформенным инструментом bombardier, о котором однажды писали в официальном .NET-блоге Microsoft.

Тестовое приложение


Я создал новый проект ASP.NET Core 5.0 Web API, в котором имеется лишь один метод:

[ApiController][Route("[controller]")]public class TestController : ControllerBase{[HttpGet]public string Get(){return $"Test {DateTime.UtcNow}";}}

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

Проект скомпилирован с применением конфигурации Release и опубликован с использованием FDD. Настройки логирования оставлены в стандартном состоянии:

"LogLevel": {"Default": "Information","Microsoft": "Warning","Microsoft.Hosting.Lifetime": "Information"}

Методика тестирования


Я запускал проект, используя следующие конфигурации:

  • Kestrel.
  • IIS в режиме InProcess.
  • IIS в режиме OutOfProcess.
  • Обратный прокси Nginx.
  • Обратный прокси Caddy.

Затем я применял bombardier. В течение 10 секунд, по 2 соединениям, велась работа с конечной точкой, доступной на localhost. После прогревочного раунда испытаний я, друг за другом, проводил ещё 3 раунда и на основе полученных данных вычислял показатель количества запросов, обработанных исследуемой системой за секунду (Request per Second, RPS).

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

Результаты тестов


Windows + Kestrel


Средний RPS: 18808


Windows + Kestrel

Windows + IIS в режиме InProcess


Средний RPS: 10089


Windows + IIS в режиме InProcess

Windows + IIS в режиме OutOfProcess


Средний RPS: 2820


Windows + IIS в режиме OutOfProcess

Linux + Kestrel


Средний RPS: 10667


Linux + Kestrel

Linux + Nginx


Средний RPS: 3509


Linux + Nginx

Linux + Caddy


Средний RPS: 3485


Linux + Caddy

Итоги


Вот как выглядят результаты тестирования (от самой быстрой комбинации ОС и серверного ПО до самой медленной):

  • Windows + Kestrel (18808)
  • Linux + Kestrel (10667)
  • Windows + IIS в режиме InProcess (10089)
  • Linux + Nginx (3509)
  • Linux + Caddy (3485)
  • Windows + IIS в режиме OutOfProcess (2820)

Мои результаты отличаются от тех, что получил Рик, тестируя ASP.NET Core 2.2-проект. В его тестах производительность IIS в режиме InProcess оказывалась выше, чем производительность Kestrel. Но сейчас Kestrel оказывается быстрее IIS в режиме InProcess, и это кажется вполне логичным и ожидаемым.

А вот неожиданностью для меня стало то, что производительность Windows-серверов c Kestrel оказалась выше производительности аналогичных Linux-серверов. Это меня удивило.

В режиме обратного прокси-сервера Nginx и Caddy, в общем-то, показывают одинаковую производительность. И та и другая конфигурации обходят IIS в режиме OutOfProcess.

Конечно, мой простой тест, в ходе которого сервер возвращает обычную строку, не позволяет проверить все нюансы производительности ASP.NET Core 5.0 и исследовать возможности каждого сервера. В реальных проектах присутствует множество факторов, которые воздействуют на производительность. План моего эксперимента не перекрывает все возможные сценарии использования ASP.NET Core 5.0, в нём, наверное, имеются какие-то недочёты. Если вы что-то такое заметите дайте мне знать.

Дополнение


Мне сообщили, что в .NET 5 у DateTime.UtcNow могут быть проблемы, касающиеся производительности. Я провёл повторные испытания, заменив эту конструкцию на Activity.Current?.Id ?? HttpContext.TraceIdentifier. Получившиеся у меня результаты были выражены немного более высокими показателями, но общая картина осталась почти такой же.

А если увеличить число подключений с 2 до 125 сервер Kestrel, и на Windows, и на Linux, способен дать гораздо более высокую пропускную способность.

Каким серверным ПО вы пользуетесь в своих проектах?


Подробнее..

Перевод Частые ошибки в настройках Nginx, из-за которых веб-сервер становится уязвимым

16.03.2021 10:10:28 | Автор: admin

Nginx это веб-сервер, на котором работает треть всех сайтов в мире. Но если забыть или проигнорировать некоторые ошибки в настройках, можно стать отличной мишенью для злоумышленников. Detectify Crowdsource подготовил список наиболее часто встречающихся ошибок, делающих сайт уязвимым для атак.

Nginx один из наиболее часто используемых веб-серверов в Интернете, поскольку он модульный, отзывчивый под нагрузкой и может масштабироваться на минимальном железе. Компания Detectify регулярно сканирует Nginx на предмет неправильных настроек и уязвимостей, из-за которых могут пострадать пользователи. Найденные уязвимости потом внедряются в качестве теста безопасности в сканер веб-приложений.

Мы проанализировали почти 50 000 уникальных файлов конфигурации Nginx, загруженных с GitHub с помощью Google BigQuery. С помощью собранных данных удалось выяснить, какие ошибки в конфигурациях встречаются чаще всего. Эта статья прольёт свет на следующие неправильные настройки Nginx:

1. Отсутствует корневой каталог

2. Небезопасное использование переменных

3. Чтение необработанного ответа сервера

4. merge_slashes отключены

Отсутствует корневой каталог

server { root /etc/nginx; location /hello.txt { try_files $uri $uri/ =404; proxy_pass http://127.0.0.1:8080/; }}

Root-директива указывает корневую папку для Nginx. В приведённом выше примере корневая папка /etc/nginx, что означает, что мы можем получить доступ к файлам в этой папке. В приведенной выше конфигурации нет места для / (location / {...}), только для /hello.txt. Из-за этого root-директива будет установлена глобально, а это означает, что запросы к / перенаправят вас на локальный путь /etc/nginx.

Такой простой запрос, как GET /nginx.conf, откроет содержимое файла конфигурации Nginx, хранящегося в /etc/nginx/nginx.conf. Если корень установлен в /etc, запрос GET на /nginx/nginx.conf покажет файл конфигурации. В некоторых случаях можно получить доступ к другим файлам конфигурации, журналам доступа и даже зашифрованным учётным данным для базовой аутентификации HTTP.

Из почти 50 000 файлов конфигурации Nginx, которые мы проанализировали, наиболее распространёнными корневыми путями были следующие:

Потерявшийся слеш

server { listen 80 default_server;server_name _;location /static { alias /usr/share/nginx/static/; }location /api {                proxy_pass http://apiserver/v1/; }}

При неправильной настройке off-by-slash можно перейти на один шаг вверх по пути из-за отсутствующей косой черты. Orange Tsai поделился информацией об этом в своём выступлении на Blackhat Нарушение логики парсера!. Он показал, как отсутствие завершающей косой черты в location директиве в сочетании с alias директивой позволяет читать исходный код веб-приложения. Менее известно то, что это также работает с другими директивами, такими как proxy_pass. Давайте разберёмся, что происходит и почему это работает.

location /api { proxy_pass http://apiserver/v1/; }

Если на Nginx запущена следующая конфигурация, доступная на сервере, можно предположить, что доступны только пути в http://apiserver/v1/.

http://server/api/user -> http://apiserver/v1//user

Когда запрашивается http://server/api/user, Nginx сначала нормализует URL. Затем он проверяет, соответствует ли префикс /api URL-адресу, что он и делает в данном случае. Затем префикс удаляется из URL-адреса, поэтому остаётся путь /user. Затем этот путь добавляется к URL-адресу proxy_pass, в результате чего получается конечный URL-адрес http://apiserver/v1//user.

Обратите внимание, что в URL-адресе есть двойная косая черта, поскольку директива местоположения не заканчивается косой чертой, а путь URL-адреса proxy_pass заканчивается косой чертой. Большинство веб-серверов нормализуют http://apiserver/v1//user до http://apiserver/v1/user, что означает, что даже с этой неправильной конфигурацией всё будет работать так, как ожидалось, и это может остаться незамеченным.

Эта неправильная конфигурация может быть использована путём запроса http://server/api../, из-за чего Nginx запросит URL-адрес http://apiserver/v1/../, который нормализован до http://apiserver/. Уровень вреда от такой ошибки определяется тем, чего можно достичь, если использовать эту неправильную конфигурацию. Например, это может привести к тому, что статус сервера Apache будет отображаться с URL-адресом http://server/api../server-status, или он может сделать доступными пути, которые не должны быть общедоступными.

Одним из признаков того, что сервер Nginx имеет неправильную конфигурацию, является возврат сервером одинакового же ответа при удалении косой черты в URL-адресе. То есть, если http://server/api/user и http://server/apiuser возвращают один и тот же ответ, сервер может быть уязвимым. Он позволяет отправлять следующие запросы:

http://server/api/user -> http://apiserver/v1//userhttp://server/apiuser -> http://apiserver/v1/user

Небезопасное использование переменных

Некоторые фреймворки, скрипты и конфигурации Nginx небезопасно используют переменные, хранящиеся в Nginx. Это может привести к таким проблемам, как XSS, обход HttpOnly-защиты, раскрытие информации и в некоторых случаях даже RCE.

SCRIPT_NAME

С такой конфигурацией, как эта:

location ~ \.php$ { include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_pass 127.0.0.1:9000; }

основная проблема будет заключаться в том, что Nginx отправит интерпретатору PHP любой URL-адрес, заканчивающийся на .php, даже если файл не существует на диске. Это распространённая ошибка во многих конфигурациях Nginx, и об этом говорится в документе Ловушки и распространенные ошибки, созданном Nginx.

XSS возможен, если PHP-скрипт попытается определить базовый URL на основе SCRIPT_NAME;

<?phpif(basename($_SERVER['SCRIPT_NAME']) ==basename($_SERVER['SCRIPT_FILENAME'])) echo dirname($_SERVER['SCRIPT_NAME']);?>GET /index.php/<script>alert(1)</script>/index.phpSCRIPT_NAME = /index.php/<script>alert(1)</script>/index.php

Использование $uri может привести к CRLF-инъекции

Другая неправильная конфигурация, связанная с переменными Nginx, заключается в использовании $uri или $document_uri вместо $request_uri.

$uri и $document_uri содержат нормализованный URI, тогда как нормализация в Nginx включает URL-декодирование URI. В блоге Volema рассказывалось, что $uri обычно используется при создании перенаправлений в конфигурации Nginx, что приводит к внедрению CRLF.

Пример уязвимой конфигурации Nginx:

location / {return 302 https://example.com$uri;}

Символами новой строки для HTTP-запросов являются \r (возврат каретки) и \n (перевод строки). URL-кодирование символов новой строки приводит к следующему представлению символов %0d%0a. Когда эти символы включены в запрос типа http://localhost/%0d%0aDetectify:%20clrf на сервер с неправильной конфигурацией, сервер ответит новым заголовком с именем Detectify, поскольку переменная $uri содержит новые URL-декодированные строчные символы.

HTTP/1.1 302 Moved TemporarilyServer: nginx/1.19.3Content-Type: text/htmlContent-Length: 145Connection: keep-aliveLocation: https://example.com/Detectify: clrf

Произвольные переменные

В некоторых случаях данные, предоставленные пользователем, можно рассматривать как переменную Nginx. Непонятно, почему это происходит, но это встречается не так уж редко, а проверяется довольно-таки сложным путём, как видно из этого отчёта. Если мы поищем сообщение об ошибке, то увидим, что оно находится в модуле фильтра SSI, то есть это связано с SSI.

Одним из способов проверки является установка значения заголовка referer:

$ curl -H Referer: bar http://localhost/foo$http_referer | grep foobar

Мы просканировали эту неправильную конфигурацию и обнаружили несколько случаев, когда пользователь мог получить значение переменных Nginx. Количество обнаруженных уязвимых экземпляров уменьшилось, что может указывать на то, что уязвимость исправлена.

Чтение необработанного ответа сервера

С proxy_pass Nginx есть возможность перехватывать ошибки и заголовки HTTP, созданные бэкендом (серверной частью). Это очень полезно, если вы хотите скрыть внутренние сообщения об ошибках и заголовки, чтобы они обрабатывались Nginx. Nginx автоматически предоставит страницу пользовательской ошибки, если серверная часть ответит ей. А что происходит, когда Nginx не понимает, что это HTTP-ответ?

Если клиент отправляет недопустимый HTTP-запрос в Nginx, этот запрос будет перенаправлен на серверную часть как есть, и она ответит своим необработанным содержимым. Тогда Nginx не распознает недопустимый HTTP-ответ и просто отправит его клиенту. Представьте себе приложение uWSGI, подобное этому:

def application(environ, start_response):   start_response('500 Error', [('Content-Type','text/html'),('Secret-Header','secret-info')])   return [b"Secret info, should not be visible!"]

И со следующими директивами в Nginx:

http {   error_page 500 /html/error.html;   proxy_intercept_errors on;   proxy_hide_header Secret-Header;}

proxy_intercept_errors будет обслуживать пользовательский ответ, если бэкенд имеет код ответа больше 300. В нашем приложении uWSGI выше мы отправим ошибку 500, которая будет перехвачена Nginx.

proxy_hide_header почти не требует пояснений; он скроет любой указанный HTTP-заголовок от клиента.

Если мы отправим обычный GET-запрос, Nginx вернёт:

HTTP/1.1 500 Internal Server ErrorServer: nginx/1.10.3Content-Type: text/htmlContent-Length: 34Connection: close

Но если мы отправим неверный HTTP-запрос, например:

GET /? XTTP/1.1Host: 127.0.0.1Connection: close

То получим такой ответ:

XTTP/1.1 500 ErrorContent-Type: text/htmlSecret-Header: secret-infoSecret info, should not be visible!

merge_slashes отключены

Для директивы merge_slashes по умолчанию установлено значение on, что является механизмом сжатия двух или более слешей в один, поэтому/// станет /. Если Nginx используется в качестве обратного прокси и проксируемое приложение уязвимо для включения локального файла, использование дополнительных слешей в запросе может оставить место для его использования. Об этом подробно рассказывают Дэнни Робинсон и Ротем Бар.

Мы нашли 33 Nginx-файла, в которых для параметра merge_slashes установлено значение off.

Попробуйте сами

Мы создали репозиторий GitHub, где вы можете использовать Docker для настройки своего собственного уязвимого тестового сервера Nginx с некоторыми ошибками конфигурации, обсуждаемыми в этой статье, и попробуйте найти их самостоятельно!

Вывод

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

Вторая часть будет позднее.


Что ещё интересного есть в блогеCloud4Y

Пароль как крестраж: ещё один способ защитить свои учётные данные

Тим Бернерс-Ли предлагает хранить персональные данные в подах

Подготовка шаблона vApp тестовой среды VMware vCenter + ESXi

Создание группы доступности AlwaysON на основе кластера Failover

Как настроить SSH-Jump Server

Подписывайтесь на нашTelegram-канал, чтобы не пропустить очередную статью. Пишем не чаще двух раз в неделю и только по делу.

Подробнее..

Перевод Свой мессенджер Matrix-synapse в связке с Jitsi-meet. Часть 3

02.04.2021 16:10:53 | Автор: admin

Всем Приветь. Как вы уже обратили внимание, порядок публикации нарушен. Изначально планировалось выпустить эту статью в качестве третьей части цикла, однако она стала второй. Это объясняется тем, что поднять один новый сервер для Matrix дешевле, чем несколько для высоконагруженного сервиса Jitsi-meet. Без паники, все будет. А пока займемся Matrix.

Сразу хочу отметить тот факт, что данный мануал не является полностью моим. Это перевод оригинального мануала разработчиков с моими дополнениями и адаптацией. И вошел он в данную серию статей, ибо связан с jitsi-meet (ну или их можно связать вместе).

Прежде чем мы погрузимся в команды, конфигурацию и "танцы" поговорим немного на тему "а зачем и кому это надо"....

А зачем и кому это надо...

Причин может быть много: от личной паранойи до требований безопасности в компании. Matrix-synapse - это мессенджер, который вы можете не только скачать или открыть в браузере, пройти регистрацию и общаться, но и поднять собственный сервер, установить на него серверную часть matrix, а потом общаться в нем с полной уверенностью, что ваша переписка не только шифруется, но и хранится не у кого-то, а у вас на сервере. Если уж совсем паранойить, то все это можно запихуть в VPN и доступ к серверу будет только у тех, у кого есть подключение к VPN (тут мы такое рассматривать не будем, а настроим только matrix в связке с jitsi).

В прошлой статье мы уже настроили два сервера: jitsi-meet - для видеоконференций и jibri - для записи и/или трансляции этих видеоконференций. Сейчас мы будем расширять наши возможности. Мы поднимем сервер со своим мессенджером и привяжем его к нашему jitsi, дабы совершать видеовызовы через него.

Сервера поднималтут.

Команды, конфигурация и "танцы".

Я все делаю на debian 9, если это противоречит вашим религиозным взглядам, то использованные тут команды подстраивайте под свою ОС. Если вы хотите, то можете поднимать Matrix на том же сервере, где у вас поднят Jitsi-meet или на отдельном: этот мануал подойдет для обоих вариантов.

Начнем мы с того, что создадим DNS записи. Нам понадобится их три: dnsname.com, matrix.dnsname.com и riot.dnsname.com - на IP нашего сервера.

Веб-сервер и SSL

Я использовал nginx для jitsi и не буду отходить от своей традиции и буду настраивать его и для matrix.

Если nginx еще не установлен, то:

# apt update# apt install nginx -y

Если nginx установлен, то создаем конфигурацию (скопируем default и перенастроим):

# cd /etc/nginx/sites-avaliable

Теперь редактируем конфигурацию:

# vim dnsname.com

И меняем следующие директивы:

server_name dnsname.com;...root /var/www/dnsname.com;

Сохраняем и закрываем.

Тоже самое делаем для riot.dnsname.com:

# vim riot.dnsname.comserver_name riot.dnsname.com;  ...  root /var/www/riot.dnsname.com/riot;

Конфигурацию matrix.dnsname.com приводим к следующему виду:

# vim matrix.dnsname.comserver {    # редактируем этот блок       server_name matrix.dnsname.eu;       root /var/www/dnsname.eu;       index index.html;       location / {               proxy_pass http://localhost:8008;       }....

Теперь пора заняться SSL сертификатами. Мы создадим и подпишем бесплатные трехмесячные сертификаты Let's Encrypt. Для этого делаем следующее:

# apt install -y python3-certbot-nginx# certbot --nginx -d dnsname.com -d riot.dnsname.com -d matrix.dnsname.com

Этой же командой спустя три месяца можно продлить сертификаты.

Теперь нужно включить виртуальные хосты и перезагрузить nginx:

# ln -s dnsname.com /etc/nginx/sites-enable/# ln -s matrix.dnsname.com /etc/nginx/sites-enable/# ln -s riot.dnsname.com /etc/nginx/sites-enable/# systemctl restart nginx

Matrix-synapse и Postgresql

Из коробки Matrix использует SQLite - при малом количестве пользователей этого достаточно, однако рекомендуется использовать Postgresql (я настоятельно рекомендую сразу начать использовать Postgresql). Для тех, кто просто хочет "пощупать" Matrix, этот шаг можно пропустить, ну а мы, все же, сразу поставим Postgresql, ибо миграция c SQLite на Postgresql - в некоторых случаях может стать теми еще "танцами". Поехали...

Переходим к установке matrix.

# apt install -y lsb-release wget apt-transport-https# wget -O /usr/share/keyrings/matrix-org-archive-keyring.gpg https://packages.matrix.org/debian/matrix-org-archive-keyring.gpg# echo "deb [signed-by=/usr/share/keyrings/matrix-org-archive-keyring.gpg] https://packages.matrix.org/debian/ $(lsb_release -cs) main" |    tee /etc/apt/sources.list.d/matrix-org.list# apt update# apt install matrix-synapse-py3 -y

В процессе установки будет запрошено DNS имя - указываем dnsname.com.

Устанавливаем Postgresql, создаем пользователя и БД для matrix-synapse:

# apt install postgresql postgresql-contrib -y# su - postgres# psqlpostgres=# \password postgrespostgres=# \q# createuser --pwprompt synapse_user# psqlpostgres=#CREATE DATABASE synapsepostgres-# ENCODING 'UTF8'postgres-# LC_COLLATE='C'postgres-# LC_CTYPE='C'postgres-# template=template0postgres-# OWNER synapse_user;postgres=# \q# exit
# cp /etc/matrix-synapse/homeserver.yaml /etc/matrix-synapse/homeserver.yaml.back# cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1

Открываем конфигурационный файл Matrix:

# vim /etc/matrix-synapse/homeserver.yaml

Находим строку #servername: "servername" и меняем значение на "dnsname.com", убираем "#", тоже самое делаем со строкой #registration_shared_secret: "UIBfgsjkdfhj3568JHGIUIEBfy3764JH" и вставляем в кавычках ключ, который мы получили ранее. Дальше ищем опцию#enable_registration: false, убираем "#" и меняем false на true

Так же находим блок database снимаем "#" и подставляем свои значения:

database:  name: psycopg2  args:    user: synapse-user    password: password    database: synapse    host: localhost    cp_min: 5    cp_max: 10

А блок database, где прописана SQLite (она ниже) - комментируем (ставим "#").

Далее идем и вставляем строку host synapse synapse_user ::1/128 md5 как у меня:

# vim /etc/postgresql/9.6/main/pg_hba.conf...   # "local" is for Unix domain socket connections only   local   all             all                                     peer   # IPv4 local connections:   host    all             all             127.0.0.1/32            md5   host    synapse         synapse_user    ::1/128                 md5   # IPv6 local connections:   host    all             all             ::1/128                 md5...

Перезапускаем Matrix и Postgresql:

# systemctl restart postgresql# systemctl enable postgresql# systemctl restart matrix-synapse# systemctl enable matrix-synapse

Зарегистрируем пользователя с правами администратора:

# register_new_matrix_user -c /etc/matrix-synapse/homeserver.yaml http://localhost:8008

Будут запрошены данные: логин, пароль и права администратора.

Если у вас уже работает Matrix с SQLite, то можно мигрировать на Postgresql. Для этого нам надо сделать копию homeserver.yaml, сделать две копии homeserver.db (одну мы сохраним для "потомков", ну или если что-то пойдет не туда, а со второй будем работать). Подправить конфиг, мигрировать и перезагрузиться сервисы.

# cp /etc/matrix-synapse/homeserver.yaml /etc/matrix-synapse/homeserver-postgresql.yaml# chown matrix-synapse:nogroup /etc/matrix-synapse/homeserver-postgresql.yaml

Теперь с файлом homeserver-postgresql.yaml надо сделать это.

Останавливаем Matrix:

# systemctl stop matrix-synapse

Далее копируем базы и мигрируем.

# cp /var/lib/matrix-synapse/homeserver.db /etc/matrix-synapse/homeserver.db.snapshot# chown matrix-synapse:nogroup /etc/matrix-synapse/homeserver.db.snapshot# cp /var/lib/matrix-synapse/homeserver.db /tmp/# cd /etc/matrix-synapse/# synapse_port_db --curses --sqlite-database homeserver.db.snapshot --postgres-config /etc/matrix-synapse/homeserver-postgresql.yaml# mv homeserver.yaml homeserver-old-sqlite.yaml# mv homeserver-postgresql.yaml homeserver.yaml# systemctl restart postgresql# systemctl start matrix-synapse

Если остались какие-то вопросы, то можно посмотреть сюда.

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

Теперь мы можем перейти в браузере по адресу https://matrix.dnsname.com и увидеть, что это работает.

Осталось совсем немного и мы сможем погрузиться в мир "черт, как же это все работает....".

Мы уже сделали виртуальный хост riot.dnsname.com и указали директорию /var/www/riot.dnsname.com/riot;, однако такой директории еще нет. Давайте ее создадим и загрузим туда веб-приложение Riot (Element) из git:

# mkdir /var/www/riot.dnsname.com# cd /var/www/riot.dnsname.com# wget https://github.com/vector-im/element-web/releases/download/v1.7.23/element-v1.7.23.tar.gz# apt install -y gnupg# wget https://github.com/vector-im/element-web/releases/download/v1.7.23/element-v1.7.23.tar.gz.asc# gpg --keyserver keyserver.ubuntu.com --search-keys releases@riot.im# wget https://packages.riot.im/riot-release-key.asc# gpg --import riot-release-key.asc# gpg --verify element-v1.7.23.tar.gz.asc# gpg --edit-key 74692659bda3d940# tar -xzvf element-v1.7.23.tar.gz# ln -s element-v1.7.23 riot# chown www-data:www-data -R riot# cd riot# cp config.sample.json config.json

Далее нам надо привести содержимое конфигурационного файла config.json к следующему виду (меняем "base_url": "https://matrix.dnsname.com", "server_name": "dnsname.com", "jitsi": { "preferredDomain": "jitsi.dnsname.com" - последняя настройка свяжет наш matrix и jitsi):

# vim /var/www/riot.dnsname.com/riot/config.json{    "default_server_config": {        "m.homeserver": {            "base_url": "https://matrix.dnsname.com",            "server_name": "dnsname.com"        },        "m.identity_server": {            "base_url": "https://vector.im"        }    },    "disable_custom_urls": false,    "disable_guests": false,    "disable_login_language_selector": false,    "disable_3pid_login": false,    "brand": "Element",    "integrations_ui_url": "https://scalar.vector.im/",    "integrations_rest_url": "https://scalar.vector.im/api",    "integrations_widgets_urls": [        "https://scalar.vector.im/_matrix/integrations/v1",        "https://scalar.vector.im/api",        "https://scalar-staging.vector.im/_matrix/integrations/v1",        "https://scalar-staging.vector.im/api",        "https://scalar-staging.riot.im/scalar/api"    ],    "bug_report_endpoint_url": "https://element.io/bugreports/submit",    "defaultCountryCode": "GB",    "showLabsSettings": false,    "features": {        "feature_new_spinner": false    },    "default_federate": true,    "default_theme": "light",    "roomDirectory": {        "servers": [            "matrix.org"        ]    },    "piwik": {        "url": "https://piwik.riot.im/",        "whitelistedHSUrls": ["https://matrix.org"],        "whitelistedISUrls": ["https://vector.im", "https://matrix.org"],        "siteId": 1    },    "enable_presence_by_hs_url": {        "https://matrix.org": false,        "https://matrix-client.matrix.org": false    },    "settingDefaults": {        "breadcrumbs": true    },    "jitsi": {        "preferredDomain": "jitsi.dnsname.com"    }}

Осталось только перезагрузить службы (или сервер целиком):

# systemctl restart nginx# systemctl restart matrix-synapse

Регистрация и чаты

Осталось понять, как этим пользоваться. Я не буду детально обрисовывать использование, а расскажу, как зарегистрироваться, установить приложение, добавить "друга по переписке". Поехали:

Переходим в браузере на https://riot.dnsname.com

Создаем аккаунт.Создаем аккаунт.Меняем домашний сервер. Придумываем логин и пароль. Дальше надо будет создать ключи шифрования, просто следуйте инструкциям riot.Меняем домашний сервер. Придумываем логин и пароль. Дальше надо будет создать ключи шифрования, просто следуйте инструкциям riot.Искать пользователя надо в данном формате.Искать пользователя надо в данном формате.

Обновление Riot

Периодически тут выкатывают обновления серверной части Riot. Обновиться очень просто. Идем на Гит (ссылка выше) и берем там ссылки необходимой версии релиза, а именно нам нужны element-vx.x.xx.tar.gz и element-vx.x.xx.tar.gz.asc. Далее идем:

# cd /var/www/riot.dnsname.com/# wget https://github.com/vector-im/element-web/releases/download/vx.x.xx/element-vx.x.xx.tar.gz# wget https://github.com/vector-im/element-web/releases/download/vx.x.xx/element-vx.x.xx.tar.gz.asc# gpg --keyserver keyserver.ubuntu.com --search-keys releases@riot.im# wget https://packages.riot.im/riot-release-key.asc# gpg --import riot-release-key.asc# gpg --verify element-vx.x.xx.tar.gz.asc# tar -xzvf element-vx.x.xx.tar.gz# rm riot# ln -s element-vx.x.xx riot# chown www-data:www-data -R riot# cd riot# cp config.sample.json config.json

Теперь надо обновить файл конфигурации как мы делали ранее тут.

Репозиторий с релизами Riot.

Клиентское приложение element.

Подробнее..

Парсинг логов при помощи Fluent-bit

25.03.2021 18:04:40 | Автор: admin

Не так давно передо мной встала задача организации логгирования сервисов, разворачиваемых с помощью docker контейнеров. В интернете нашел примеры простого логгирования контейнеров, однако хотелось большего. Изучив возможности Fluent-bit я собрал рабочий пайплайн трансформации логов. Что в сочетании с Elasticsearch и Kibana, позволило быстро искать и анализировать лог-сообщения.

Цель туториала: организовать логгирование docker контейнеров. Также необходимо структурировать записи логов, и обеспечить поиск и фильтрацию по их полям.

Кому интересно, добро пожаловать под кат)

Необходимы базовые знания bash, docker-compose, Elasticsearch и Kibana.

Обзор используемого стека

Тестовое приложение будем запускать с помощьюdocker-compose.

Для организации логгирования воспользуемся следующими технологиями:

  • fluent-bit- осуществляет сбор, обработку и пересылку в хранилище лог-сообщений.

  • elasticsearch- централизованно хранит лог-сообщения, обеспечивает их быстрый поиск и фильтрацию.

  • kibana- предоставляет интерфейс пользователю, для визуализации данных хранимых в elasticsearch

На Хабре естьобзор стековтехнологий, используемых для логгирования контейнеров. Прежде чем идти дальше предварительно можно с ней ознакомиться.

Подготовка тестового приложения

Для примера организуем логгирование веб-сервера Nginx.

Подготовка Nginx

  1. Создадим директорию с проектом и добавим в нее docker-compose.yml, в котором будем задавать конфигурацию запуска контейнеров приложения.

  2. Определим формат логов Nginx. Для этого создадим директорию nginx c файлом nginx.conf. В нем переопределим стандартный формат логов:

    user  nginx;worker_processes  1;error_log  /var/log/nginx/error.log warn;pid        /var/run/nginx.pid;events {    worker_connections  1024;}http {    include       /etc/nginx/mime.types;    default_type  application/octet-stream;log_format  main  'access_log $remote_addr "$request" '                  '$status "$http_user_agent"';access_log  /var/log/nginx/access.log  main;sendfile        on;keepalive_timeout  65;include /etc/nginx/conf.d/*.conf;}
    
  3. Добавим сервисwebв docker-compose.yml:

    version: "3.8"services:  web:    container_name: nginx    image: nginx    ports:      - 80:80    volumes:      # добавляем конфигурацию в контейнер      - ./nginx/nginx.conf:/etc/nginx/nginx.conf
    

Подготовка fluent-bit

Для начала организуем самый простой вариант логгирования. Создадим директорию fluent-bit c конфигурационным файлом fluent-bit.conf. Про формат и схему конфигурационного файла можно прочитатьздесь.

  1. Fluent-bit предоставляет большое количество плагинов для сбора лог-сообщений из различных источников. Полный список можно найтиздесь. В нашем примере мы будем использовать плагинforward.

    Плагин выводаstdoutпозволяет перенаправить лог-сообщения в стандартный вывод (standard output).

    [INPUT]    Name              forward[OUTPUT]    Name stdout    Match *
    
  2. Добавим в docker-compose.yml сервисfluent-bit:

    version: "3.8"services:  web:    ...  fluent-bit:    container_name: fluent-bit    image: fluent/fluent-bit    ports:      # необходимо открыть порты, которые используются плагином forward      - 24224:24224      - 24224:24224/udp    volumes:      # добавляем конфигурацию в контейнер      - ./fluent-bit/fluent-bit.conf:/fluent-bit/etc/fluent-bit.conf
    
  3. Добавим настройки логгирования для сервисаweb:

    version: "3.8"services:  web:    ...    depends_on:      - fluent-bit    logging:      # используемый драйвер логгирования      driver: "fluentd"      options:        # куда посылать лог-сообщения, необходимо что бы адрес         # совпадал с настройками плагина forward        fluentd-address: localhost:24224        # теги используются для маршрутизации лог-сообщений, тема         # маршрутизации будет рассмотрена ниже        tag: nginx.logs  fluent-bit:    ...
    
  4. Запустим тестовое приложение:

    docker-compose up
    

    Сгенерируем лог-сообщение, откроем еще одну вкладку терминала и выполним команду:

    curl localhost
    

    Получим лог-сообщение в следующем формате:

    [    1616473204.000000000,    {"source"=>"stdout",    "log"=>"172.29.0.1 "GET / HTTP/1.1" 200 "curl/7.64.1"",    "container_id"=>"efb81a754706b1ece6948072934df85ea44466305b326cd45",    "container_name"=>"/nginx"}]
    

    Сообщение состоит из:

    • временной метки, добавляемой fluent-bit;

    • лог-сообщения;

    • мета данных, добавляемых драйвером fluentd.

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

 docker-compose.yml fluent-bit    fluent-bit.conf nginx     nginx.conf

Кратко о маршрутизации лог-сообщиний в fluent-bit

Маршрутизация в fluent-bit позволяет направлять лог-сообщения через различные фильтры, для их преобразования, и в конечном итоге в один или несколько выходных интерфейсов. Для организации маршрутизации используется две основные концепции:

  • тег (tag) - человеко читаемый индикатор, позволяющий однозначно определить источник лог-сообщения;

  • правило сопоставления (match) - правило, определяющее куда лог-сообщение должно быть перенаправлено.

Выглядит все следующим образом:

  1. Входной интерфейс присваивает лог-сообщению заданные тег.

  2. В настройках фильтра или выходного интерфейса обязательно необходимо указать правило сопостовления, которое определяет выполнять обработку данного лог-сообщения или нет.

Подробнее можно прочитать вофициальной документации.

Очистка лог-сообщений от мета данных.

Мета данные для нас не представляют интерес, и только загромождают лог сообщение. Давайте удалим их. Для этого воспользуемся фильтромrecord_modifier. Зададим его настройки в файле fluent-bit.conf:

[FILTER]    Name record_modifier    # для всех лог-сообщений    Match *    # оставить только поле log    Whitelist_key log

Теперь лог-сообщение имеет вид:

[    1616474511.000000000,    {"log"=>"172.29.0.1 "GET / HTTP/1.1" 200 "curl/7.64.1""}]

Отделение логов запросов от логов ошибок

На текущий момент логи посылаемые Nginx можно разделить на две категории:

  • логи с предупреждениями, ошибками;

  • логи запросов.

Давайте разделим логи на две группы и будем структурировать только логи запросов. Все логи-сообщения от Nginx помечаются тегом nginx.logs. Поменяем тег для лог-сообщений запросов на nginx.access. Для их идентификации мы заблаговременно добавили в начало сообщения префикс access_log.

Добавим новый фильтрrewrite_tag. Ниже приведена его конфигурация.

[FILTER]    Name rewrite_tag    # для сообщений с тегом nginx.logs    Match nginx.logs    # применить правило: для лог-сообщений поле log которых содержит строку    # access_log, поменять тег на nginx.access, исходное лог-сообщение отбросить.    Rule $log access_log nginx.access false

Теперь все лог-сообщения запросов будут помечены тегом nginx.access, что в будущем позволит нам выполнять фильтрацию логов описанным выше категориям.

Парсинг лог-сообщения

Давайте структурируем наше лог-сообщение. Для придания структуры лог-сообщению его необходимо распарсить. Это делается с помощью фильтраparser.

  1. Лог-сообщение представляет собой строку. Воспользуемся парсеромregex, который позволяет с помощью регулярных выражений определить пары ключ-значение для информации содержащейся в лог-сообщении. Зададим настройки парсера. Для этого в директории fluent-bit создадим файл parsers.conf и добавим в него следующее:

    [PARSER]    Name   nginx_parser    Format regex    Regex  ^access_log (?<remote_address>[^ ]*) "(?<method>\S+)(?: +(?<path>[^\"]*?)(?: +\S*)?)?" (?<status>[^ ]*) "(?<http_user_agent>[^\"]*)"$    Types  status:integer
    
  2. Обновим конфигурационный файл fluent-bit.conf. Подключим к нему файл с конфигурацией парсера и добавим фильтр parser.

    [SERVICE]    Parsers_File /fluent-bit/parsers/parsers.conf[FILTER]    Name parser    # для сообщений с тегом nginx.access    Match nginx.access    # парсить поле log    Key_Name log    # при помощи nginx_parser    Parser nginx_parser
    
  3. Теперь необходимо добавить файл parsers.conf в контейнер, сделаем это путем добавления еще одного volume к сервису fluent-bit:

    version: "3.8"services:  web:    ...  fluent-bit:    ...    volumes:      - ./fluent-bit/fluent-bit.conf:/fluent-bit/etc/fluent-bit.conf
    
  4. Перезапустим приложение, сгенерируем лог-сообщение запроса. Теперь оно имеет следующую структуру:

    [  1616493566.000000000,  {    "remote_address"=>"172.29.0.1",    "method"=>"GET",    "path"=>"/",    "status"=>200,    "http_user_agent"=>"curl/7.64.1"  }]
    

Сохранение лог-сообщений в elasticsearch

Теперь организуем отправку лог-сообщений на хранения в elasticsearch.

  1. Добавим два выходных интерфейса в конфигурацию fluent-bit, один для лог-сообщений запросов, другой для лог-сообщений ошибок. Для этого воспользуемся плагиномes.

    [OUTPUT]    Name  es    Match nginx.logs    Host  elasticsearch    Port  9200    Logstash_Format On    # Использовать префикс nginx-logs для логов ошибок    Logstash_Prefix nginx-logs[OUTPUT]    Name  es    Match nginx.access    Host  elasticsearch    Port  9200    Logstash_Format On    # Использовать префикс nginx-access для логов запросов    Logstash_Prefix nginx-access
    
  2. Добавим в docker-compose.yml сервисы elasticsearch и kibana.

    version: "3.8"services:  web:    ...  fluent-bit:    ...    depends_on:      - elasticsearch  elasticsearch:    container_name: elasticsearch    image: docker.elastic.co/elasticsearch/elasticsearch:7.10.2    environment:      - "discovery.type=single-node"  kibana:    container_name: kibana    image: docker.elastic.co/kibana/kibana:7.10.1    depends_on:      - "elasticsearch"    ports:      - "5601:5601"
    

На текущем этапе структура проекта выглядит следующим образом:

 docker-compose.yml fluent-bit    fluent-bit.conf    parsers.conf nginx     nginx.conf

Финальную версию проекта можно найти в репозитории.

Результаты

Благодаря структурированию лог-сообщений мы можем фильтровать их по различным полям, к примеру:

  • показать только лог-сообщения запросов;

  • показать лог-сообщения запросов с http статусом 404;

  • отображать не все поля лог-сообщения.

Пример фильтрации логов. Выполнена фильтрация по значению поля "status", так же выбраны только необходимые в данный момент поля.Пример фильтрации логов. Выполнена фильтрация по значению поля "status", так же выбраны только необходимые в данный момент поля.

Всем спасибо! Надеюсь туториал был полезен.

Подробнее..

Подборка телеграм-каналов для DevOps инженеров

13.03.2021 14:14:35 | Автор: admin

Приветствую, братцы!

Задача получения актуальной информации и совета опытных коллег сегодня актуальна как никогда. С одной стороны, сложно превзойти крупнейшие ИТ-сообщества в Slack. С другой стороны, важно иметь контакт с коллегами в нашей стране, в своем городе. Телеграм за последние годы стал крупнейшей площадкой для русскоязычного ИТ-сообщества, присоединяйтесь, не отставайте :)

Подборка телеграм-каналов и чатов:

Вакансии

Devops Jobs - Вакансии и резюме

Jobs for Devs & Ops - Вакансии для инженеров и разработчиков

Новостные каналы

Mops DevOps - Kubernetes, DevOps, SRE и многое другое

DevOps Deflope News - Новостной канал

Записки админа - Linux и администрировании серверов

k8s (in)security - Канал о (не)безопасности Kubernetes

Мир IT c Антоном Павленко - IT новости, статьи и видео

Конференции

DevOpsConf Channel - Информационный канал профессиональной конференции по эксплуатации и devops DevOpsConf Russia

Meetup Moscow - анонсы конференций

Инструменты DevOps

terraform_ru - Русскоязычный чат Hashicorp Terraform

pro_ansible- Чат для взаимопомощи по Ansible

Docker_ru- Русскоговорящее сообщество по экосистеме Docker (чат)

RU.Docker - Официальное Русское Сообщество (чат)

ru_gitlab- Русскоговорящая группа по GitLab

ru_jenkins- Русскоговорящая группа по Jenkins

Инфраструктура

Kubernetes- Общаемся на темы, посвященные Kubernetes, конфигурации и возможностям

istio_ru - Чат про Mervice Mesh в целом и Istio в частности

Вокруг Kubernetes в Mail.ru Group митапы по Kubernetes, DevOps, открытым технологиям в Mail.ru Group и немного проKubernetes как сервис

Envoy Proxy- Делимся опытом, экспертизой, советами и фэйлами :)

nginx_ru - Сообщество пользователей nginx, новости, обсуждения конфигураций

SDS и Кластерные FS - Обсуждаем Software-defined storage, кластерные файловые системы, блочные хранилища, стратегии построения хранилища и все что с ними связанно (Linstor, DRBD, ZFS, LVM, Ceph, GlusterFS, Lustre, MooseFS, LizardFS, mdadm, S3, iSCSI, NFS, OrangeFS, OCFS, GFS2)

Грефневая Кафка (pro.kafka)- Здесь топят за Кафку (Apache Kafka )

pro.kafka- Чат для добросовестных господ и дам, посвящённый Apache Kafka

DBA- Общаемся и обсуждаем темы, посвященные DBA, PostgreSQL, Redis, MongoDB, MySQL...

Облачные провайдеры

AWS_ru- Чат про Amazon Web Services

AWS notes- Канал про Amazon Web Services

Yandex.Cloud - Новости от команды платформы Yandex.Cloud

IT-журнал Завтра облачно - Блог команды Mail.ru Cloud Solutions (MCS)

Мониторинг и сбор логов

VictoriaMetrics_ru - Чат для обсуждения VictoriaMetrics

Церковь метрик- Канал про Метрики. Метрики. Метрики.

ru_logs - ElasticSearch, Graylog, Mtail, rsyslog и все такое прочее

Мониторим ИТ- Канал о мониторинге ИТ-инфраструктуры и приложений

Друзья, очень вероятно, что я мог забыть про какой-нибудь хороший, полезный и всеми любимы телеграм-канал. Жду ваши идеи в комментариях.

Подробнее..
Категории: Kubernetes , Gitlab , Devops , Nginx , Docker , Monitoring , Istio , Telegram , Aws , Envoy

Перевод Настройка распределенной трассировки в Kubernetes с OpenTracing, Jaeger и Ingress-NGINX

20.05.2021 18:13:57 | Автор: admin

В эпоху, когда крупные компании используют более 500 микросервисов одновременно, важно иметь возможность быстро выяснить, из-за чего произошел сбой или снизилась производительность. Без специализированных инструментов это может быть похоже на поиск иголки в стоге сена.

Распределённая трассировка (Distributed Tracing) - это метод, используемый для мониторинга приложений. Для микросервисов он просто незаменим.

В качестве примера мы будем использовать приложение Meow-Micro, специально созданное для этой статьи. Но вы можете развернуть собственные приложения, если захотите.

Что нам потребуется?

В этом руководстве предполагается, что вы понимаете код, написанный на Go, умеете использовать Ingress-nginx и знаете, как работают основные объекты Kubernetes, такие как Service и Deployment.

Если вы хотите освежить знания, воспользуйтесь этими материалами:

Запуск Kubernetes в Docker Desktop

Начнём с установки Docker Desktop, который позволяет не только запускать контейнеры, но и создать локальный кластер Kubernetes.

После установки Docker Desktop выполните следующие действия, чтобы создать кластер Kubernetes:

  1. Нажмите на иконкуPreferences

2. Выберете вкладкуKubernetes, установите флажокEnable Kubernetes и нажмите кнопкуApply & Restart

3. В появившемся окне нажмите кнопкуInstall и дождитесь пока установка завершится

4. Выберите пунктKubernetes в панели задач

5. В контекстном меню выбиритеdocker-desktop

6. Проверьте, что вы подключены к нужному кластеру

$ kubectl cluster-infoKubernetes master is running at https://kubernetes.docker.internal:6443KubeDNS is running at https://kubernetes.docker.internal:6443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy

Установка Ingress-NGINX Controller

1. Воспользуйтесь командой из официального руководства для установки Ingress-NGINX Controller.

$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v0.45.0/deploy/static/provider/cloud/deploy.yamlnamespace/ingress-nginx createdserviceaccount/ingress-nginx createdconfigmap/ingress-nginx-controller createdclusterrole.rbac.authorization.k8s.io/ingress-nginx createdclusterrolebinding.rbac.authorization.k8s.io/ingress-nginx createdrole.rbac.authorization.k8s.io/ingress-nginx createdrolebinding.rbac.authorization.k8s.io/ingress-nginx createdservice/ingress-nginx-controller-admission createdservice/ingress-nginx-controller created\deployment.apps/ingress-nginx-controller createdvalidatingwebhookconfiguration.admissionregistration.k8s.io/ingress-nginx-admission createdserviceaccount/ingress-nginx-admission created]clusterrole.rbac.authorization.k8s.io/ingress-nginx-admission createdclusterrolebinding.rbac.authorization.k8s.io/ingress-nginx-admission createdrole.rbac.authorization.k8s.io/ingress-nginx-admission createdrolebinding.rbac.authorization.k8s.io/ingress-nginx-admission createdjob.batch/ingress-nginx-admission-create created\job.batch/ingress-nginx-admission-patch created

2. Проверьте, что установка Ingress Сontroller прошла успешно. Pods должны быть запущены и находятся в статусе Ready.

$ kubectl get pods -n ingress-nginxNAME                                     READY   STATUS    RESTARTS ingress-nginx-admission-create-52jsl        0/1     Completed   0          ingress-nginx-admission-patch-78fkc         0/1     Completed   0          ingress-nginx-controller-6f5454cbfb-qsfnn   1/1     Running     0 

Важно:Если Pod находится в статусе Pending из-за нехватки ресурсов CPU/Memory, вы можете добвить ресурсы вашему кластеру в настройках Docker Desktop.

Установка Jaeger и настройка Ingress Controller

Jaeger - это платформа распределенная трассировки, которую мы будем использовать для мониторинга наших микросервисов. Установим Jaeger и включим трассировку на уровне Ingress Controller.

1. Вначале склонируйте репозиторийmeow-micro, этот проект мы будем использовать во всех примерах.

$ git clone https://github.com/diazjf/meow-micro.gitCloning into 'meow-micro'...remote: Enumerating objects: 105, done....$ cd meow-micro

2. В репозитории вы найдёте манифесты для jaeger-all-in-one. Установим Jaeger из этих манифестов.

$ kubectl apply -f jaeger/jaeger-all-in-one.yamldeployment.apps/jaeger createdservice/jaeger-query createdservice/jaeger-collector createdservice/jaeger-agent createdservice/zipkin created

3. Убедимся, что Jaeger запущен и готов к работе.

$ kubectl get podsNAME                      READY   STATUS    RESTARTS   AGEjaeger-6f6b5d8689-8gccp   1/1     Running   0          17s

4. Пришло время настроить совместную работу Ingress-NGINX and Jaeger, для этого необходимо добавить параметрыenable-opentracingиjaeger-collector-hostвingress-nginx-controllerConfigMap. В параметре jaeger-collector-host указываем имя сервиса Jaeger.

$ echo '  apiVersion: v1  kind: ConfigMap  data:    enable-opentracing: "true"    jaeger-collector-host: jaeger-agent.default.svc.cluster.local                metadata:    name: ingress-nginx-controller    namespace: ingress-nginx  ' | kubectl replace -f -configmap/ingress-nginx-controller replaced

5. Убедимся, что в настройках Ingress Controller включени настроен opentracing.

$ kubectl get pods -n ingress-nginx | grep controlleringress-nginx-controller-6f5454cbfb-qptxt   1/1     Running     0          8m56s$ kubectl exec -it ingress-nginx-controller-6f5454cbfb-qptxt -n ingress-nginx -- bash -c "cat nginx.conf | grep ngx_http_opentracing_module.so"load_module /etc/nginx/modules/ngx_http_opentracing_module.so;$ kubectl exec -it ingress-nginx-controller-6f5454cbfb-qptxt -n ingress-nginx -- bash -c "cat nginx.conf | grep jaeger"opentracing_load_tracer /usr/local/lib/libjaegertracing_plugin.so /etc/nginx/opentracing.json;$ kubectl exec -it ingress-nginx-controller-6f5454cbfb-qptxt -n ingress-nginx -- bash -c "cat /etc/nginx/opentracing.json"{  "service_name": "nginx",  "propagation_format": "jaeger",  "sampler": {    "type": "const",    "param": 1,    "samplingServerURL": "http://127.0.0.1:5778/sampling"  },  "reporter": {    "endpoint": "",    "localAgentHostPort": "jaeger-agent.default.svc.cluster.local:6831"  },  "headers": {    "TraceContextHeaderName": "",    "jaegerDebugHeader": "",    "jaegerBaggageHeader": "",    "traceBaggageHeaderPrefix": ""  }}

Jaeger и Ingress Controller успешно установлены и настроены. Самое время развернуть наши микросервисы!

Разворачиваем тестовое приложение

Тестовое приложение meow-micro состоит из двух микросервисов. Клиент meow-client - принимает REST запрос и отправляет информацию сервису meow-server через GRPC.

Подробная информация об использовании REST и GRPC с GoLang:

Тестовое приложение также содержит несколько дополнительных инструментов:

tracing.go

Получает параметры для настройки Jaeger из окружения, в котором запускается Helm для установкиmeow-clientandmeow-server.

cfg, err := config.FromEnv()if err != nil {panic(fmt.Sprintf("Could not parse Jaeger env vars: %s", err.Error())) }  tracer, closer, err := cfg.NewTracer()if err != nil {   panic(fmt.Sprintf("Could not initialize jaeger tracer: %s", err.Error())) }

client.go

Выполняет конфигурирование клиента - задает service nameдля trace и определяет span.

  • Span базовый элемент распределенной трассировки. Представляет собой описание некоего рабочего процесса (например, запроса к базе данных). Span'ы обычно содержат ссылки на другие span'ы, что позволяет объединять множество span'ов вTrace.

  • Trace визуализация жизни запроса в процессе его перемещения по распределенной системе.

Подробную информацию о понятиях trace и span можно посмотреть в официальной документации.

// main function spanos.Setenv("JAEGER_SERVICE_NAME", "meow-client")tracer, closer := tracing.Init()defer closer.Close()http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {spanCtx, _ := tracer.Extract(opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(r.Header))span := tracer.StartSpan("send-meow-communication", ext.RPCServerOption(spanCtx))defer span.Finish()...// sleep function spanos.Setenv("JAEGER_SERVICE_NAME", "meow-client")tracer, closer := tracing.Init()defer closer.Close()span := tracer.StartSpan("sleep")defer span.Finish()

Установка тестового приложения

Установить Helm v3 можно на любую операционную систему, например на macOS с помощью brew. Теперь мы готовы развернуть микросервисы в нашем кластере.

$ brew install helm...==> Downloading https://ghcr.io/v2/homebrew/core/helm/manifests/3.5.4######################################################################## 100.0%==> Downloading https://ghcr.io/v2/homebrew/core/helm/blobs/sha256:5dac5803c1ad2db3a91b0928fc472aaf80a4==> Downloading from https://pkg-containers-az.githubusercontent.com/ghcr1/blobs/sha256:5dac5803c1ad2db######################################################################## 100.0%==> Pouring helm--3.5.4.big_sur.bottle.tar.gz...$ helm versionversion.BuildInfo{Version:"v3.3.4", GitCommit:"a61ce5633af99708171414353ed49547cf05013d", GitTreeState:"clean", GoVersion:"go1.14.9"}

Воспользуемся Makefile из репозитория, чтобы развернуть тестовое приложение в кластере Kubernetes.

# Build client and server from Dockerfile$ make builddocker build -t meow-client:1.0 -f client/Dockerfile .[+] Building 17.1s (10/10) FINISHED...docker build -t meow-server:1.0 -f server/Dockerfile .[+] Building 0.5s (10/10) FINISHED...# Install Microservices into Kubernetes via Helm$ make installhelm install -f helm/Values.yaml meow-micro ./helmNAME: meow-microLAST DEPLOYED: Mon Apr 26 13:42:38 2021NAMESPACE: defaultSTATUS: deployedREVISION: 1TEST SUITE: None

Проверим, что Pods обоих сервисов запустились и работают.

$ kubectl get podsNAME                           READY   STATUS    RESTARTS   AGEjaeger-6f6b5d8689-s7cln        1/1     Running   0          26mmeow-client-8b974778c-85896    1/1     Running   0          15mmeow-server-56f559db44-5mvgp   1/1     Running   0          15m

Просмотр данных трассировки

А теперь самое интересное! Взглянем на трассировку.

Откройте консоль Jaeger, указав в браузере адресhttp://localhost:8081.

2. Отправим запрос нашему приложению.

$ curl http://localhost/meow -X POST -d '{"name": "Meow-Mixer"}'200 - Meow sent: Meow-Mixer

3. Обновите страницу браузера. В меню Service выберите - nginx.

4. Нажмите кнопкуFind Traces.

5. Отобразятся все трассировки для nginx. Каждую из них можно развернуть, чтобы получить больше информации.

6. Теперь вы можете отслеживать, сколько времени занял каждый запрос.

И выводить дополнительную информацию по каждому запросу.

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

Другие системы трассировки

Мы рассмотрели как использовать Jaeger для трассировки запросов Ingress-Nginx. Для этих целей можно также использовать Zipkin или DataDog.

Подробнее..

Немного об использовании regex в map nginx

21.05.2021 18:18:17 | Автор: admin

Давно ничего не писал, поэтому разбавим конец пятницы простыми, но не всегда очевидными иcканиями в Nginx.

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

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

И здесь важным является не только, то что "map не влечёт за собой никаких дополнительных расходов на обработку запросов", а и то, что "переменные вычисляются только в момент использования".

Как известно, конфиг Nginx, в основном, декларативен. Это касается и директивы map, и, не смотря на то, что она расположена в контексте http, её вычисление не происходит до момента обработки запроса. То есть при использовании результирующей переменной в контекстах server, location, if и т.п. мы "подставляем" не готовый результат вычисления, а лишь "формулу" по который этот результат будет вычислен в нужный момент. В этой конфигурационной казуистике не возникает проблем до того момента, пока мы не используем регулярные выражения. А именно регулярные выражения с выделениями. А ещё точнее, регулярные выражения с неименованными выделениями. Проще показать на примере.

Допустим у нас есть домен example.com с множеством поддоменов 3-го уровня, а-ля ru.example.com, en.example.com, de.example.com и т.д., и мы хотим их перенаправить на новые поддомены ru.example.org, en.example.org, de.example.org и т.п. Вместо того чтобы описывать сотни строк редиректов мы поступим вот так:

map $host $redirect_host {  default "example.org";  "~^(\S+)\.example\.com$"  $1.example.org;}server {    listen       *:80;    server_name  .example.com;  location / {        rewrite ^(.*)$ https://$redirect_host$1 permanent;    }

Здесь мы ошибочно ожидали, что при запросе ru.example.com произойдет вычисление регулярки в map и, соответственно, попав в location переменная $redirect_host будет содержать значение ru.example.org, однако на деле это не так:

$ GET -Sd ru.example.comGET http://ru.example.com301 Moved PermanentlyGET https://ru.example.orgru

Оказалось, что на момент исполнения запроса наша переменная равна ru.example.orgru. Всё из-за того, что мы пренебрегли предупреждением "переменные вычисляются только в момент использования", а в нашем rewrite оказалась некая регулярка вложенная в регулярку.

Самый простой вариант решения - не использовать regexp одновременно и в map и в месте вычисления переменной, например, для данного конкретного случая это может выглядеть так:

map $host $redirect_host {  default "example.org";  "~^(\S+)\.example\.com$"  $1.example.org;}server {    listen       *:80;    server_name  .example.com;    location / {        return 301 https://$redirect_host$request_uri;    }}

Но что делать, если альтернативного решения без регулярок нет (ну или просто очень хочется).
Пробуем использовать именованные выделения в map:

map $host $redirect_host {  default "example.org";  "~^(?<domainlevel3>\S+)\.example\.com$"  $domainlevel3.example.org;}server {    listen       *:80;    server_name  .example.com;    location / {        rewrite ^(.*)$ https://$redirect_host$1 permanent;    }}

Попытка не увенчалась успехом:

$ GET -Sd ru.example.comGET http://ru.example.com301 Moved PermanentlyGET https://ru.example.orgru

так как наше неименованное выделение $1 получит результат именованного $domainlevel3. То есть необходимо использовать именованные выделения в обеих регулярках:

map $host $redirect_host {  default "example.org";  "~^(?<domainlevel3>\S+)\.example\.com$"  $domainlevel3.example.org;}server {    listen       *:80;    server_name  .example.com;    location / {        rewrite ^(?<requri>.*)$ https://$redirect_host$requri permanent;    }}

И теперь всё работает как ожидалось:

$ GET -Sd ru.example.comGET http://ru.example.com301 Moved PermanentlyGET https://ru.example.org/
Подробнее..

Acme.sh Ansible Alias mode Автоматизируем получение и распространение TLS сертификатов

09.06.2021 20:16:20 | Автор: admin

Acme.sh - скрипт, позволяющий без особых проблем получать let's encrypt сертификаты очень разными способами. В данной статье я разберу как получать сертификаты через DNS api, но этим уже никого не удивишь, поэтому расскажу про метод DNS alias, он свежий (всего 3 года) и интересный. А так же про автоматизацию на Ansible и немного про мониторинг сертификатов.

Видеоверсия

Режимы acme.sh получения сертификатов прямо на целевом сервере

  • Webroot

  • Nginx\Apache

  • Stanalone

Режимы хорошие и удобные, когда у вас один - два сервера и можно просто на каждый установить acme.sh. Когда количество серверов, которым нужно TLS, переваливает за десяток, удобнее начать использовать wilcard сертификаты и выделить отдельный сервер для получения и распространения сертификата\ов. Получить wildcard сертификат можно только через подтверждение владения DNS зоной. DNS режимов несколько:

  • DNS manual

  • DNS API

  • DNS alias

Все примеры буду показывать на моём личном домене и установленном локально acme.sh.

DNS manual mode

Manual режим работает супер просто. Запускаем acme.sh с флагом --dns

acme.sh --issue --dns -d *.itdog.info --yes-I-know-dns-manual-mode-enough-go-ahead-please

koala@x220:~$ acme.sh --issue --dns -d *.itdog.info --yes-I-know-dns-manual-mode-enough-go-ahead-please[Ср мая  5 14:52:29 MSK 2021] Using CA: https://acme-v02.api.letsencrypt.org/directory[Ср мая  5 14:52:29 MSK 2021] Creating domain key[Ср мая  5 14:52:29 MSK 2021] The domain key is here: /home/koala/.acme.sh/*.itdog.info/*.itdog.info.key[Ср мая  5 14:52:29 MSK 2021] Single domain='*.itdog.info'[Ср мая  5 14:52:29 MSK 2021] Getting domain auth token for each domain[Ср мая  5 14:52:32 MSK 2021] Getting webroot for domain='*.itdog.info'[Ср мая  5 14:52:32 MSK 2021] Add the following TXT record:[Ср мая  5 14:52:32 MSK 2021] Domain: '_acme-challenge.itdog.info'[Ср мая  5 14:52:32 MSK 2021] TXT value: 'QXRgFOfVOZGOBC1qxAToMNOf7Xsv9gjM8hYG6akRoJ8'[Ср мая  5 14:52:32 MSK 2021] Please be aware that you prepend _acme-challenge. before your domain[Ср мая  5 14:52:32 MSK 2021] so the resulting subdomain will be: _acme-challenge.itdog.info[Ср мая  5 14:52:32 MSK 2021] Please add the TXT records to the domains, and re-run with --renew.[Ср мая  5 14:52:32 MSK 2021] Please add '--debug' or '--log' to check more details.[Ср мая  5 14:52:32 MSK 2021] See: https://github.com/acmesh-official/acme.sh/wiki/How-to-debug-acme.sh

--issue - запрос на получение

--dns без аргумента - режим ручного DNS

--yes-I-know-dns-manual-mode-enough-go-ahead-please - интересное решение проблемы, когда люди не понимают что такое ручной режим

Он выдаёт TXT запись, которую нам необходимо добавить в наш dns хостинг, в данном случае это _acme-challenge.itdog.info TXT QXRgFOfVOZGOBC1qxAToMNOf7Xsv9gjM8hYG6akRoJ8

Ручной он потому что, мы вручную добавляем эту запись.

Анимация manual modeАнимация manual mode

После этого добавления нужно подождать какое-то время, что бы запись зарезолвилась и выполнить такую же команду, только с --renew

После добавления записи проверяем начала ли она резолвится на гугловом dns

koala@x220:~$ dig -t txt _acme-challenge.itdog.info @8.8.8.8; <<>> DiG 9.11.3-1ubuntu1.15-Ubuntu <<>> -t txt _acme-challenge.itdog.info @8.8.8.8;; ANSWER SECTION:_acme-challenge.itdog.info. 1798 IN    TXT    "QXRgFOfVOZGOBC1qxAToMNOf7Xsv9gjM8hYG6akRoJ8"

Она резолвится, а значит можно получать сертификат

koala@x220:~$ acme.sh --renew --dns -d *.itdog.info --yes-I-know-dns-manual-mode-enough-go-ahead-please[Ср мая  5 14:58:08 MSK 2021] Renew: '*.itdog.info'[Ср мая  5 14:58:09 MSK 2021] Using CA: https://acme-v02.api.letsencrypt.org/directory[Ср мая  5 14:58:09 MSK 2021] Single domain='*.itdog.info'[Ср мая  5 14:58:09 MSK 2021] Getting domain auth token for each domain[Ср мая  5 14:58:09 MSK 2021] Verifying: *.itdog.info[Ср мая  5 14:58:13 MSK 2021] Success[Ср мая  5 14:58:13 MSK 2021] Verify finished, start to sign.[Ср мая  5 14:58:13 MSK 2021] Lets finalize the order.[Ср мая  5 14:58:13 MSK 2021] Le_OrderFinalize='https://acme-v02.api.letsencrypt.org/acme/finalize/121...'[Ср мая  5 14:58:15 MSK 2021] Downloading cert.[Ср мая  5 14:58:15 MSK 2021] Le_LinkCert='https://acme-v02.api.letsencrypt.org/acme/cert/042...'[Ср мая  5 14:58:16 MSK 2021] Cert success.-----BEGIN CERTIFICATE-----certificate-----END CERTIFICATE-----[Ср мая  5 14:58:16 MSK 2021] Your cert is in  /home/koala/.acme.sh/*.itdog.info/*.itdog.info.cer [Ср мая  5 14:58:16 MSK 2021] Your cert key is in  /home/koala/.acme.sh/*.itdog.info/*.itdog.info.key [Ср мая  5 14:58:16 MSK 2021] The intermediate CA cert is in  /home/koala/.acme.sh/*.itdog.info/ca.cer [Ср мая  5 14:58:16 MSK 2021] And the full chain certs is there:  /home/koala/.acme.sh/*.itdog.info/fullchain.cer 

После этого TXT запись можно удалить.

Теперь есть ключ и сертификат, который будет действителен 3 месяца. Обновить сертификат можно будет через 2 месяца, let's enctypt даёт запас времени и если у вас вдруг что-то сломается, будет целый месяц чтобы починить и обновить сертификат.

И да, обновляется только сертификат, ключ остаётся таким, какой был выдан в первый раз. Обратите внимание на даты создания файлов, особенно *.example.com.key

# ls -l --time-style=+%Y-%m-%d \*.example.com/total 28-rw-r--r-- 1 root root 1587 2021-04-15 ca.cer-rw-r--r-- 1 root root 3433 2021-04-15 fullchain.cer-rw-r--r-- 1 root root 1846 2021-04-15 *.example.com.cer-rw-r--r-- 1 root root  719 2021-04-15 *.example.com.conf-rw-r--r-- 1 root root  980 2021-04-15 *.example.com.csr-rw-r--r-- 1 root root  211 2021-04-15 *.example.com.csr.conf-rw-r--r-- 1 root root 1675 2019-04-10 *.example.com.key

Хороший режим для одного раза или для понимания как работает подтверждение, но на постоянке каждые 2 месяца вручную обновлять TXT записи не серьёзно, поэтому рассматриваем следующий режим.

DNS API mode

Как это работает? Принцип действия тот же самый что у manual, только acme.sh сам вносит и удаляет TXT запись с помощью API вашего dns провайдера.

Анимация API modeАнимация API mode

Под DNS хостингом и DNS провайдером я буду иметь в виду сервис, в который вносятся DNS записи. Это может быть и DNS хостинг, который есть почти у каждой компании, торгующей доменами (namecheap, beget итд) или как отдельный сервис за деньги (Amazon Route 53, ClouDNS итд), или же ваш собственный сервис развернутый с помощью BIND, PowerDNS итд.

У каждого DNS провайдера свой не стандартизированный API и их поддержка в acme.sh реализована отдельными скриптами. Список всех поддерживаемых провайдеров находится тут https://github.com/acmesh-official/acme.sh/wiki/dnsapi

Для каждого написано как пользоваться и пример. Если вашего провайдера или сервиса нет в списке, можно написать свой скрипт, но не спешите открывать vim, дождитесь третьего способа.

Работу этого режима покажу на примере хостинга DNS у namecheap.

Для каждого DNS провайдера свои настройки, где-то нужно просто включить API и сгенерировать токен, где-то бывает посложнее, например для namecheap нужно ещё внести IP в allow list. Включаем API и сразу генерируется token, добавляем IP в список.

Теперь на локальной машине нужно настроить доступ к API

export NAMECHEAP_USERNAME="USERNAME"export NAMECHEAP_API_KEY="TOKEN"export NAMECHEAP_SOURCEIP="MY-IP"

Отступление про дополнительные флаги force и test. Будем использовать флаг -f (--force), что бы наши сертификаты генерировались заново, т.к. acme.sh видит уже сгенерированные сертификаты при их наличии не будет заново получать. Можно конечно просто сделать rm -rf ~/.acme.sh/domain/ вместо этого. Так же будем использовать флаг --test, что бы лишний раз не нагружать продакшн сервера let's encrypt. Вот такое сообщение мы получим, если после подтверждения в manual режиме попробуем другой режим.

[Ср мая 5 16:39:31 MSK 2021] *.itdog.info is already verified, skip dns-01.

Команда получения через API выглядит таким образом

acme.sh --issue --dns dns_namecheap -d *.itdog.info --test

Здесь после --dns мы добавляем имя провайдера.

Запускаем acme.sh

Раскрыть
koala@x220:~$ acme.sh --issue --dns dns_namecheap -d *.itdog.info --test[Ср мая  5 16:48:05 MSK 2021] Using ACME_DIRECTORY: https://acme-staging-v02.api.letsencrypt.org/directory[Ср мая  5 16:48:06 MSK 2021] Using CA: https://acme-staging-v02.api.letsencrypt.org/directory[Ср мая  5 16:48:06 MSK 2021] Creating domain key[Ср мая  5 16:48:07 MSK 2021] The domain key is here: /home/koala/.acme.sh/*.itdog.info/*.itdog.info.key[Ср мая  5 16:48:07 MSK 2021] Single domain='*.itdog.info'[Ср мая  5 16:48:07 MSK 2021] Getting domain auth token for each domain[Ср мая  5 16:48:09 MSK 2021] Getting webroot for domain='*.itdog.info'[Ср мая  5 16:48:10 MSK 2021] Adding txt value: nCH4tBWCkSVn76301f2SdJqCAzmtXvzQAB_Ag8hURLo for domain:  _acme-challenge.itdog.info[Ср мая  5 16:48:15 MSK 2021] The txt record is added: Success.[Ср мая  5 16:48:15 MSK 2021] Let's check each DNS record now. Sleep 20 seconds first.[Ср мая  5 16:48:36 MSK 2021] You can use '--dnssleep' to disable public dns checks.[Ср мая  5 16:48:36 MSK 2021] See: https://github.com/acmesh-official/acme.sh/wiki/dnscheck[Ср мая  5 16:48:36 MSK 2021] Checking itdog.info for _acme-challenge.itdog.info[Ср мая  5 16:48:37 MSK 2021] Domain itdog.info '_acme-challenge.itdog.info' success.[Ср мая  5 16:48:37 MSK 2021] All success, let's return[Ср мая  5 16:48:37 MSK 2021] Verifying: *.itdog.info[Ср мая  5 16:48:41 MSK 2021] Success[Ср мая  5 16:48:41 MSK 2021] Removing DNS records.[Ср мая  5 16:48:41 MSK 2021] Removing txt: nCH4tBWCkSVn76301f2SdJqCAzmtXvzQAB_Ag8hURLo for domain: _acme-challenge.itdog.info[Ср мая  5 16:48:46 MSK 2021] Removed: Success[Ср мая  5 16:48:46 MSK 2021] Verify finished, start to sign.[Ср мая  5 16:48:46 MSK 2021] Lets finalize the order.[Ср мая  5 16:48:46 MSK 2021] Le_OrderFinalize='https://acme-staging-v02.api.letsencrypt.org/acme/finalize/193...'[Ср мая  5 16:48:48 MSK 2021] Downloading cert.[Ср мая  5 16:48:48 MSK 2021] Le_LinkCert='https://acme-staging-v02.api.letsencrypt.org/acme/cert/fa62...'[Ср мая  5 16:48:49 MSK 2021] Cert success.-----BEGIN CERTIFICATE-----certificate-----END CERTIFICATE-----[Ср мая  5 16:48:49 MSK 2021] Your cert is in  /home/koala/.acme.sh/*.itdog.info/*.itdog.info.cer [Ср мая  5 16:48:49 MSK 2021] Your cert key is in  /home/koala/.acme.sh/*.itdog.info/*.itdog.info.key [Ср мая  5 16:48:49 MSK 2021] The intermediate CA cert is in  /home/koala/.acme.sh/*.itdog.info/ca.cer [Ср мая  5 16:48:49 MSK 2021] And the full chain certs is there:  /home/koala/.acme.sh/*.itdog.info/fullchain.cer

В логе прям видно, что acme.sh добавляет TXT запись, ждёт немного, проверяет запись через доверенные DNS сервера, удаляет запись и скачивает сертификаты с ключом.

После первого запуска через API, acme.sh заносит env переменные c доступами к API себе в файл ~/.acme.sh/account.conf и вам не нужно каждый раз их экспортировать.

Отлично, получение автоматизировали, всё вроде классно. Но у этого метода есть свои недостатки:

  • Если никто не написал скрипта для вашего провайдера\сервиса, то нужно либо писать, либо переезжать на другой провайдер

  • А есть ли у вашего провайдера API?

  • Поразмышляем немного об безопасности. Вот у меня в "открытую" лежит полный доступ к редактированию моего домена, если он каким-то образом попадёт в чужие руки, эти руки могут сделать что угодно. Эту проблему можно решить ограничим доступа в API, например по токену можно только добавлять\удалять txt записи _acme-challenge. Есть ли такая возможность у вашего провайдера? Я не встречал такого, наверное есть у какого-нибудь AWS конечно. Обычно уже хорошо если есть API, а токен один и даёт полный доступ

  • У вас несколько доменов на разных провайдерах (сочувствую). Тут конечно можно настроить каждое API и сделать для каждого провайдера отдельный запуск acme.sh со своими переменными, но мне кажется это не очень удобным. Тем более если у одного из них отсутствует API или скрипт

  • Кто-то просто не любит, что бы в DNS постоянно лазил какой-то скрипт и что-то добавлял\удалял

DNS aliase mode

Это модернизированный режим DNS API.

Идея такая: Есть технический домен, через добавления TXT записей на котором мы подтверждаем владение основным доменом. т.е. acme.sh смотрит CNAME запись у основного домена, видит "перенаправление" на технический домен и идёт к нему проверять TXT запись. А дальше всё как в режиме DNS API.

Анимация alias modeАнимация alias mode

Разберём последовательно. Для демонстрации я купил домен tech-domain.club, он выступает в качестве технического домена. В моём примере основной домен itdog.info располагается на namecheap, а техничский tech-domain.club я делегирую на Hetzner DNS, таким образом операции с записями будут производиться через API Hetzner'a.

В записях основного домена мы добавляем CNAME запись, которая указывает на технический домен

_acme-challenge CNAME _acme-challenge.tech-domain.club

Для провайдера с техническим доменом мы настраиваем доступ к API.

Экспортируем токен Hetzner export HETZNER_Token="TOKEN"

Команда выглядит так (-f и --test опять же для примера)

acme.sh --issue -d *.itdog.info --challenge-alias tech-domain.club --dns dns_hetzner -f --test
Раскрыть
koala@x220:~$ acme.sh --issue -d *.itdog.info -d itdog.info --challenge-alias tech-domain.club --dns dns_hetzner -f --test[Пт мая  7 13:40:11 MSK 2021] Domains have changed.[Пт мая  7 13:40:11 MSK 2021] Using CA: https://acme-v02.api.letsencrypt.org/directory[Пт мая  7 13:40:11 MSK 2021] Multi domain='DNS:*.itdog.info,DNS:itdog.info'[Пт мая  7 13:40:11 MSK 2021] Getting domain auth token for each domain[Пт мая  7 13:40:15 MSK 2021] Getting webroot for domain='*.itdog.info'[Пт мая  7 13:40:15 MSK 2021] Getting webroot for domain='itdog.info'[Пт мая  7 13:40:15 MSK 2021] Adding txt value: Zlrij9n4y5QXfH6yx_PBn45bgmIcT70-JuW2rIUa6lc for domain:  _acme-challenge.tech-domain.club[Пт мая  7 13:40:16 MSK 2021] Adding record[Пт мая  7 13:40:17 MSK 2021] Record added, OK[Пт мая  7 13:40:20 MSK 2021] The txt record is added: Success.[Пт мая  7 13:40:20 MSK 2021] Let's check each DNS record now. Sleep 20 seconds first.[Пт мая  7 13:40:41 MSK 2021] You can use '--dnssleep' to disable public dns checks.[Пт мая  7 13:40:41 MSK 2021] See: https://github.com/acmesh-official/acme.sh/wiki/dnscheck[Пт мая  7 13:40:41 MSK 2021] Checking itdog.info for _acme-challenge.tech-domain.club[Пт мая  7 13:40:42 MSK 2021] Domain itdog.info '_acme-challenge.tech-domain.club' success.[Пт мая  7 13:40:42 MSK 2021] All success, let's return[Пт мая  7 13:40:42 MSK 2021] *.itdog.info is already verified, skip dns-01.[Пт мая  7 13:40:42 MSK 2021] Verifying: itdog.info[Пт мая  7 13:40:46 MSK 2021] Success[Пт мая  7 13:40:46 MSK 2021] Removing DNS records.[Пт мая  7 13:40:46 MSK 2021] Removing txt: Zlrij9n4y5QXfH6yx_PBn45bgmIcT70-JuW2rIUa6lc for domain: _acme-challenge.tech-domain.club[Пт мая  7 13:40:50 MSK 2021] Record deleted[Пт мая  7 13:40:50 MSK 2021] Removed: Success[Пт мая  7 13:40:50 MSK 2021] Verify finished, start to sign.[Пт мая  7 13:40:50 MSK 2021] Lets finalize the order.[Пт мая  7 13:40:50 MSK 2021] Le_OrderFinalize='https://acme-v02.api.letsencrypt.org/acme/finalize/121...'[Пт мая  7 13:40:52 MSK 2021] Downloading cert.[Пт мая  7 13:40:52 MSK 2021] Le_LinkCert='https://acme-v02.api.letsencrypt.org/acme/cert/04e...'[Пт мая  7 13:40:53 MSK 2021] Cert success.-----BEGIN CERTIFICATE-----certificate-----END CERTIFICATE-----[Пт мая  7 13:40:53 MSK 2021] Your cert is in  /home/koala/.acme.sh/*.itdog.info/*.itdog.info.cer [Пт мая  7 13:40:53 MSK 2021] Your cert key is in  /home/koala/.acme.sh/*.itdog.info/*.itdog.info.key [Пт мая  7 13:40:53 MSK 2021] The intermediate CA cert is in  /home/koala/.acme.sh/*.itdog.info/ca.cer [Пт мая  7 13:40:53 MSK 2021] And the full chain certs is there:  /home/koala/.acme.sh/*.itdog.info/fullchain.cer 

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

Кстати, если вам нужно в одном файле иметь несколько сертификатов, например и itdog.info и wildcard *.itdog.info, то просто перечислите их с -d, например

acme.sh --issue --challenge-alias tech-domain.club --dns hetzner -d *.itdog.info -d itdog.info

Это правило действует и для других методов.

И так, что даёт нам этот режим:

  • Если у вашего провайдера нет API или лень писать скрипт, возьмите технический домен, делегируйте его на сервис, который поддерживает acme.sh

  • Если у вашего провайдера нет настройки прав для доступа через API, то технический домен тоже выручает. В случае, если наш token утечёт, у злоумышленника будет доступ только к вашему техническому домену, и если вы его используете только для acme.sh, то максимум что сможет сделать злоумышленник - получить ключ и сертификат для вашего домена. Это тоже неприятно и можно использовать, но это совершенно другой уровень угрозы, по сравнению с полным доступом к доменной зоне

  • В ситуации с кучей доменов на одном или нескольких провайдерах, жизнь так же становится проще, когда все они просто имеют CNAME запись

Есть так же режим domain-alias, он даёт возможность использовать не _acme-challenge запись, а кастомную, подробности можно прочитать в документации

Автоматизация получения и распространения сертификатов

Мы получили сертификаты, лежат они у нас красиво в ~/.acme.sh и никак не используются. Надо каким-то образом их распространять на сервера. Далее расскажу, как я это делаю с помощью ansible. Ansible используется и для получения\обновления и для распространения. Сразу предупреждаю, мои плейбуки простые как три копейки и заточены под определенную инфраструктуру. Playbooks, hosts на github.

Мой сервер с ansible, уже имеет доступ ко всем необходимым серверам, на нём установлен acme.sh и реализовано два плейбука, на получение и распространение. Кстати, не забудьте закомментировать acme.sh в crontab, что бы не было лишних запросов и путаницы.

Playbook для получения сертификатов

В vars указывается только технический домен, эта переменная используется несколько раз. Токен от API вынесен в отдельный vars файл, что бы хранить его в зашифрованном виде в git. Task "Date and time" нужен для логирования, что бы понимать когда именно что-то пошло не так. Следующие два плейбука это простой shell, отличаются друг от друга количеством доменов в одном файле сертификата. Всем доменам, которым не нужно сочетать в себе обычный и wildcard домен, идут списком в loop.

Домены, которые должны подходить как для обычного, так и для wildcard идут по втором taks, тоже с помощью loop. Если вам нужно например wilcard вида *.*.itdog.info, то просто добавьте ещё один -d и ещё один subkey в item. Опция ignore_errors необходима, потому что exit code 0 будет только 6 раз за год при обновлении сертификата, в остальное время будут сообщения о том, что сертификат не нужно обновлять, для ansible это ошибка на которой он будет останавливаться.

Для чего плейбук на получение? Ведь в acme.sh и так уже всё настроено!

В одном плейбуке мы собираем всю нашу конфигурацию, доступы и все домены, которым необходим TLS, как минимум, это удобно - не надо копаться конфигах acme.sh. В случае изменения, например, токена, мы просто редактируем его в vars_files, а если нужно добавить ещё один домен\подомен, мы просто добавляем его в loop. Ну и в случае переноса сервера, не нужно переносить ~/.acme.sh, только плейбуки с vars_files взять из git.

Playbook для распространения сертификатов

Здесь нужно писать конечно под вашу инфраструктуру, поэтому повторюсь, показываю это для примера.

Три типа серверов из моей инфраструктуры:

  • tls-hosts - Обычный nginx установленный как пакет из стандартного репозитория

  • tls-hosts-docker - Веб проект с тем же nginx, но уже в docker

  • tls-hosts-docker-rename - Сторонний продукт, в который надо подкладывать сертификат и ключ с определённым именем в определённую директорию (например Harbor, Zabbix)

Первый кейс самый простой, мы сами пишем конфигурацию nginx и можем куда угодно положить сертификаты и как угодно назвать их. При обновлении сертификата, требуется сделать nginx -s reload

Во втором случае всё плюс-минус так же, но уже нужно сделать docker exec project-nginx -s reolad, т.е. уже другой handler.

В третьем случае у нас помимо handler в контейнере, ещё нужно дать сертификату с ключом определённое имя, потому что оно прописано в их конфигурации, которую лучше не трогать, чтоб не было проблем при обновлении.

В моём случае доменов много, пути, по которым сертификаты с ключами хранятся, различаются. Так же есть случаи когда у одного сервера несколько доменов. Что бы была возможность настроить для каждого хоста свой путь и необходимый домен, в hosts для каждого хоста заданы переменные пути и домена.

nginx.itdog.info tls_path=/etc/letsencrypt/*.itdog.info/ DOMAIN=*.itdog.info

Для случаев, когда доменов на сервере несколько, делается два хоста с разными именами и одинаковым ansible_host (Совет, как сделать лучше, приветствуется).

nginx.example.com-1 ansible_host=nginx.example.com tls_path=/etc/letsencrypt/*.example.com/ DOMAIN=example.comnginx.example.com-2 ansible_host=nginx.example.com tls_path=/etc/letsencrypt/*.example.org/ DOMAIN=example.org

Для каждого типа серверов создана своя группа в hosts. Для каждой группы свои немного отличающиеся друг от друга tasks. Для tls-hosts-docker так же добавлена переменная с именем контейнера nginx. А для tls-hosts-docker-rename добавлена переменная, в которой задаётся конечное имя сертификата и ключа.

docker-zabbix.itdog.info tls_path=/root/docker-zabbix/zbx_env/etc/ssl/nginx/ DOMAIN=*.itdog.info CONTAINER=docker-zabbix_zabbix-web-nginx-pgsql_1 cert_name=ssl.crt key_name=ssl.key

Для nginx нужен fullchain и domain.key - копируются только они. Если файлы различаются, происходит копирование и срабатывает handler nginx -s reload. Так же есть проверка, перед тем как зарелоудить nginx, что это файл. У меня один раз был случай, в самом начале пользования acme.sh, скрипт вместо файла с сертификатом создал директорию. Прямо как traefik 1.7 создаёт acme.json директорию, вместо файла. Поэтому я сделал простую проверку. В идеале нужно делать проверку, что сертификат валидный и не просроченный, но для этого требуется иметь на каждом хосте python-pyOpenSSL.

Crontab

23 3 * * * /usr/bin/ansible-playbook /etc/ansible/playbook-get-tls.yml -v >> /var/log/get-tls.log23 4 * * * /usr/bin/ansible-playbook /etc/ansible/playbook-copy-tls.yml -v >> /var/log/copy-tls.log

Можно без проблем вызывать их каждый день, let's encrypt будет вежливо говорить, что пока не нужно обновляться. А когда придёт срок, сертификаты будут обновлены.

Мониторинг сертификатов

Надо следить за тем, что бы сертификаты на доменах обновлялись. Можно мониторить логи получения и распространения, и когда что-то идёт не так - кидать warning в системе мониторинга. Но мы пойдём с одной стороны на минималках, с другой более основательно и ещё помимо этого захватим другие исталяции, например с traefik, который живёт там сам по себе.

Я использую zabbix и скрипт от @selivanov_pavel

Проверим с его помощью мой домен локально

koala@x220 ~/t/acme.sh-test> ./ssl_cert_check.sh expire itdog.info 44341

41 день сертификат на itdog.info будет актуален. Сертификат в let's encrypt обновляется за 30 дней до протухания. А значит, например, если ему осталось жить 10 дней, значит что-то пошло не так и надо идти смотреть.

Темплейт состоит из одного item и одного trigger. Теплейт есть так же на github

ssl_cert_check.sh["expire","{HOST.NAME}","{$TLS_PORT}"]

В item две переменных, первая HOST.NAME берёт имя хоста, предполагается что у нас хост назван по доменному имени. Переменная $TLS_PORT по дефолту 443, но если нужно проверять на нестандартном порту, то записываем значение порта в macros.

Триггер тоже супер простой

{Check tls expire:ssl_cert_check.sh["expire","{HOST.NAME}","{$TLS_PORT}"].last()}<=10

Если полученное значение меньше 10ти - аллерт. Таким образом, мы узнаем если у нас начнут протухать сертификаты и будет 10 дней на починку.

Нормально работает?

Да, acme.sh + DNS API + ansible у меня крутится два года. acme.sh + DNS Alias + ansible крутится пол года. Проблемы возникали только, когда при тестировании доменов забыл отключить crontab и он принёс staging сертификат на прод. Такая проблема решается проверкой на валидность.

Да, в идеале, ansible должен проверять перед копированием, что сертификат валидный и не просроченный. А система мониторинга проверять, помимо expire, валидность сертификатов.

Подробнее..

Развертывание ML модели в Docker с использованием Flask (REST API) масштабирование нагрузки через Nginx балансер

27.03.2021 18:23:22 | Автор: admin

Как известно настройка и обучение моделей машинного обучения это только одна из частей цикла разработки, не менее важной частью является развертывание модели для её дальнейшего использования. В этой статье я расскажу о том, как модель машинного обучения может быть развернута в виде Docker микросервиса, а также о том, как можно распараллелить работу микросервиса с помощью распределения нагрузки в несколько потоков через Load balancer. В последнее время Docker набрал большую популярность, однако здесь будет описан только один из видов стратегий развертывания моделей, и в каждом конкретном случае выбор лучшего варианта остаётся за разработчиком.



Гитхаб репозиторий с исходным кодом: https://github.com/cdies/ML_microservice


Введение


Для этого примера я использовал распространённый набор данных MNIST. Конечная ML модель будет развернута в Docker контейнере, доступ к которой будет организован через HTTP протокол посредствам POST запроса (архитектурный стиль REST API). Полученный таким образом микросервис будет распараллелен через балансировщик на базе Nginx.


Веб фреймворк Flask уже содержит в себе веб-сервер, однако, он используется строго for dev purpose only, т.е. только для разработки, вследствие этого я воспользовался веб-сервером Gunicorn для предоставления нашего REST API.


Описание ML модели


Как уже было отмечено выше, для построения ML модели я использовал, наверное, один из самых известных наборов данных MNIST, тут в принципе показан стандартный пайплайн: загрузка и обработка данных -> обучение модели на нейронной сети Keras -> сохранение модели для повторного использования. Исходный код в файле mnist.py


from tensorflow.keras.layers import Dense, Conv2D, MaxPooling2D, Flatten, Dropoutfrom tensorflow.keras.models import Sequentialfrom tensorflow import kerasfrom tensorflow.keras.datasets import mnist(x_train, y_train), (x_test, y_test) = mnist.load_data()x_train = x_train.reshape((60000,28,28,1)).astype('float32')/255x_test = x_test.reshape((10000,28,28,1)).astype('float32')/255y_train = keras.utils.to_categorical(y_train, 10)y_test = keras.utils.to_categorical(y_test, 10)model = Sequential()model.add(Conv2D(32, (3,3), activation='relu', input_shape=(28,28,1)))model.add(Conv2D(64, (3,3), activation='relu'))model.add(MaxPooling2D(pool_size=(2,2)))model.add(Dropout(0.25))model.add(Flatten())model.add(Dense(128, activation='relu'))model.add(Dropout(0.5))model.add(Dense(10, activation='softmax'))model.compile(loss=keras.losses.categorical_crossentropy,     optimizer=keras.optimizers.Adadelta(), metrics=['accuracy'])model.fit(x_train, y_train, batch_size=128,     epochs=12, verbose=1, validation_data=(x_test, y_test))score = model.evaluate(x_test, y_test)print(score)# Save modelmodel.save('mnist-microservice/model.h5')

Построение HTTP REST API


Сохранённую ML модель я буду использовать для создания простого REST API микросервиса, для этого воспользуюсь веб-фреймворком Flask. Микросервис будет принимать изображение цифры, приводить его к виду, подходящему для использования в нейронной сети, которую мы сохранили в файле model.h5 и возвращать распознанное значение и его вероятность. Исходный код в файле mnist_recognizer.py


from flask import Flask, jsonify, requestfrom tensorflow import kerasimport numpy as npfrom flask_cors import CORSimport imageapp = Flask(__name__)# Cross Origin Resource Sharing (CORS) handlingCORS(app, resources={'/image': {"origins": "http://localhost:8080"}})@app.route('/image', methods=['POST'])def image_post_request():      model = keras.models.load_model('model.h5')    x = image.convert(request.json['image'])    y = model.predict(x.reshape((1,28,28,1))).reshape((10,))    n = int(np.argmax(y, axis=0))    y = [float(i) for i in y]    return jsonify({'result':y, 'digit':n})if __name__ == "__main__":    app.run(host='0.0.0.0', port=5000)

Docker файл микросервиса


В Dockerfile файле микросервиса содержатся все необходимые зависимости, код и сохраненная модель.


FROM python:3.7RUN python -m pip install flask flask-cors gunicorn numpy tensorflow pillowWORKDIR /appADD image.py image.pyADD mnist_recognizer.py mnist_recognizer.pyADD model.h5 model.h5EXPOSE 5000CMD [ "gunicorn", "--bind", "0.0.0.0:5000", "mnist_recognizer:app" ]

В таком виде микросервис уже можно использовать в однопоточном режиме, для этого нужно выполнить следующие команды в папке mnist-microservice:


docker build -t mnist_microservice_test .

docker run -d -p 5000:5000 mnist_microservice_test

Nginx балансер


Тут сразу стоит уточнить, что, в принципе распараллелить процесс можно было бы с помощью Gunicorn веб-сервера, в частности добавить в Dockerfile в строку запуска Gunicorn веб-сервера --workers n, чтобы было n процессов. Однако я исходил из того, что в Docker контейнере не нужно плодить процессы, кроме необходимых, поэтому решил разделить процессы по контейнерам (одна ML модель один контейнер), а не сваливать все процессы в один контейнер. (Пишите в комментах, как бы сделали вы)


Чтобы балансировщик нагрузки работал, нужно, чтобы Nginx перенаправлял запросы на порт 5000, который слушает наш микросервис. Исходный код в файле nginx.conf


user  nginx;events {    worker_connections   1000;}http {        server {              listen 4000;              location / {                proxy_pass http://mnist-microservice:5000;              }        }}

В заключительном docker-compose.yml файле я распараллелил созданный ранее микросервис, который назвал здесь mnist-microservice с помощью параметра replicas.


version: '3.7'services:    mnist-microservice:        build:            context: ./mnist-microservice        image: mnist-microservice        restart: unless-stopped        expose:            - "5000"        deploy:            replicas: 3    nginx-balancer:        image: nginx        container_name: nginx-balancer        restart: unless-stopped        volumes:            - ./nginx-balancer/nginx.conf:/etc/nginx/nginx.conf:ro        depends_on:            - mnist-microservice        ports:            - "5000:4000"    nginx-html:        image: nginx        container_name: nginx-html        restart: unless-stopped        volumes:            - ./html:/usr/share/nginx/html:ro        depends_on:            - nginx-balancer        ports:            - "8080:80"

Как видно, микросервис также продолжает слушать порт 5000 внутри виртуальной сети докера, в то же самое время nginx-balancer перенаправляет трафик от порта 4000 к порту 5000 также внутри виртуальной сети докера, а уже порт 5000 внешней сети я пробросил на 4000 внутренний порт nginx-balancer. Таким образом, для внешнего веб-сервера nginx-html ничего не поменялось, также не пришлось менять исходный код микросервиса.



Чтобы всё запустить, необходимо выполнить в корневой папке проекта:


docker-compose up --build

Для проверки работы микросервиса можно открыть адрес http://localhost:8080/ в браузере и начать посылать в него цифры нарисованные мышкой, в результате должно получиться что-то вроде этого:



Выводы


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


Полезные ссылки:
https://medium.com/swlh/machine-learning-model-deployment-in-docker-using-flask-d77f6cb551d6
https://medium.com/@vinodkrane/microservices-scaling-and-load-balancing-using-docker-compose-78bf8dc04da9
https://github.com/deadfrominside/keras-flask-app

Подробнее..

Развертывание приложений Django

11.04.2021 20:17:45 | Автор: admin

Введение

После того, как мы закончили разработку веб-приложения, оно должно быть размещено на хосте, чтобы общественность могла получить доступ к нему из любого места. Мы посмотрим, как развернуть и разместить приложение на экземпляре AWS EC2, используя Nginx в качестве веб-сервера и Gunicorn в качестве WSGI.

AWS EC2

Amazon Elastic Compute Cloud (Amazon EC2) - это веб-сервис, обеспечивающий масштабируемость вычислительных мощностей в облаке. Мы устанавливаем и размещаем наши веб-приложения на экземпляре EC2 после выбора AMI (OS) по нашему усмотрению. Подробнее об этом мы поговорим в следующих разделах.

NGINX

Nginx - это веб-сервер с открытым исходным кодом. Мы будем использовать Nginx для сервера наших веб-страниц по мере необходимости.

GUNICORN

Gunicorn - это серверная реализация интерфейса шлюза Web Server Gateway Interface (WSGI), который обычно используется для запуска веб-приложений Python.

WSGI - используется для переадресации запроса с веб-сервера на Python бэкэнд.

Мы не будем использовать сервер, который поставляется с django по умолчанию в производстве.

Развертывание приложения

Мы запустим EC2 экземпляр на AWS, для этого войдите в консоль aws.

  • Выберите EC2 из всех сервисов

  • Выберите запуск New instance и выберите Ubuntu из списка.

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

  • Теперь настройте группы безопасности и откройте порты 8000 и 9000, так как мы будем использовать эти порты . Просмотрите и запустите ваш экземпляр, может потребоваться некоторое время, чтобы он запустился.

Подключение к Экземпляру

Мы можем подключиться к экземпляру, используя опцию 'connect' в консоли (или с помощью putty или любого другого подобного инструмента ). После подключения запустите следующие команды

sudo apt-get update

Установите python , pip и django

sudo apt install pythonsudo apt install python3-pippip3 install django

Теперь, когда мы установили наши зависимости, мы можем создать папку, в которую мы скопируем наше приложение django.

cd  /home/ubuntu/  mkdir Projectcd Projectmkdir ProjectNamecd ProjectName

Теперь мы поместим наш код по следующему пути.
/home/ubuntu/Project/ProjectName

GitHub

Убедитесь, что ваш код находится в репозитории, чтобы мы могли легко втянуть его в наш экземпляр ec2.

  • Перейдите в только что созданную папку (/home/ubuntu/Project/ProjectName/)

  • git clone <repository-url>

Это клонирует репозиторий в папку, и в следующий раз мы сможем просто вытащить изменения с помощью git pull.

Settings.py Файл.

Мы должны внести некоторые изменения в settings.py в нашем проекте.

  • Вставьте свои секретные ключи и пароли в переменные окружения

  • Установить Debug = False

  • Добавте Ваш домейн в ALLOWED_HOSTS

BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))STATIC_ROOT = os.path.join(BASE_DIR, static)

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

manage.py makemigrationsmanage.py migratemanage.py collectstatic

Установка Nginx

Для установки Nginx выполните команду

 sudo apt install nginx

Есть конфигурационный файл с именем по умолчанию в /etc/nginx/sites-enabled/, который имеет базовую настройку для NGINX, мы отредактируем этот файл.

sudo vi default

Файл будет выглядеть так после добавления необходимой конфигурации, а остальная часть файла останется такой же.

мы добавим proxy_pass http://0.0.0.0:9000 и укажем путь к нашей статической папке, добавив путь внутри каталога /static/, как указано выше. Убедитесь, что вы собрали все статические файлы в общую папку, запустив команду

manage.py collectstatic

Теперь запустите сервер nginx

sudo service nginx start             #to start nginxsudo service nginx stop              #to stop nginxsudo service nginx restart           #to restart nginx

Установка Gunicorn

pip install gunicorn

Убедитесь, что Вы находитесь в папке проекта, например: /home/ubuntu/Project, и запустите следующую команду, чтобы запустить gunicorn

gunicorn ProjectName.wsgi:application- -bind 0.0.0.0:9000

Теперь, когда мы установили и настроили nginx и gunicorn, к нашему приложению можно получить доступ через DNS экземпляра ec2.

Подробнее..
Категории: Python , Nginx , Python3 , Django , Aws , Gunicorn

Продолжаем знакомство с APIM Gravitee

27.05.2021 20:23:24 | Автор: admin

Всем привет! Меня всё ещё зовут Антон. В предыдущейстатьея провел небольшой обзор APIM Gravitee и в целом систем типа API Management. В этой статье я расскажу,как поднять ознакомительный стенд APIM Gravitee (https://www.gravitee.io), рассмотрим архитектуру системы, содержимое docker compose file, добавим некоторые параметры, запустим APIM Gravitee и сделаем первую API. Статья немного погружает в технические аспекты и может быть полезна администраторам и инженерам, чтобы начать разбираться в системе.

Архитектура

Для ознакомительного стенда будем использовать простейшую архитектуру

Все в докере, в том числе и MongoDB и Elasticsearch. Чтобы сделать полноценную среду для тестирования крайне желательно компоненты MongoDB и Elasticsearch вынести за пределы Docker. Также для простоты манипулирования настройками можно вынести конфигурационные файлы Gateway и Management API: logback.xml и gravitee.yml.

docker-compose.yml

Среду для начальных шагов будем поднимать, используяdocker-compose file, предоставленный разработчиками наgithub. Правда,внесемнесколькокорректив.

docker-compose.yml# Copyright (C) 2015 The Gravitee team (<http://gravitee.io>)## Licensed under the Apache License, Version 2.0 (the "License");# you may not use this file except in compliance with the License.# You may obtain a copy of the License at##<http://www.apache.org/licenses/LICENSE-2.0>## Unless required by applicable law or agreed to in writing, software# distributed under the License is distributed on an "AS IS" BASIS,# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.# See the License for the specific language governing permissions and# limitations under the License.#version: '3.5'networks:frontend:name: frontendstorage:name: storagevolumes:data-elasticsearch:data-mongo:services:mongodb:image: mongo:${MONGODB_VERSION:-3.6}container_name: gio_apim_mongodbrestart: alwaysvolumes:- data-mongo:/data/db- ./logs/apim-mongodb:/var/log/mongodbnetworks:- storageelasticsearch:image: docker.elastic.co/elasticsearch/elasticsearch:${ELASTIC_VERSION:-7.7.0}container_name: gio_apim_elasticsearchrestart: alwaysvolumes:- data-elasticsearch:/usr/share/elasticsearch/dataenvironment:- http.host=0.0.0.0- transport.host=0.0.0.0- xpack.security.enabled=false- xpack.monitoring.enabled=false- cluster.name=elasticsearch- bootstrap.memory_lock=true- discovery.type=single-node- "ES_JAVA_OPTS=-Xms512m -Xmx512m"ulimits:memlock:soft: -1hard: -1nofile: 65536networks:- storagegateway:image: graviteeio/apim-gateway:${APIM_VERSION:-3}container_name: gio_apim_gatewayrestart: alwaysports:- "8082:8082"depends_on:- mongodb- elasticsearchvolumes:- ./logs/apim-gateway:/opt/graviteeio-gateway/logsenvironment:- gravitee_management_mongodb_uri=mongodb://mongodb:27017/gravitee?serverSelectionTimeoutMS=5000&connectTimeoutMS=5000&socketTimeoutMS=5000- gravitee_ratelimit_mongodb_uri=mongodb://mongodb:27017/gravitee?serverSelectionTimeoutMS=5000&connectTimeoutMS=5000&socketTimeoutMS=5000- gravitee_reporters_elasticsearch_endpoints_0=http://elasticsearch:9200networks:- storage- frontendmanagement_api:image: graviteeio/apim-management-api:${APIM_VERSION:-3}container_name: gio_apim_management_apirestart: alwaysports:- "8083:8083"links:- mongodb- elasticsearchdepends_on:- mongodb- elasticsearchvolumes:- ./logs/apim-management-api:/opt/graviteeio-management-api/logsenvironment:- gravitee_management_mongodb_uri=mongodb://mongodb:27017/gravitee?serverSelectionTimeoutMS=5000&connectTimeoutMS=5000&socketTimeoutMS=5000- gravitee_analytics_elasticsearch_endpoints_0=http://elasticsearch:9200networks:- storage- frontendmanagement_ui:image: graviteeio/apim-management-ui:${APIM_VERSION:-3}container_name: gio_apim_management_uirestart: alwaysports:- "8084:8080"depends_on:- management_apienvironment:- MGMT_API_URL=http://localhost:8083/management/organizations/DEFAULT/environments/DEFAULT/volumes:- ./logs/apim-management-ui:/var/log/nginxnetworks:- frontendportal_ui:image: graviteeio/apim-portal-ui:${APIM_VERSION:-3}container_name: gio_apim_portal_uirestart: alwaysports:- "8085:8080"depends_on:- management_apienvironment:- PORTAL_API_URL=http://localhost:8083/portal/environments/DEFAULTvolumes:- ./logs/apim-portal-ui:/var/log/nginxnetworks:- frontend

Первичные системы, на основе которых строится весь остальной сервис:<o:p>

  1. MongoDB - хранение настроек системы, API, Application, групп, пользователей и журнала аудита.

  2. Elasticsearch(Open Distro for Elasticsearch) - хранение логов, метрик, данных мониторинга.

MongoDB

docker-compose.yml:mongodb

mongodb:image: mongo:${MONGODB_VERSION:-3.6}<o:p>container_name: gio_apim_mongodb<o:p>restart: always<o:p>volumes:<o:p>- data-mongo:/data/db<o:p>- ./logs/apim-mongodb:/var/log/mongodb<o:p>networks:<o:p>- storage<o:p>

С MongoDB всё просто поднимается единственный экземпляр версии 3.6, если не указано иное, с volume для логов самой MongoDB и для данных в MongoDB.<o:p>

Elasticsearch

docker-compose.yml:elasticsearch

elasticsearch:image: docker.elastic.co/elasticsearch/elasticsearch:${ELASTIC_VERSION:-7.7.0}<o:p>container_name: gio_apim_elasticsearchrestart: alwaysvolumes:- data-elasticsearch:/usr/share/elasticsearch/dataenvironment:- http.host=0.0.0.0- transport.host=0.0.0.0<o:p>- xpack.security.enabled=false- xpack.monitoring.enabled=false- cluster.name=elasticsearch- bootstrap.memory_lock=true- discovery.type=single-node- "ES_JAVA_OPTS=-Xms512m -Xmx512m"ulimitsmemlock:soft: -1hard: -1nofile: 65536networks:- storage

elasticsearch:

elasticsearch:image: docker.elastic.co/elasticsearch/elasticsearch:${ELASTIC_VERSION:-7.7.0}container_name: gio_apim_elasticsearchrestart: alwaysvolumes:- data-elasticsearch:/usr/share/elasticsearch/data<o:p>environment:- http.host=0.0.0.0- transport.host=0.0.0.0- xpack.security.enabled=false- xpack.monitoring.enabled=false- cluster.name=elasticsearch- bootstrap.memory_lock=true- discovery.type=single-node- "ES_JAVA_OPTS=-Xms512m -Xmx512m"ulimits:memlock:soft: -1hard: -1nofile: 65536networks:- storage

С Elasticsearch также всё просто поднимается единственный экземпляр версии 7.7.0, если не указано иное, с volume для данных в Elasticsearch. Сразу стоит убрать строки xpack.security.enabled=false и xpack.monitoring.enabled=false, так как хоть они и указаны как false, Elasticsearch пытается найти XPack и падает. Исправили ли этот баг в новых версиях не понятно, так что просто убираем их, или комментируем. Также стоит обратить внимание на секцию ulimits, так как она требуется для нормальной работы Elasticsearch в docker.<o:p>

Дальше уже поднимаются компоненты сервиса:

  1. Gateway

  2. Management API

  3. Management UI

  4. Portal UI

Gateway/APIM Gateway

docker-compose.yml:gatewaygateway:image: graviteeio/apim-gateway:${APIM_VERSION:-3}container_name: gio_apim_gatewayrestart: alwaysports:- "8082:8082"depends_on:- mongodb- elasticsearchvolumes:- ./logs/apim-gateway:/opt/graviteeio-gateway/logs      environment:    - gravitee_management_mongodb_uri=mongodb://mongodb:27017/gravitee?serverSelectionTimeoutMS=5000&connectTimeoutMS=5000&socketTimeoutMS=5000      - gravitee_ratelimit_mongodb_uri=mongodb://mongodb:27017/gravitee?serverSelectionTimeoutMS=5000&connectTimeoutMS=5000&socketTimeoutMS=5000- gravitee_reporters_elasticsearch_endpoints_0=http://elasticsearch:9200networks:- storage- frontend<o:p>

С Gateway всё несколько сложнее поднимается единственный экземпляр версии 3, если не указано иное. Если мы посмотрим, что лежит наhub.docker.com, то увидим, что у версий 3 и latest совпадают хеши. Дальше мы видим, что запуск данного сервиса, зависит от того, как будут работать сервисы MongoDB иElasticsearch. Самое интересное, что если Gateway запустился и забрал данные по настроенным API из mongodb, то дальше связь с mongodb и elasticsearch не обязательна. Только в логи будут ошибки сыпаться, но сам сервис будет работать и соединения обрабатывать согласно той версии настроек, которую последний раз закачал в себя Gateway. В секции environment можно указать параметры, которые будут использоваться в работе самого Gateway, для переписывания данных из главного файла настроек: gravitee.yml. Как вариант можно поставить теги, тенанты для разграничения пространств, если используется Open Distro for Elasticsearch вместо ванильного Elasticsearch. Например, так мы можем добавить теги, тенанты и включить подсистему вывода данных о работе шлюза.

environment:- gravitee_tags=service-tag #включаемтег: service-tag- gravitee_tenant=service-space #включаемтенант: service-space- gravitee_services_core_http_enabled=true # включаем сервис выдачи данных по работе Gateway  - gravitee_services_core_http_port=18082 # порт сервиса- gravitee_services_core_http_host=0.0.0.0 # адрес сервиса- gravitee_services_core_http_authentication_type=basic # аутентификация либо нет, либо basic - логин + пароль  - gravitee_services_core_http_authentication_type_users_admin=password #логин: admin,пароль: password  Чтобы к подсистеме мониторинга был доступ из вне, надо ещё открыть порт 18082.ports:- "18082:18082"Management API/APIM APIdocker-compose.yml:management_apimanagement_api:image: graviteeio/apim-management-api:${APIM_VERSION:-3}container_name: gio_apim_management_apirestart: alwaysports:- "8083:8083"links:- mongodb- elasticsearchdepends_on:- mongodb- elasticsearchvolumes:- ./logs/apim-management-api:/opt/graviteeio-management-api/logsenvironment:- gravitee_management_mongodb_uri=mongodb://mongodb:27017/gravitee?serverSelectionTimeoutMS=5000&connectTimeoutMS=5000&socketTimeoutMS=5000  - gravitee_analytics_elasticsearch_endpoints_0=http://elasticsearch:9200 networks:    - storage- frontend

ManagementAPI это ядро всей системы и предоставляет службы для управления и настройкиAPI, логи, аналитики и веб-интерфейсовManagementUIиPortalUI. Зависит от MongoDB и Elasticsearch. Также можно через секцию environment указать параметры, которые будут использоваться в работе самого ядра системы. Дополним наши настройки:

environment:- gravitee_email_enable=true # включаем возможность отправлять письма- gravitee_email_host=smtp.domain.example # указываем сервер через который будем отправлять письма- gravitee_email_port=25 # указываем порт для сервера- gravitee_email_username=domain.example/gravitee #логиндлясервера- gravitee_email_password=password #парольдлялогинаотсервера  - gravitee_email_from=noreply@domain.example # указываем от чьего имени будут письма- gravitee_email_subject="[Gravitee.io] %s" #указываемтемуписьма

Management UI/APIM Console

docker-compose.yml:apim_consolemanagement_ui:image: graviteeio/apim-management-ui:${APIM_VERSION:-3}container_name: gio_apim_management_uirestart: alwaysports:- "8084:8080"depends_on:- management_apienvironment:- MGMT_API_URL=http://localhost:8083/management/organizations/DEFAULT/environments/DEFAULT/      volumes:    - ./logs/apim-management-ui:/var/log/nginxnetworks:- frontend

Management UI предоставляет интерфейс для работы администраторам и разработчикам. Все основные функции можно осуществлять и выполняя запросы непосредственно к REST API. По опыту могу сказать, что в переменной MGMT_API_URL вместоlocalhostнадо указать IP адрес или название сервера, где вы это поднимаете, иначе контейнер не найдет Management API.

Portal UI/APIM Portaldocker-compose.yml:apim_portalportal_ui:image: graviteeio/apim-portal-ui:${APIM_VERSION:-3}container_name: gio_apim_portal_uirestart: alwaysports:- "8085:8080"depends_on:- management_apienvironment:- PORTAL_API_URL=http://localhost:8083/portal/environments/DEFAULTvolumes:- ./logs/apim-portal-ui:/var/log/nginxnetworks:- frontend

Portal UI это портал для менеджеров. Предоставляет доступ к логам, метрикам и документации по опубликованным API. По опыту могу сказать, что в переменной PORTAL_API_URL вместоlocalhostнадо указать IP-адрес или название сервера, где вы это поднимаете, иначе контейнер не найдет Management API.<o:p>

Теперь соберем весь файл вместе.

docker-compose.yml

# Copyright (C) 2015 The Gravitee team (<http://gravitee.io>)## Licensed under the Apache License, Version 2.0 (the "License");# you may not use this file except in compliance with the License.# You may obtain a copy of the License at##         <http://www.apache.org/licenses/LICENSE-2.0>## Unless required by applicable law or agreed to in writing, software# distributed under the License is distributed on an "AS IS" BASIS,# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.# See the License for the specific language governing permissions and# limitations under the License.#version: '3.5'networks:  frontend:    name: frontend  storage:    name: storagevolumes:  data-elasticsearch:  data-mongo:services:  mongodb:    image: mongo:${MONGODB_VERSION:-3.6}    container_name: gio_apim_mongodb    restart: always    volumes:      - data-mongo:/data/db      - ./logs/apim-mongodb:/var/log/mongodb    networks:      - storage  elasticsearch:    image: docker.elastic.co/elasticsearch/elasticsearch:${ELASTIC_VERSION:-7.7.0}    container_name: gio_apim_elasticsearch    restart: always    volumes:      - data-elasticsearch:/usr/share/elasticsearch/data    environment:      - http.host=0.0.0.0      - transport.host=0.0.0.0      - cluster.name=elasticsearch      - bootstrap.memory_lock=true      - discovery.type=single-node      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"    ulimits:      memlock:        soft: -1        hard: -1      nofile: 65536    networks:      - storage  gateway:    image: graviteeio/apim-gateway:${APIM_VERSION:-3}    container_name: gio_apim_gateway    restart: always    ports:      - "8082:8082"      - "18082:18082"    depends_on:      - mongodb      - elasticsearch    volumes:      - ./logs/apim-gateway:/opt/graviteeio-gateway/logs    environment:      - gravitee_management_mongodb_uri=mongodb://mongodb:27017/gravitee?serverSelectionTimeoutMS=5000&connectTimeoutMS=5000&socketTimeoutMS=5000      - gravitee_ratelimit_mongodb_uri=mongodb://mongodb:27017/gravitee?serverSelectionTimeoutMS=5000&connectTimeoutMS=5000&socketTimeoutMS=5000      - gravitee_reporters_elasticsearch_endpoints_0=http://elasticsearch:9200         - gravitee_tags=service-tag # включаем тег: service-tag         - gravitee_tenant=service-space # включаем тенант: service-space         - gravitee_services_core_http_enabled=true # включаем сервис выдачи данных по работе Gateway         - gravitee_services_core_http_port=18082 # порт сервиса         - gravitee_services_core_http_host=0.0.0.0 # адрес сервиса          - gravitee_services_core_http_authentication_type=basic # аутентификация либо нет, либо basic - логин + пароль         - gravitee_services_core_http_authentication_type_users_admin=password # логин: admin, пароль: password    networks:      - storage      - frontend  management_api:    image: graviteeio/apim-management-api:${APIM_VERSION:-3}    container_name: gio_apim_management_api    restart: always    ports:      - "8083:8083"    links:      - mongodb      - elasticsearch    depends_on:      - mongodb      - elasticsearch    volumes:      - ./logs/apim-management-api:/opt/graviteeio-management-api/logs    environment:      - gravitee_management_mongodb_uri=mongodb://mongodb:27017/gravitee?serverSelectionTimeoutMS=5000&connectTimeoutMS=5000&socketTimeoutMS=5000      - gravitee_analytics_elasticsearch_endpoints_0=http://elasticsearch:9200         - gravitee_email_enable=true # включаем возможность отправлять письма         - gravitee_email_host=smtp.domain.example # указываем сервер через который будем отправлять письма         - gravitee_email_port=25 # указываем порт для сервера         - gravitee_email_username=domain.example/gravitee # логин для сервера         - gravitee_email_password=password # пароль для логина от сервера         - gravitee_email_from=noreply@domain.example # указываем от чьего имени будут письма          - gravitee_email_subject="[Gravitee.io] %s" # указываем тему письма    networks:      - storage      - frontend  management_ui:    image: graviteeio/apim-management-ui:${APIM_VERSION:-3}    container_name: gio_apim_management_ui    restart: always    ports:      - "8084:8080"    depends_on:      - management_api    environment:      - MGMT_API_URL=http://localhost:8083/management/organizations/DEFAULT/environments/DEFAULT/    volumes:      - ./logs/apim-management-ui:/var/log/nginx    networks:      - frontend  portal_ui:    image: graviteeio/apim-portal-ui:${APIM_VERSION:-3}    container_name: gio_apim_portal_ui    restart: always    ports:      - "8085:8080"    depends_on:      - management_api    environment:      - PORTAL_API_URL=http://localhost:8083/portal/environments/DEFAULT    volumes:      - ./logs/apim-portal-ui:/var/log/nginx    networks:      - frontend

Запускаем

Итоговый файл закидываем на сервер с примерно следующими характеристиками:

vCPU: 4

RAM: 4 GB

HDD: 50-100 GB

Для работы Elasticsearch, MongoDB и Gravitee Gateway нужно примерно по 0.5 vCPU, больше только лучше. Примерно тоже самое и с оперативной памятью - RAM. Остальные сервисы по остаточному принципу. Для хранения настроек много места не требуется, но учитывайте, что в MongoDB еще хранятся логи аудита системы. На начальном этапе это будет в пределах 100 MB. Остальное место потребуется для хранения логов в Elasticsearch.

docker-compose up -d # если не хотите видеть логиdocker-compose up # если хотите видеть логи и как это все работает

Как только в логах увидите строки:

gio_apim_management_api_dev | 19:57:12.615 [graviteeio-node] INFO  i.g.r.a.s.node.GraviteeApisNode - Gravitee.io - Rest APIs id[5728f320-ba2b-4a39-a8f3-20ba2bda39ac] version[3.5.3] pid[1] build[23#2f1cec123ad1fae2ef96f1242dddc0846592d222] jvm[AdoptOpenJDK/OpenJDK 64-Bit Server VM/11.0.10+9] started in 31512 ms.

Можно переходить по адресу: http://ваш_адрес:8084/.

Нужно учесть, что Elasticsearch может подниматься несколько дольше, поэтому не пугайтесь если увидите такое "приглашение":

Надо просто ещё немного подождать. Если ошибка не ушла, то надо закапываться в логи и смотреть, что там за ошибки. Видим такое приглашение отлично!

Вводим стандартный логин и пароль: admin/admin и мы в системе!

Первичная настройка

Настройки самой системы

Переходим в менюSettings PORTAL Settings

Здесь можно настроить некоторый параметры системы. Например: доступные методы аутентификации наших клиентов: Keyless, API_KEY, Oauth2 или JWT. Подробно их мы рассмотрим скорее всего в третьей статье, а пока оставим как есть. Можно подключить Google Analytics. Время обновления по задачам и нотификациям. Где лежит документация и много ещё чего по мелочи.

Добавление tags и tenant

ПереходимвменюSettings GATEWAY Shardings Tags

Здесь надо добавить теги, по которым у нас будут различаться шлюзы. Нажимаем "+" и добавляем тег и его описание.

Сохраняем. Также здесь можно тег закрепить за какой-либо командой, чтобы только они могли пользоваться теми шлюзами, которые зарегистрированы с указанными тегами.

ПереходимвменюSettings GATEWAY Tenants

То же самое и с настройкой тенантов. Только тут нет кнопки "+", но есть серенькая надпись "New tenant", которую надо нажать для добавления нового тенанта. Естественно, данный тенант должен быть создан в Open Distro for Elasticsearch, и к нему должны быть выданы права.

Добавление пользователей

Переходим вSettings USER MANAGEMENT Users

Здесь можно добавлять пользователей, вот только работать это будет, если у нас настроена рассылка по email. Иначе новым пользователям не придёт рассылка от системы со ссылкой на сброс пароля. Есть ещё один способ добавить пользователя, но это прям хардкод-хардкод!

В файле настроек Management API: gravitee.yml есть такой кусочек настроек:

security:  providers:  # authentication providers    - type: memory      # password encoding/hashing algorithm. One of:      # - bcrypt : passwords are hashed with bcrypt (supports only $2a$ algorithm)      # - none : passwords are not hashed/encrypted      # default value is bcrypt      password-encoding-algo: bcrypt      users:        - user:          username: admin          password: $2a$10$Ihk05VSds5rUSgMdsMVi9OKMIx2yUvMz7y9VP3rJmQeizZLrhLMyq          roles: ORGANIZATION:ADMIN,ENVIRONMENT:ADMIN

Здесьперечисленытипыхранилищдляпользователей: memory, graviteeиldap.Данные из хранилищаmemoryберутся из файла настроек:gravitee.yml. Данные из хранилищаgraviteeхранятся вMongoDB. Для хранения пользовательских паролей, по умолчанию используется тип хеширования BCrypt с алгоритмом $2a$. В представленных настройках мы видим пользователя: admin с хешированным паролем: admin и его роли. Если мы будем добавлять пользователей через UI, то пользователи будут храниться в MongoDB и тип их будет уже gravitee.

Создание групп пользователей

Переходим вSettings USER MANAGEMENT Groups

При нажатии на "+" получаем возможность добавить группу и пользователей в эту группу.

Проверка доступных шлюзов

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

Здесь мы видим настройки шлюза. В частности, Sharding tags и Tenant. Их мы добавили чуть раньше.

Естественно, есть возможность мониторинга состояния шлюза. Данные по мониторингу хранятся в Elasticsearch в отдельном индексе.

Публикация первого API

Для публикации первого API нам сначала потребуется сделать какой-нибудь backend с API.

BackEnd с API, балеринами и Swagger.

Возьмём FastAPI и сделаем простейшее backend с API.

#!/bin/env python3import uvicornfrom fastapi import FastAPIapp = FastAPI()@app.get('/')@app.get('/{name}')def read_root(name: str = None):    """    Hello world    :return: str = Hello world    """    if name:        return {"Hello": name}    return {"Hello": "World"}@app.get("/items/{item_id}")@app.post("/items/{item_id}")@app.put("/items/{item_id}")def gpp_item(item_id: str):    """    Get items    :param item_id: id    :return: dict    """    return {"item_id": item_id}if __name__ == "__main__":    uvicorn.run(app, host="0.0.0.0", port=8000)

Это иAPIможно назвать с трудом, но для примера работы вполне хватит.

Теперь запустим его! Можно даже на том же сервере.

python3 main.py

Теперь, если мы зайдем на этот серверhttp://backend_server:8000/,мы увидим приветствие миру! Если подставим своё имя, типа так:http://backend_server:8000/Anton, то приветствовать уже будут вас! Если же хотите познать всю мощьFastAPI, то сразу заходите на адрес:http://backend_server:8000/docsилиhttp://backend_server:8000/redoc. На этих страницах можно найти все методы, с которым работает данное API и также ссылку на swagger файл. Копируем URL до swagger файла.

В прошлый раз мы создавали наш план вручную. Было несколько утомительно. Сегодня мы сделаем все быстрее, через импорт swagger файла!

На главном экране Gravitee нажимаем большой "+", выбираем "IMPORT FROM LINK", вставляем URL и нажимаем "IMPORT".

Получается как-то так

Нажимаем "IMPORT"!

Почти полностью сформированный API! Теперь немного доработаем напильником...

Для начала нажимаем "START THE API" чтобы API запустить.

Переходим в "Plans" и нажимаем "+".

Создаем тестовый план.

Тип аутентификации выбираем Keyless (public) и нажимаем "NEXT".

Ограничения по количеству запросов и путям пропускаем. Нажимаем "NEXT".

Политики нам тоже пока не нужны. Нажимаем "SAVE".

План создан, но пока находиться в стадии "Staging"

Нажимаем на кнопку публикации плана - синее облачко со стрелочкой вверх! Подтверждаем кнопкой "PUBLISH"

И получаем опубликованный план и какую-то желтую полоску с призывом синхронизировать новую версию API.

Нажимаем "deploy your API" и подтверждаем наше желание "OK"

Переходим вAPIs Proxy Entrypoints

Здесь можно указать точки входа для нашего API и URL пути. У нас указан только путь "/fastapi". Можно переключиться в режим "virtual-hosts" и тогда нам будет доступен вариант с указанием конкретных серверов и IP. Это может быть актуально для серверов с несколькими сетевыми интерфейсами.

ВAPIs Proxy GENERAL CORSможнопроизвестинастройкиCross-origin resource sharing.

ВAPIs Proxy GENERAL Deploymentsнадоуказатьвсеsharding tags,которыебудутиспользоватьсяэтимAPI.

ВAPIs Proxy BACKEND SERVICES Endpointsможно указать дополнительные точки API и настроить параметры работы с ними.

Сейчас нас интересуют настройки конкретной Endpoint, поэтому нажимаем на нижнюю шестеренку.

Исправляем "Target" на http://backend_server:8000/, устанавливаем tenant, сохраняем и деплоим!

ВAPIs Proxy Deploymentsнадо указать те sharding tags, которые могут использовать данное API. После этого необходимо вернуться в созданный план и в списке Sharding tags выбрать тег "service-tag".

ВAPIs Designможно указать политики, которые будут отрабатывать при обработке запросов.

ВAPIs Analytics Overviewможно смотреть статистику по работе данного конкретного API.

ВAPIs Analytics Logsможно настроить логи и потом их смотреть.

ВAPIs Auditможно посмотреть, как изменялись настройки API.

Остальное пока рассматривать не будем, на работу оно не влияет.

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

Переходим наhttp://gravitee_host:8082/fastapi/ , и вам покажется приветствие миру:

Также сразу можно заглянуть вAPIs Analytics Overview/Logsдля просмотра статистики обработки запросов.

Заключение

Итак, поздравляю всех, кто дочитал до сюда и остался в живых! Теперь вы знаете, как поднять ознакомительный стенд APIM Gravitee, как его настроить, создать новое API из swagger файла и проверить, что всё работает. Вариантов настройки шлюзов, точек входа и выхода, сертификатов, балансировок нагрузки и записи логов много. В одной статье всего и не расскажешь. Так что в следующей статье я расскажу о более продвинутых настройках системы APIM Gravitee. В Телеграмме есть канал по данной системе:https://t.me/gravitee_ru, вопросы по нюансам настройки можно задавать туда.

Подробнее..

Recovery mode Как ускорить сайт в 4 раза, просто перенастроив сервер

02.06.2021 12:04:43 | Автор: admin

Если вы работаете с сайтом, который постепенно растет, - увеличивается количество товаров, трафик с рекламы - то рано или поздно придется перейти в режим работы highload, высоких нагрузок на сервер. Но что делать, если ваш сайт не растет, а сервер все чаще не выдерживает, и происходит блокировка данных? Именно с этой проблемой мы столкнулись, дорабатывая сайт для интернет-магазина светового оборудования с ассортиментом более чем 100 000 товаров.

Исходная ситуация

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

Поиск проблемы

Мы провели аудит настроек сервера и сайта, разделив работы на два этапа: анализ back-end и front-end, и обнаружили низкую скорость загрузки страниц на back-ende - порядка 80 секунд на самых посещаемых страницах, что в итоге приводило к существенному снижению конверсии.

Мы выяснили, что основная проблема заключалась в неправильно настроенном кэше и настройке базы данных.

В итоге был составлен план действий из четырех шагов, который помог нам добиться неплохих результатов. Что мы сделали?

Решение

Шаг 1. Настройка баз данных

На первом этапе мы настроили базу данных MySQL без изменения систем хранения, исходя из доступных ресурсов и нагрузки проекта. Эти действия, в первую очередь, были направлены на оптимизацию потребления ресурсов оперативной памяти, что позволило избежать ухода сервера в SWAP, когда, исчерпав ресурсы оперативной памяти, сервер начинал работать из файла подкачки и замедлял работу сайта.

Шаг 2. Смена типа хранения на InnoDB

Почему мы выбрали InnoDB?

В InnoDB данные хранятся в больших совместно используемых файлах, в отличие от используемого прежде MyISAM, где для каждой конкретной таблицы создается отдельный файл данных. InnoDB обеспечивает надежность хранения данных за счет блокировки данных на уровне строки и транзакционности.

Главное преимущество InnoDB заключается в скорости работы при выполнении запроса к базе InnoDB происходит блокировка только строки, при выполнении же запроса к базе MyISAM блокируется вся таблица. Дело в том, что пока запрос не будет выполнен, никакие другие обращения к таблице/строке будут невозможны. А поскольку строки значительно меньше целых таблиц, InnoDB обрабатывает запросы быстрее.

Также была произведена оптимизация работы самой базы данных InnoDB. Например, были оптимизированы параметры:

# InnoDB parameters

innodb_file_per_table

innodb_flush_log_at_trx_commit

innodb_flush_method

innodb_buffer_pool_size

innodb_log_file_size

innodb_buffer_pool_instances

innodb_file_format

innodb_locks_unsafe_for_binlog

innodb_autoinc_lock_mode

transaction-isolation

innodb-data-file-path

innodb_log_buffer_size

innodb_io_capacity

innodb_io_capacity_max

innodb_checksum_algorithm

innodb_read_io_threads

innodb_write_io_threads

Промежуточные результаты

После выполнения шагов 1 и 2 количество одновременных соединений с веб-сервером уменьшилось, так как запросы к базе данных и подключение к ней стали обрабатываться быстрее.

Это в свою очередь привело к уменьшению потребляемой оперативной памяти.

Шаг 3. Перенастройка Nginx и установка модулей кэширования brotli, pagespeed, proxy_buffering

Nginx позиционируется как простой, быстрый и надежный сервер, неперегруженный функциями. Уже длительное время Nginx обслуживает серверы многих высоконагруженных российских сайтов, например, Яндекс, Mail.Ru, ВКонтакте и Рамблер. Для улучшения производительности при использовании дополнительных серверов, Nginx поддерживает буферизацию (proxy_buffering) и кеширование (proxy_cache), чем мы и воспользовались.

Не обошлось и без курьезов настроек Nginx. У клиента был обычный интернет-магазин с товарами, тогда как настройки буферизации, которые мы обнаружили во время аудита, позволяли ему быть чуть ли ни стриминговым сервисом. Мы существенно уменьшили значения в параметре client_max_body_size, что в совокупности с перенастройкой Nginx еще больше снизило потребление памяти.

Шаг 4. Оптимизация настроек PHP-FPM и Memcache и отключение Apache

PHP-FPM нередко используется в паре с веб-сервером Nginx. Последний обрабатывает статические данные, а обработку скриптов отдает PHP-FPM. Такая реализация работает быстрее, чем распространенная модель Nginx + Apache.

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

Необходимым шагом стал перевод работы PHP-FPM на unix socket. Зачем это понадобилось? Nginx сам по себе довольно быстрый веб-сервер, однако самостоятельно он не может обрабатывать скрипты. Для этого необходим бэкенд в виде PHP-FPM. Чтобы вся эта связка работала без потери скорости, мы использовали unix socket способ подключения к PHP-FPM, позволяющий избегать сетевые запросы и дающий значительный прирост в скорости работы сайта.

Результаты работ

1. Время отклика главной страницы уменьшилось с 24 секунд до чуть более 3 секунд, внутренних до 5-8 сек.

2. Уменьшилось потребление серверных ресурсов.

3. Стабилизировалось поведение сервера - он перестал зависать.

4. Глубина просмотров увеличилась на 30%, и как следствие, это дало улучшение в SЕО, а также последующих продаж: растут поведенческие показатели => растут позиции сайта в выдаче => растет трафик => растут продажи.

5. Клиенту были даны рекомендации по оптимизации front-end части сайта для ускорения работы сайта. Например:

  • оптимизировать графики и настройку выдачи изображений в формате webp;

  • настроить lazyload-загрузки данных;

  • вынести все некритические для отображения страницы скрипты в конец страницы.

Вывод

Мы ускорили сайт и устранили проблемы с его загрузкой без изменения кода. Скорость работы сайта влияет на многие показатели: начиная с удобства для пользователя и заканчивая ранжированием сайта в поисковой выдаче, что в конечно итоге сказывается на конверсии.

Подробнее..

Быстрый запуск Nextcloud и Onlyoffice на Ubuntu SSL от Letsencrypt

20.06.2021 18:15:44 | Автор: admin

Мало того, что многие не доверяют общедоступным облакам, так они еще и предлагают непозволительно малые объемы дискового пространства.

Однажды мне понадобилось 1Tb облачного хранилища и выбор пал на Nextcloud, который и было решено развернуть на собственном домашнем сервере

В данной статье я опишу как быстро и безболезненно установить и настроить облако Nextcloud и облачный редактор Onlyoffice

Статья предполагает, что у вас уже установлен и настроен Ubuntu.

Все действия были проверены на Ubuntu Server 20.04

Что будем делать:
1. Установим Nginx, PHP и MariaDB
2. Добавим бесплатный SSL-сертификат Let's Encrypt
3. Развернем NextCloud
4. Произведем тонкие настройки сервера
5. Установим Onlyoffice

Бесплатные доменные имена в домене .tk можно получить на www.freenom.com

Первым делом, устанавливаем вспомогательные утилиты

sudo apt-get install nano mc zip -y

Этот пункт можно пропустить, если настраиваете облако на локальный диск, а не на отдельную машину с доступом по nfs, мне понадобилось сделать это именно на nfs

# Ставим nfs-clientsudo apt install nfs-common -y# -------------------# Монтируем папку nfs# Ставим nginxsudo mkdir -p /nfs/ncsudo mount your_host_ip:/папка_шары_nfs/ /nfs/ncsudo ls -l /nfs/nc/sudo df -hsudo du -sh /nfs/nc/# -------------------# Монтируем nfs при загрузкеsudo nano /etc/fstab# Добавим такую строку в конец файлаyour_host_ip:/папка_шары_nfs/  /nfs/nc  nfs auto,nofail,noatime,nolock,intr,tcp,actimeo=1800 0 0

Ставим nginx

sudo apt install nginx -ysudo nginx -Vsudo systemctl enable nginxsudo systemctl start nginx

Ставим php 7.4

sudo apt install php7.4-fpm php7.4-mysql php7.4 php7.4-curl php7.4-gd php7.4-json php7.4-mbstring php7.4-common php7.4-xml php7.4-zip php7.4-opcache php-apcu php-imagick -y

Настраиваем php 7.4

sudo nano /etc/php/7.4/fpm/pool.d/www.conf

снимаем комментарии со строк

env[HOSTNAME] = $HOSTNAME
env[PATH] = /usr/local/bin:/usr/bin:/bin
env[TMP] = /tmp
env[TMPDIR] = /tmp
env[TEMP] = /tmp

# Настраиваем php.ini:

sudo nano /etc/php/7.4/fpm/php.ini

opcache.enable=1
opcache.enable_cli=1
opcache.interned_strings_buffer=8
opcache.max_accelerated_files=10000
opcache.memory_consumption=128
opcache.save_comments=1
opcache.revalidate_freq=1

Разрешаем автозапуск php-fpm и перезапускаем его:

sudo systemctl enable php7.4-fpmsudo systemctl restart php7.4-fpm

Устанавливаем MariaDB:

sudo apt install mariadb-serversudo systemctl enable mariadbsudo systemctl start mariadb

Запуск сценария безопасности (здесь можно поменять пароль рута, убрать ненужные разрешения):

sudo mysql_secure_installation

Создаем базу данных для Nextcloud (в примере указан пароль nextcloud, его лучше заменить на свой) :

sudo mysql -u root -p

Вводим пароль рута для MariaDB

 >CREATE DATABASE nextcloud;CREATE USER 'nextcloud'@'localhost' IDENTIFIED BY 'nextcloud';GRANT ALL ON nextcloud.* TO 'nextcloud'@'localhost' IDENTIFIED BY 'nextcloud' WITH GRANT OPTION;FLUSH PRIVILEGES;EXIT;

Теперь надо создать файл конфигурации Nginx для Nextcloud

sudo nano /etc/nginx/sites-enable/nextcloud.conf

И вставляем в него следующий текст, естественно, заменив nc.myhost.com на свои сервера

server {    listen 80;    server_name nc.myhost.com;    return 301 https://$server_name$request_uri;}server {    listen 443 ssl;    server_name nc.myhost.com;    ssl_certificate /etc/nginx/ssl/cert.pem;    ssl_certificate_key /etc/nginx/ssl/cert.key;    root /var/www/nextcloud;    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;    client_max_body_size 10G;    fastcgi_buffers 64 4K;    rewrite ^/caldav(.*)$ /remote.php/caldav$1 redirect;    rewrite ^/carddav(.*)$ /remote.php/carddav$1 redirect;    rewrite ^/webdav(.*)$ /remote.php/webdav$1 redirect;    index index.php;    error_page 403 = /core/templates/403.php;    error_page 404 = /core/templates/404.php;    location = /robots.txt {      allow all;      log_not_found off;      access_log off;    }    location ~ ^/(data|config|\.ht|db_structure\.xml|README) {        deny all;    }    location / {        rewrite ^/.well-known/host-meta /public.php?service=host-meta last;        rewrite ^/.well-known/host-meta.json /public.php?service=host-meta-json last;        rewrite ^/.well-known/carddav /remote.php/carddav/ redirect;        rewrite ^/.well-known/caldav /remote.php/caldav/ redirect;        rewrite ^(/core/doc/[^\/]+/)$ $1/index.html;        try_files $uri $uri/ index.php;    }    location ~ ^(.+?\.php)(/.*)?$ {        try_files $1 = 404;        include fastcgi_params;        fastcgi_param SCRIPT_FILENAME $document_root$1;        fastcgi_param PATH_INFO $2;        fastcgi_param HTTPS on;        fastcgi_pass unix:/run/php/php7.4-fpm.sock;    }    location ~* ^.+\.(jpg|jpeg|gif|bmp|ico|png|css|js|swf)$ {        expires modified +30d;        access_log off;    }}

Теперь необходимо получить сертификаты для ssl

Устанавливаем Certbot и его плагин для Nginx:

sudo apt install certbot python3-certbot-nginx

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

Сначала с ключом --dry-run проверяем все ли в порядке

sudo certbot certonly --agree-tos --email you@mail -d nc.myhost.com-d www.myhost.com -d zabbix.myhost.com --nginx --dry-run --d

Если все хорошо, то получаем сертификаты

sudo certbot certonly --agree-tos --email почта@администратора -d myhost.com-d nc.myhost.com-d cloud.myhost.com-d zabbix.myhost.com-d www.myhost.com-d mail.myhost.com sudo certbot certonly --agree-tos --email your@mail -d nc.myhost.com-d www.33rus.com -d zabbix.33rus.com --nginx n 

Сертификаты появятся в папке /etc/letsencrypt/live/myhost.com cert.pem chain.pem fullchain.pem privkey.pem

Подключаем сертификаты к сайту

sudo nano /etc/nginx/sites-available/nextcloud.conf

ssl_certificate /etc/letsencrypt/live/myhost.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/myhost.com/privkey.pem;

Устанавливаем Nextcloud:

Скачиваем последнюю версию с сайте Nextcloud:

cd /tmp/sudo wget https://download.nextcloud.com/server/releases/nextcloud-21.0.0.zipsudo unzip nextcloud-21.0.0.zipsudo cp -R nextcloud /var/www/nextcloud/cd /var/www/sudo chown -R www-data:www-data nextcloud/sudo chown -R www-data:www-data /nfs/nc

Обратите внимание, в данном случае я использую папку на nfs, вам необходимо использовать папку в соответствии с вашими настройками

Почти все. Заходим на https://nc.myhost.com
Создаем пользователя, пароль, прописываем доступ к каталогу /nfs/nc/
Прописываем созданную ранее базу данных и пароль к ней.

Теперь тонкая настройка Nextcloud и установка Onlyoffice

Ставим Redis и APCu

sudo apt install memcached php-memcached -ysudo apt install php-apcu redis-server php-redis -ysudo nano /var/www/nextcloud/config/config.php

И добавляем следующие строки перед закрывающей скобкой )

'memcache.local' => '\OC\Memcache\APCu',
'memcache.distributed' => '\OC\Memcache\Redis',
'redis' =>
array (
'host' => '127.0.0.1',
'port' => 6379,
),
'memcache.locking' => '\OC\Memcache\Redis',

Переиндексация файлов (если вдруг вы скопировали файлы в папку nexcloud не через интерфейсы nextcloud, то их надо переиндексировать)

sudo -u www-data php /var/www/nextcloud/occ files:scan --all

Устанавливаем OnlyOffice DocumentServer

Первым делом устанавливаем версию PostgreSQL, включенную в вашу версию Ubuntu:

sudo apt install postgresql -y

После установки PostgreSQL создайте базу данных и пользователя PostgreSQL:

Пользователем и паролем для созданной базы данных должны быть onlyoffice.

sudo -i -u postgres psql -c "CREATE DATABASE onlyoffice;"sudo -i -u postgres psql -c "CREATE USER onlyoffice WITH password 'onlyoffice';"sudo -i -u postgres psql -c "GRANT ALL privileges ON DATABASE onlyoffice TO onlyoffice;"

Установка rabbitmq и nginx-extras:

sudo apt install rabbitmq-server -ysudo apt install nginx-extras -y

Установка ONLYOFFICE Docs

Добавьте GPG-ключ:

sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys CB2DE8E5

Добавьте репозиторий ONLYOFFICE Docs:

sudo echo "deb https://download.onlyoffice.com/repo/debian squeeze main" | sudo tee /etc/apt/sources.list.d/onlyoffice.list sudo apt update

Устанавливаем mariadb-client!

sudo apt install mariadb-client -y

Устанавливаем ONLYOFFICE Docs. Не ошибитесь с вводом пароля. Это должен быть onlyoffice

sudo apt install onlyoffice-documentserver -y

Переводим onlyoffice на https

sudo cp -f /etc/onlyoffice/documentserver/nginx/ds-ssl.conf.tmpl /etc/onlyoffice/documentserver/nginx/ds.confsudo nano /etc/onlyoffice/documentserver/nginx/ds.conf 

Меняем порт ssl не забыв пробросить его в роутере

listen 0.0.0.0:7443 ssl;
listen [::]:7443 ssl default_server;

Перезапускаем nginx

sudo service nginx restart

Настраиваем cron

sudo crontab -u www-data -e

# Добавляем строчку

*/5 * * * * php -f /var/www/nextcloud/cron.php

Ну, вот и все, останется через веб-интерфейс установить плагин ONLYOFFICE в вашем Nextcloud и прописать сервер https://myhost.com:7443

Подробнее..

Есть ли корпоративная жизнь на удаленке и как ее обеспечить интеграция внутренней системы аутентификации

30.04.2021 12:16:20 | Автор: admin

В digital-агентстве Convergent, где я работаю, в потоке множество проектов, и у каждого из них может быть собственная админка. Есть несколько окружений (дев, стейдж, лайв). А ещё есть разные внутрикорпоративные сервисы (как собственной разработки, так и сторонние вроде Redmine или Mattermost), которыми ежедневно пользуются сотрудники.

Наша команда всегда была распределённой между несколькими офисами, но с учётом событий последнего года все сотрудники перешли на удалёнку. Так мы столкнулись с необходимостью организовать всё многообразие внутренних и клиентских сервисов в единой системе.

В данной статье я хочу поделиться опытом создания собственной внутренней системы аутентификации на основе OpenResty, а также спецификации OAuth2. В качестве основного языка программирования мы используем PHP, а фреймворк Yii 2.

Суммирую необходимый функционал:

  • Единое место управления всеми доступами. Здесь происходит выдача и отзыв доступов в административные панели сайтов и списки доменов;

  • По умолчанию весь доступ запрещён, если не указано обратное (доступ к любым доменам контролируется с помощью OpenResty);

  • Аутентификация для сотрудников;

  • Аутентификация для клиентов.

Закрытый доступ к сайтам и инфраструктуре

Начну со схемы, как мы организовали инфраструктуру доступов.

Упрощенная схема взаимодействия между пользователями и серверамиУпрощенная схема взаимодействия между пользователями и серверами

Первое, что нужно было сделать, это закрыть доступ по умолчанию ко всем тестовым окружениям и инфраструктурным сервисам. Сотрудники в таком случае могут получить доступ ко всему путём добавления своего IP в вайтлист (об этом позже), а клиенты получают доступ точечно.

Фронт-контроллером в данном случае выступает OpenResty это модифицированная версия nginx, которая в т. ч. поддерживает из коробки язык Lua. На нём я написал прослойку, через которую проходят все HTTP(s)-запросы.

Вот так может выглядеть код скрипта аунтентификации (в упрощенном варианте):

auth.lua
function authenticationPrompt()    ngx.header.www_authenticate = 'Basic realm="Restricted by OpenResty"'    ngx.exit(401)endfunction hasAccessByIp()    local ip = ngx.var.remote_addr    local domain = ngx.var.host    local port = ngx.var.server_port    local res, err = httpc:request_uri(os.getenv("AUTH_API_URL") .. "/ip.php", {        method = "GET",        query = "ip=" .. ip .. '&domain=' .. domain .. '&port=' .. port,        headers = {          ["Content-Type"] = "application/x-www-form-urlencoded",        },        keepalive_timeout = 60000,        keepalive_pool = 10,        ssl_verify = false    })    if res ~= nil then        if (res.status == 200) then            session.data.domains[domain] = true            session:save()            return true        elseif (res.status == 403) then            return false        else            session:close()            ngx.say("Server error: " .. res.body)            ngx.exit(500)        end    else        session:close()        ngx.say("Server error: " .. err)        ngx.exit(500)    endendfunction hasAccessByLogin()    local header = ngx.var.http_authorization    local domain = ngx.var.host    local port = ngx.var.server_port    if (header ~= nil) then        header = ngx.decode_base64(header:sub(header:find(' ') + 1))        login, password = header:match("([^,]+):([^,]+)")        if login == nil then            login = ""        end        if password == nil then            password = ""        end        local res, err = httpc:request_uri(os.getenv("AUTH_API_URL") .. '/login.php', {            method = "POST",            body = "username=" .. login .. '&password=' .. password .. '&domain=' .. domain .. '&port=' .. port,            headers = {              ["Content-Type"] = "application/x-www-form-urlencoded",            },            keepalive_timeout = 60000,            keepalive_pool = 10,            ssl_verify = false        })        if res ~= nil then            if (res.status == 200) then                session.data.domains[domain] = true                session:save()                return true            elseif (res.status == 403) then                return false            else                session:close()                ngx.say("Server error: " .. res.body)                ngx.exit(500)            end        else            session:close()            ngx.say("Server error: " .. err)            ngx.exit(500)        end    else        return false    endendos = require("os")http = require "resty.http"httpc = http.new()session = require "resty.session".new()session:start()if (session.data.domains == nil) then    session.data.domains = {}endlocal domain = ngx.var.hostif session.data.domains[domain] == nil then    if (not hasAccessByIp() and not hasAccessByLogin()) then        session:close()        authenticationPrompt()    else        session:close()    endelse    session:close()end

Алгоритм работы скрипта довольно простой:

  • Поступает HTTP-запрос от пользователя;

  • OpenResty запускает скрипт auth.lua;

  • Скрипт определяет запрашиваемый домен и отправляет два запроса на внешний бэкенд;

  • Первый на проверку IP-адреса пользователя в базу;

  • Если IP отсутствует, выводит браузерное окно для ввода логина и пароля, отправляет второй запрос на проверку доступа;

  • В любой другой ситуации выводит окно Вход.

На GitHub я выложил рабочий пример, который можно быстро развернуть с помощью Docker.

Немного расскажу о том, как выглядит управление в нашей системе.

Отмечу, что IP-адреса попадают в базу при аутентификации пользователя. Это сделано специально для сотрудников. Такие адреса помечают как временные и доступные ограниченное время, после чего они удаляются.

В случаях, когда нужно разрешить межсерверное взаимодействие, через панель управления можно добавить IP-адреса вручную.

Для клиентов же создаются простые доступы по паре логин-пароль. Такие доступы ограничиваются в пределах определенных доменных адресов.

Управление доступами по паролю и по IP-адресуУправление доступами по паролю и по IP-адресу

Управление базой сотрудников подтягивается в ID через синхронизацию с другой внутренней системой. Каждый новый сотрудник при добавлении в нашу CRM автоматически получает учётную запись в ID, а также все нужные письма с доступами и инструкциями для последующей настройки и работы.

Форма аутентификации для сотрудниковФорма аутентификации для сотрудников

Каждый день мы начинаем работу с данной аутентификации, таким образом добавляя свой IP-адрес в белый список для получения доступа к инфраструктуре.

Двухфакторная аутентификация

Для обеспечения двухфакторной аутентификации для сотрудников решено было добавить Google Authenticator. Такой механизм защиты позволяет больше обезопасить себя от утечки доступов. Для PHP есть готовая библиотека sonata-project/GoogleAuthenticator. Пример интеграции можно посмотреть здесь.

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

OAuth и OpenID

Третье, и не менее важное для нас, создание OAuth-сервера. За основу мы взяли модуль thephpleague/oauth2-server. Для Yii 2 готового решения не было, поэтому написали собственную имплементацию сервера. OAuth2 достаточно обширная тема, расписывать её работу в данной статье не буду. Библиотека имеет хорошую документацию. Также она поддерживает различные фреймворки, включая Laravel и Symfony.

Таким образом, любой сторонний сервис, который поддерживает кастомные OAuth2 конфигурации, достаточно просто подключается к нашей системе. Значимой фишкой такой интеграции стало подключение нашего ID к Mattermost. Последний в бесплатной версии поддерживает только аутентификацию с помощью GitLab, которую удалось эмулировать через наш сервис.

Также для всех наших проектов на Yii был разработан модуль для подключения ID. Это позволило вынести всё управление доступами в админпанели для сотрудников в централизованное место. Кстати, если интересно, я писал статью о модульном подходе, который мы применили в нашем digital-агентстве.

Заключение

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

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

Ссылки по теме

Подробнее..

Домашний DPI, или как бороться с провайдером его же методами

21.03.2021 10:06:16 | Автор: admin

Зачем всё это нужно?

Долгое время я терпел ограничения РосКомНадзора и соответствующие действия провайдеров по различным ограничениям доступа к сайтам - но с определённого момента устал, и начал думать как бы сделать так, чтобы было и удобно, и быстро, и при этом с минимумом заморочек после настройки... Хочу оговориться, что цель анонимизации не ставилась.

Какие вообще уже есть методы решения?

Вообще, эта проблема имеет несколько решений. Кратко перечислю самые известные и их минусы:

  1. Заворачивание всего трафика в VPN-туннель либо какой-то прокси - как вариант, TOR. В случае с TOR-ом о скорости можно забыть, в остальных случаях скорость и время отклика также страдают, поскольку необходимо проксировать через удалённый сервер. Поскольку РосКомНадзор у нас действует на всей территории России, получается что весь трафик придётся проксировать через зарубежный сервер, а значит время отклика (или "пинг") будет сильно страдать. Сразу отрезается весь пласт, например, игровых приложений.

  2. "Гибридный" вариант с использованием списков. Основной трафик идёт напрямую, но часть IP-адресов перенаправляются через VPN/прокси/TOR. Списки можно забирать, например, отсюда. Минусы - приходится периодически обновлять эти списки, и какими бы они не были актуальными есть вероятность всё же наткнуться на заблокированный сайт. На самом деле, один из лучших способов для комфортного пользования интернетом без ограничений. Но можно лучше!

  3. Использование "чёрной магии", связанной с особенностями применяемого провайдерами DPI-софта. Я, в общем и целом, про "дыры" в обработке трафика (например, блокирование только на уровне DNS, или пропуск необычно сформированных пакетов), и, в частности, про GoodbyeDPI уважаемого @ValdikSS. Самый большой минус - работает далеко не везде. И чем дальше, тем хуже работает. Рост вычислительных мощностей скорее упрощает жизнь провайдерам, чем нам в этом вопросе...

  4. ...и наконец, использование DPI для обхода DPI! На самом деле этот способ является подвариантом "гибридного", но безо всяких списков. Мы анализируем пришедшие пакеты от провайдера, делаем вывод, заблокировал ли он нам что-то, и на этом основании либо пускаем дальше трафик к запросившему, либо перенаправляем трафик через VPN/прокси/TOR. Всё ещё требует конфигурации VPN/прокси/TOR, но уже не требует никаких списков, а также позволяет принимать решения на основании теоретически сколь угодно сложной логики!

Про последний способ дальше и пойдёт речь. И поможет нам в этом NGINX.

Во многом идея была вдохновлена Squid-овским механизмом HTTPS Peek and Splice, но его возможностей к сожалению не хватило.

Так ведь NGINX - это веб-сервер, а не инструмент для DPI?

NGINX - это весьма продвинутый мультипротокольный сервер, и его возможности в качестве HTTP-сервера - лишь верхушка айсберга. При этом он кроссплатформенен, имеет мало зависимостей и при этом бесконечно расширяем.

И самым лучшим расширением NGINX является проект OpenResty - который добавляет практически ко всем аспектам NGINX-а поддержку Lua.

Мне могут сейчас возразить, что современный NGINX поддерживает "изкаробки" возможность скриптинга на JavaScript (njs), и будет прав, но, во-первых, OpenResty гораздо более развитый проект и его API имеет гораздо больше возможностей, а во-вторых, OpenResty использует LuaJit с поддержкой FFI, что позволяет вызывать C-методы напрямую из Lua-мира - и это создаёт такую возможность для расширения, которая njs даже и не снилась. Во всяком случае пока что...

При этом, NGINX имеет возможность проксировать и "сырой" TCP-трафик (теоретически и UDP тоже, но я реализовал "DPI" только TCP).

О деталях реализации

Конкретно у меня по причине отсутствия кабельного интернета дома сейчас используется 4G-интернет от Мегафона, поэтому описание будет происходить с точки зрения именно Мегафона.

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

Прежде всего, мы поднимаем NGINX в режиме stream на каком-то порту... Пусть будет 40443. Но сам по себе nginx не знает что делать с трафиком, что туда приходит. Именно это мы и будем разруливать с помощью Lua.

Прежде всего, мы перенаправляем весь трафик с 80 и 443 порта на этот самый 40443 порт при помощи iptables и его команды REDIRECT. Эта команда интересна тем, что прописывает в свойства сокета опцию SO_ORIGINAL_DST, в которой сохраняет оригинальный IP и порт, куда пакет изначально направлялся, до того как iptables над ним зверски поиздевался, переписав destination... Кхм, я отвлёкся. Эту информацию можно извлечь при помощи getsockopt... Правда из коробки обёртки над ним не было, так что пришлось написать простенький C-модуль для nginx.

Теоретически можно было бы использовать TPROXY, и пропатчить NGINX для поддержки SO_TRANSPARENT сокетов, но хотелось не прибегать к прямому патчу исходников NGINX-а и обойтись модулями, поэтому REDIRECT.

Итак, мы запрашиваем заблокированный сайт... Пусть будет, например, rutracker.org.

...И сразу надо определиться, по HTTP или HTTPS. Несмотря на то, что HTTP постепенно умирает, всё же есть ещё сайты, использующие его. Так что обработка HTTP реализована.

HTTP - тут всё просто

Итак, мы запрашиваем http://rutracker.org. И видим, что нас перенаправило на http://m.megafonpro.ru/rkn, где Мегафон услужливо сообщает, что, мол, так и так, сайт заблокирован, просим извинить, а пока посмотрите на нашу рекламу.

Да, Мегафон просто напросто отправил 307 Temporary Redirect с Location на свой собственный сайт для отображения этого сообщения. А значит мы вполне можем отследить ровно это - 307 редирект с Location в котором http://m.megafonpro.ru/rkn.

Для этого мы вычитываем первые данные, пришедшие от клиента (вплоть до 16 кбайт, но по факту первые пакеты весьма маленькие), перенаправляем их серверу и читаем ответ от него. Если находим в нём этот редирект - это означает сразу две вещи:

  1. Сайт блокируется, значит этот коннект надо редиректить.

  2. Запрос скорее всего не дошёл до сервера, а значит переотправять его повторно - безопасно. Это необязательно верно, но верно наверное в 99% случаев. Правда, если это неверно, и вы отправляете запросы, что-то меняющие на удалённом сервере, то тут беда... Прилетит по итогу два запроса - один - тот на который заблокирован ответ, и второй - спроксированный. И узнать мы это никак не сможем. Хорошо, что HTTP без SSL становится всё меньше, правда? =)

Если редиректа нету, значит мы просто отправляем этот запрос дальше клиенту и дальше просто проксируем трафик, не вмешиваясь в него.

HTTPS вплоть до TLSv1.2 - посложнее

С SSL/TLS всё гораздо сложнее... Но есть и хорошие новости. Перед любым HTTP-запросом мы сначала должны выполнить Handshake, а значит первый пакет точно не вызовет выполнение команды на сервере в случае, если нас заблокировали, но исходный пакет таки ушёл на сервер.

Мы запрашиваем https://rutracker.org и получаем в браузере... Ошибку. Сертификат недействительный, потому что выпущен даже не для этого домена, беда-беда...

Анализируем сам сертификат... И что же мы видим? CN=megafon.ru. Получается, что для того, чтобы понять, что сайт блокируется, достаточно вычитать полученный от сервера сертификат, и если мы запрашиваем что угодно кроме megafon.ru, а получили сертификат с CN=megafon.ru - нас блокируют, и надо проксировать.

Осталось только понять, как понять, куда именно мы изначально обращались, и как получить этот сертификат.

И здесь нам поможет SNI - дело в том, что (современный, мы не говорим про эпоху IE6, она ушла и слава богу) клиент отправляет домен, к которому обращается, в составе незашифрованных данных ClientHello. Самое интересное, что эти данные умеет вычитывать даже сам NGINX из коробки - модуль ssl_preread поставляется вместе с ним. Ну а Lua-биндинги позволяют получить эту информацию и для наших целей...

Итак, что мы делаем? Процедура во многом аналогична HTTP - мы отправляем первый пакет от клиента серверу (который как раз содержит ClientHello) и ждём от сервера ответа с сертификатом. После чего убеждаемся что SNI не megafon.ru, парсим сертификат (спасибо человеку, написавшему биндинги к OpenSSL для Lua), и принимаем решение - проксировать или нет.

И всё было бы хорошо, если бы не TLSv1.3, который всю эту историю сильно обламывает...

TLSv1.3 наносит ответный удар

Во-первых, в TLSv1.3 SNI может быть зашифрованным. Хорошая новость в том, что зашифрованный SNI не расшифрует и сам провайдер, а значит он будет блокировать любые TLS-запросы к IP точно также. Вторая особенность в том, что сертификат сервер клиенту теперь тоже отправляет в зашифрованном виде...

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

На этом этапе я уже даже думал о том, чтобы патчить ClientHello, убирая из него поддержку TLSv1.3, осуществляя по сути атаку на downgrade до TLSv1.2, но вовремя почитал описание TLSv1.3. В нём реализовано аж ДВА механизма по предотвращению подобных даунгрейдов, поэтому вариант плохой.

...И здесь приходится прибегать к экстренным мерам. На самом деле этот метод я реализовал даже первым, и он по сути и является сутью метода peek and splice у Squid-а.

Мы не можем просто взять и вычитать сертификат. Поэтому мы просто открываем свой собственный коннект к серверу и пытаемся сами совершить tls-handshake. Получаем из него сертификат с CN=megafon.ru? Значит нас блокируют. Нет? Значит всё в порядке. И нам не сильно важно какой другой - да пусть даже мы дисконнект получим. Главное, что мы не получили сертификат, который является флагом блокировки.

Единственным минусом такого подхода является то, что в случае, если Мегафон начнёт поддерживать eSNI, то коннекты к его сайту будут проксироваться, но пока что SNI у него вполне незашифрованный. Да и, на самом деле, сертификат там самоподписанный отдаётся, так что можно и углубить проверку.

А что дальше с заблокированным трафиком-то делать?

Итак, мы понимаем, что сайт блокируется. Что делать? Лучшее и наиболее универсальное что я придумал - это SOCKS5 прокси. Протокол проще некуда, к нему есть удобная клиентская реализация, которую чуть доработать - и можно пользоваться. Вдобавок, SOCKS5 реализован в Tor и SSH. Поднять SOCKS5-сервер - дело пяти минут.

Особенности некоторых сайтов и приятный бонус

Во время тестов я натолкнулся на весьма странное поведение linkedin.com. Дело в том, что при его запросе почему-то я вообще не получал никакого ответа, коннект просто уходил в таймаут. Я решил, что если коннект таймаутится, есть смысл попробовать его тоже перенаправить через VPN - хуже точно не будет.

Каково же было моё удивление, когда точно такое же поведение было и через VPN. Причём, с подключением через VPN IPv4 не соединялся, а по IPv6 вполне всё работало.

Тут я вспомнил, что у SOCKS5 есть два режима подключения к удалённому хосту - по IP и по хосту. Поэтому я реализовал следующую обработку соединения (Hostname мы получаем из SNI):

direct IP => direct Hostname => socks5 IP => socks5 Hostname

С таймаутом на соединение в 2 секунды. В чём смысл? Если вдруг оказывается, что наша машина, на котором развёрнут NGINX оказывается IPv6-capable, а изначальный клиент нет, то мы сможем спроксировать трафик через IPv6, при том, что клиент будет думать что соединяется по IPv4. Аналогично и с прокси-сервером.

А что насчёт производительности?

Конкретно в моём случае, основные затраты на производительность - при первичном подключении к серверу. Но, честно говоря, даже они минимальны. После соединения потребление что CPU, что RAM остаётся почти незаметным. Конечно, Netgear R7000 достаточно мощная машинка - двухядерник с 1 GHz ядрами и 256 МБ оперативки - но он даже не нагружается на 10% во время обычного использования (активного сёрфинга, просмотра видео на YouTube). При прогоне спидтеста потребление вообще остаётся на уровне 5% CPU. Самую большую нагрузку составил как ни странно сайт по проверке замедления t.co (https://speed.gulag.link/) - вот там ядра напрягаются до 80% на одном ядре (и около 25% на другом), но при этом так и не достигают 100%.

Итак, переходим к практике - что нам для этого потребуется?

В качестве железа подойдёт почти что всё что угодно, на чем можно запустить Linux. В моём случае, я запускаю это всё на Netgear R7000 с ARM-процессором внутри, и кастомной прошивкой с версией ядра всего лишь 2.6.36. В общем и целом теоретически запустится на практически любом Linux-е с версией ядра хотя бы 2.6.32.

Поскольку решение не совсем стандартное, придётся пересобирать NGINX/OpenResty из исходников. Я успешно собирал прямо на самом роутере - занимает некоторое время, но не так чтобы бесконечное.

Конкретно нужно:

  1. OpenResty - это NGINX с LuaJit и основными Lua модулями. Я скачивал релизный тарболл из их раздела загрузок.

  2. lua-resty-openssl и его C-модуль к NGINX lua-resty-openssl-aux-module. Нужен для получения и разбора сертификатов SSL-сессий.

  3. Мой самописный C-модуль к NGINX и Lua-биндинг lua-resty-getorigdest-module для получения информации об IP и порте того, куда изначально обращался клиент.

  4. lua-struct для парсинга бинарных пакетов (в частности, поиска сертификата после ServerHello).

  5. Мой форк SOCKS5 Lua-клиента lua-resty-socks5 уважаемого @starius, которому была добавлена возможность соединяться через SOCKS5 не только по хостнейму, но и по IP-адресу.

  6. SOCKS5 прокси-сервер для проксирования заблокированного трафика - например, socks-прокси TOR-а, либо обыкновенный ssh -D. Для VPN-сервера - надо установить его на самом VPN-сервере и проксировать через него. Настройка socks-прокси выходит за рамки этой статьи.

Также я исхожу из того, что запускаете вы это на устройстве, маршрутизирующем ваш доступ к интернету - в моём случае это роутер. Оно должно завестись в том числе и на локалхосте, но для этого придётся садаптировать правила iptables. Также, исхожу из того, что трафик от клиентов приходит с br0 устройства.

Подготовка и сборка

Предполагаю, что сборку осуществляем в /home/username/build. Устанавливать будем в /opt/nginxdpi

  1. Разархивируем тарболл openresty, делаем git clone всем указанным выше дополнениям. Для удобства переименовываем папку openresty-X.Y.Z.V в openresty.

  2. Переходим в /home/username/build/openresty, выполняем:

    ./configure --prefix=/opt/nginxdpi --with-cc=gcc \
    --add-module=/home/username/build/lua-resty-openssl-aux-module \
    --add-module=/home/username/build/lua-resty-openssl-aux-module/stream \
    --add-module=/home/username/build/lua-resty-getorigdest-module/src

  3. Выполняем make -j4 && make install, ждём пока всё соберётся...

  4. После сборки копируем:

cp -r /home/username/build/lua-resty-getorigdest-module/lualib/* /opt/nginxdpi/lualib/ cp -r /home/username/build/lua-resty-openssl/lib/resty/* /opt/nginxdpi/lualib/resty/cp -r /home/username/build/lua-resty-openssl-aux-module/lualib/* /opt/nginxdpi/lualib/cp /home/username/build/lua-resty-socks5/socks5.lua /opt/nginxdpi/lualib/resty/cp /home/username/build/lua-struct/src/struct.lua /opt/nginxdpi/lualib/

Готово! Можно приступать к конфигурированию.

Конфигурация

Вся логика содержится в следующем конфигурационном файле:

nginx.conf
user root;worker_processes auto;events {    worker_connections 1024;}stream {    preread_buffer_size 16k;    server {        listen 30443 so_keepalive=on;        tcp_nodelay on;        #error_log /opt/nginxdpi/cfg/nginx/error.log info;        error_log off;        lua_socket_connect_timeout 2s;        ssl_preread on;        content_by_lua_block {            local prefer_hosts = false;            local prefer_socks_hosts = true;            local host = nil;                                local socket = ngx.req.socket(true);            socket.socktype = "CLIENT";            local god = require("resty.getorigdest");            local dest = god.getorigdest(socket);            local sni_name = ngx.var.ssl_preread_server_name;            ngx.log(ngx.DEBUG, dest);            ngx.log(ngx.DEBUG, sni_name);            local openssl = require("resty.openssl");            openssl.load_modules();            local ngx_re = require("ngx.re");            local cjson = require("cjson");            local socks5 = require("resty.socks5");            local struct = require("struct");            local dests = ngx_re.split(dest, ":");            local dest_addr = dests[1];            local dest_port = tonumber(dests[2]);            local connect_type_last = nil;            local socket_create_with_type = function(typename)                local target = ngx.socket.tcp();                target.socktype = typename;                return target;            end            local socket_connect_dest = function(target)                local ok = nil;                local err = nil;                if (prefer_hosts == true and host ~= nil) then                    ok, err = target:connect(host, dest_port);                    connect_type_last = "host";                    if (err ~= nil) then                        local socktype = target.socktype;                        target = socket_create_with_type(socktype);                        ok, err = target:connect(dest_addr, dest_port);                        connect_type_last = "ip";                    end                else                    ok, err = target:connect(dest_addr, dest_port);                    connect_type_last = "ip";                    if (err ~= nil and host ~= nil) then                        local socktype = target.socktype;                        target = socket_create_with_type(socktype);                        ok, err = target:connect(host, dest_port);                        connect_type_last = "host";                    end                end                if (ok == nil and err == nil) then                    err = "failure";                end                return target, err;            end            local intercept = false;            local connected = false;            local upstream = socket_create_with_type("UPSTREAM");            local bufsize = 1024*16;            local peek, err, partpeek = socket:receiveany(bufsize);            if (peek == nil and partpeek ~= nil) then                peek = partpeek;            elseif (err ~= nil) then                ngx.log(ngx.WARN, err);            end            if (dest_port == 80 or ngx.re.match(peek, "(^GET \\/)|(HTTP\\/1\\.0[\\r\\n]{1,2})|(HTTP\\/1\\.1[\\r\\n]{1,2})") ~= nil) then                local http_host_find, err = ngx.re.match(peek, "[\\r\\n]{1,2}([hH][oO][sS][tT]:[ ]?){1}(?<host>[0-9A-Za-z\\-\\.]+)[\\r\\n]{1,2}");                local http_host = nil;                if (http_host_find ~= nil and http_host_find["host"] ~= false) then                    http_host = http_host_find["host"];                end                if (http_host ~= nil and host == nil) then                    host = http_host;                end                upstream = socket_connect_dest(upstream);                local ok, err = upstream:send(peek);                if (err ~= nil) then                    ngx.log(ngx.WARN, err);                end                local data, err, partdata = upstream:receiveany(bufsize);                if (data == nil and partdata ~= nil) then                    data = partdata;                elseif (err ~= nil) then                    ngx.log(ngx.WARN, err);                end                if (data ~= nil) then                    local match = "HTTP/1.1 307 Temporary Redirect\r\nLocation: http://m.megafonpro.ru/rkn";                    local match_len = string.len(match);                    local extract = data:sub(1, match_len);                    if (match == extract) then                        upstream:close();                        upstream = socket_create_with_type("UPSTREAM");                        intercept = true;                    else                        connected = true;                        local ok, err = socket:send(data);                        if (err ~= nil) then                            ngx.log(ngx.WARN, err);                        end                        peek = nil;                    end                end            elseif (dest_port == 443 or sni_name ~= nil) then                local serv_host = nil;                                if (sni_name ~= nil and host == nil) then                    host = sni_name;                end                                local err = nil;                upstream, err = socket_connect_dest(upstream);                ngx.log(ngx.DEBUG, err);                                local ok, err = upstream:send(peek);                if (err ~= nil) then                    ngx.log(ngx.WARN, err);                end                                -- Parsing the ServerHello packet to retrieve the certificate                local offset = 1;                local data = "";                local size = 0;                local servercert = nil;                upstream:settimeouts(2000, 60000, 1000);                while (servercert == nil) do                    if (size == 0 or offset >= size) then                        local data2, err, partdata = upstream:receiveany(bufsize);                        if (data2 ~= nil) then                            data = data .. data2;                        elseif (data2 == nil and partdata ~= nil) then                            data = data .. partdata;                        elseif (err ~= nil) then                            ngx.log(ngx.WARN, err);                            break;                        end                        size = data:len();                        ngx.log(ngx.DEBUG, "UPSTREAM received for ServerHello certificate retrieval! "..size);                    end                    ngx.log(ngx.DEBUG, offset);                    if (offset < size) then                        local contenttype, version, length, subtype = struct.unpack(">BHHB", data, offset);                        if (contenttype ~= 22) then                            -- We got something other than handshake before we retrieved the cert, probably the server is sending the cert encrypted, fallback to legacy cert retrieval                            break;                        elseif (subtype ~= 11) then                            offset = offset + 5 + length;                        else                            local suboffset = offset + 5;                            local _, _, _, _, certslength, _, firstcertlength = struct.unpack(">BBHBHBH", data, suboffset);                            -- We need only the first cert, we don't care about the others in the chain                            local firstcert = data:sub(suboffset + 1 + 3 + 3 + 3, firstcertlength);                            servercert = firstcert;                        end                    end                end                upstream:settimeouts(2000, 60000, 60000);                                local cert = nil;                if (servercert ~= nil) then                    cert = openssl.x509.new(servercert, "DER");                    ngx.log(ngx.DEBUG, "Cert retrieved from ServerHello peeking");                else                    -- We employ a legacy method of gathering the certificate, involving connecting to the server and doing a SSL handshake by ourselves                    local serv = socket_create_with_type("TLSCHECK");                    local err = nil;                    serv, err = socket_connect_dest(serv);                    ngx.log(ngx.DEBUG, err);                    local session, err = serv:sslhandshake(false, sni_name, false, false);                    ngx.log(ngx.DEBUG, err);                    local sslsess, err = openssl.ssl.from_socket(serv);                    ngx.log(ngx.DEBUG, err);                    if (sslsess ~= nil) then                        cert = sslsess:get_peer_certificate();                        ngx.log(ngx.DEBUG, "Cert retrieved from secondary handshake");                    end                    serv:close();                end                                -- Parsing the certificate                if (cert ~= nil) then                    local sub = cert:get_subject_name();                    local alt = cert:get_subject_alt_name();                    for k, obj in pairs(sub) do                        ngx.log(ngx.DEBUG, k.." "..cjson.encode(obj));                        if (serv_host == nil and k == "CN" and obj.blob:find("*", 1, true) == nil) then                            serv_host = obj.blob;                        end                        if (k == "CN" and obj.blob == "megafon.ru" and (sni_name == nil or sni_name:find("megafon.ru", 1, true) == nil)) then                            ngx.log(ngx.DEBUG, k.." "..obj.blob);                            upstream:close();                            upstream = socket_create_with_type("UPSTREAM");                            intercept = true;                                                    end                    end                    for k, obj in pairs(alt) do                        ngx.log(ngx.DEBUG, k.." "..cjson.encode(obj));                        if (serv_host == nil and k == "DNS" and obj:find("*", 1, true) == nil) then                            serv_host = obj;                        end                    end                end                if (serv_host ~= nil and host == nil) then                    host = serv_host;                end                                if (intercept ~= true) then                    connected = true;                    local ok, err = socket:send(data);                    if (err ~= nil) then                        ngx.log(ngx.WARN, err);                    end                    peek = nil;                end            end            if (connected == false and intercept == false) then                local err = nil;                upstream, err = socket_connect_dest(upstream);                if (err ~= nil) then                    intercept = true;                    upstream = socket_create_with_type("UPSTREAM");                end            end            if (intercept == true) then                local ok, err = upstream:connect("192.168.120.1", 45213);                ngx.log(ngx.DEBUG, err);                ok, err = socks5.auth(upstream);                ngx.log(ngx.DEBUG, err);                local ok = nil;                local err = nil;                if (prefer_socks_hosts == true and host ~= nil) then                    ok, err = socks5.connect(upstream, host, dest_port);                    connect_type_last = "socks_host";                    if (err ~= nil) then                        upstream = socket_create_with_type("UPSTREAM");                        upstream:connect("192.168.120.1", 45213);                        ok, err = socks5.auth(upstream);                        ok, err = socks5.connect_ip(upstream, dest_ip, dest_port);                        connect_type_last = "socks_ip";                    end                else                    ok, err = socks5.connect_ip(upstream, dest_addr, dest_port);                    connect_type_last = "socks_ip";                    if (err ~= nil and host ~= nil) then                        upstream = socket_create_with_type("UPSTREAM");                        upstream:connect("192.168.120.1", 45213);                        ok, err = socks5.auth(upstream);                        ok, err = socks5.connect(upstream, host, dest_port);                        connect_type_last = "socks_host";                    end                end                ngx.log(ngx.DEBUG, err);            end            upstream:setoption("keepalive", true);            upstream:setoption("tcp-nodelay", true);            upstream:setoption("sndbuf", bufsize);            upstream:setoption("rcvbuf", bufsize);            ngx.log(ngx.INFO, "RESULT: "..tostring(host).."/"..dest_addr..":"..dest_port.." intercept:"..tostring(intercept).." connecttype:"..connect_type_last);            local ok = false;            if (peek ~= nil and peek:len() > 0) then                ok, err = upstream:send(peek);                if (err ~= nil) then                    ngx.log(ngx.WARN, err);                end            else                ok = true;            end            local pipe = function(src, dst)                while true do                    local data, err, partial = src:receiveany(bufsize);                    local errs = nil;                    local ok = false;                    if (data ~= nil) then                        ok, errs = dst:send(data)                    elseif (data == nil and partial ~= nil) then                        ok, errs = dst:send(partial)                    elseif (err == 'closed') then                        ngx.log(ngx.WARN, src.socktype..":"..err);                        return;                    elseif (err ~= nil and err ~= "timeout") then                        ngx.log(ngx.WARN, src.socktype..":"..err);                    end                    if (errs == 'closed') then                        ngx.log(ngx.WARN, dst.socktype..":"..errs);                        return;                    elseif (errs ~= nil) then                        ngx.log(ngx.WARN, dst.socktypeerr..":"..errs);                    end                end            end            if (ok ~= false) then                local co_updown = ngx.thread.spawn(pipe, upstream, socket);                local co_downup = ngx.thread.spawn(pipe, socket, upstream);                ngx.thread.wait(co_updown);                ngx.thread.wait(co_downup);            end            upstream:close();            ngx.flush(true);            socket:shutdown("send");        }    }}

Можно было бы наверное разделить lua код от конфига, но мне было немного лень =)

Замените 192.168.120.1 и 45213 на хост и порт вашего SOCKS5-сервера!

Размещаем его в /opt/nginxdpi/cfg/nginx.conf

Создаём файл /opt/nginxdpi/cfg/start.sh со следующим содержимым:

start.sh
#!/bin/sh/opt/nginxdpi/bin/openresty -c /opt/nginxdpi/cfg/nginx.confiptables -t nat -A PREROUTING -i br0 -p tcp -m tcp --dport 80 -j REDIRECT --to-ports 30443iptables -t nat -A PREROUTING -i br0 -p tcp -m tcp --dport 443 -j REDIRECT --to-ports 30443

Обратите внимание, что br0 - это интерфейс локальной сети, с которого подключаются клиенты!

Даём start.sh права на выполнение chmod +x /opt/nginxdpi/cfg/start.sh, и наконец запускаем всё это добро (от рута! В принципе теоретически может заработать и без рута, но я не пробовал...):

/opt/nginxdpi/cfg/start.sh

После этого весь ваш HTTP и HTTPS трафик будет проксироваться через этот сервер.

Что можно сделать ещё?

На самом деле есть несколько вещей, которые можно доработать, разной степени сложности.

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

Во-вторых, пока что совершенно не поддерживается IPv6. Добавить поддержку на самом деле несложно... С другой стороны, как правило IPv6 трафик сейчас фильтруется редко (если вообще где-то фильтруется).

В-третьих, опять таки не поддерживается UDP. И если на текущем этапе это не столь критично (мало что по UDP сейчас блокируется), то с развитием HTTP3/QUIC эта проблема будет гораздо более критичной. Правда как проксировать QUIC я пока понятия не имею, да и DTLS отличается от TLS... Там будет хватать своих проблем. Это, наверное, самая сложная задача.

И в завершение...

На самом деле этот механизм можно использовать и для полноценного DPI. По сути мы получаем полноценную TCP-сессию, которую можем инспектировать "на лету" - достаточно, по сути, пропатчить pipe-функцию. С другой стороны, смысла в анализе сырых данных после завершения SSL-handshake нынче мало, а зашифрованного трафика становится только больше...

Этот же метод в принципе позволяет и записывать трафик - закон Яровой исполнять, например. Надеюсь я не открыл сейчас ящик Пандоры... =)

...вдруг кому-то из провайдеров этот метод позволит сэкономить на DPI-софте и уменьшить цену тарифов? =) Кто знает.

Скажу честно, местами мне было лень обрабатывать ошибки, поэтому возможно странное поведение. Если обнаружите какие-то ошибки пишите, попробую поправить - но в принципе я сижу через этот DPI сейчас сам, и проблем особых не заметил (а те что заметил - пофиксил).

Желаю удачи в адаптации под своих провайдеров!

Подробнее..

Домашний медиа сервер minidlna

30.04.2021 02:06:43 | Автор: admin

На кой черт это надо?

Так сложились обстоятельства, что мне удалось скоммуниздить старенький ПК, да и чтобы он просто непылился, так как такое старье продавать за 5-7 тысяч (цена в моем регионе за подобную конфигурацию) мне стыдно, а получать за него 3 тысячину такое. Ярешил, сделаюдома небольшую библиотеку фильмов, музыки, да и у жены очень много фотографий, которые её очень дороги. Задумка была следующей.

  • Некиймедиа сервер,который будет транслировать медиаконтент на телевизор.

  • Некое локальное хранилище для файлов, порой приходится с флешками побегать, а то жесткого диска на 500ГБ не хватает.

  • Торрент клиент, которыйбудет качать все что я пожелаю.

Целипоставлены, осталось их реализовать.Вряд лиопытные пользователи найдутздесьчто-то полезное для себя, наверное, тольконачинающиепользователи Linux, так как все действия очень простые в исполнении.

Установка Linux

То, как поставитьлинукс,я думаю, не стоит говорить. Я просто опишу что я сделал после установки.Во-первых, пока компьютер был возле меня, я настроил статический адрес в файле /etc/netplan/00-installer-config.yaml

# This is the network config written by 'subiquity'#ens34network:ethernets:ens34:addresses:- 192.168.31.5/24gateway4: 192.68.31.1nameservers:addresses: [192.168.31.1, 8.8.8.8]optional: trueversion: 2

Думаю, тут вроде понятно, что где что и как.Конечно,правильнее было быпривязатьip-адрес через роутер, но у меня роутер Xiaomi, и там все виероглифах... Я просто сделал статический ip-адрес на компьютере. После этого я его отнес в подвал и подключил к роутеру и сел за рабочую машину. Теперь стоит создать пару ssh-ключей для удобства

maks@Kubuntu:~/.ssh$ ssh-keygen -f ~/.ssh/homeGenerating public/private rsa key pair.Enter passphrase (empty for no passphrase): Enter same passphrase again: Your identification has been saved in /home/maks/.ssh/homeYour public key has been saved in /home/maks/.ssh/home.pubThe key fingerprint is:SHA256:i4rpsCZdQq5S+M2JyPWavEtCZqjsIJj2rHa69dzPgz0 maks@KubuntuThe key's randomart image is:+---[RSA 3072]----+|                 ||                 ||                 ||. .              ||.B      S        ||O.+..  . .       ||OX.O... +        ||X*@+Bo...E       ||**OO=o ..oo      |+----[SHA256]-----+maks@Kubuntu:~/.ssh$ 

Ну и отправил ключ на медиа сервер

$ ssh-copy-id -i ~/.ssh/home.pub maks@192.168.31.5/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/home/maks/.ssh/home.pub"/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed%sudo   ALL=(ALL:ALL) NOPASSWD:ALL/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keysmaks@192.168.31.5's password: Number of key(s) added: 1Now try logging into the machine, with:   "ssh 'maks@192.168.31.5'"and check to make sure that only the key(s) you wanted were added.

Теперь настрою файл ~/.ssh/config

Host home        HostName        192.168.31.5        User            maks        IdentityFile    /home/maks/.ssh/home

И теперь для подключения достаточно написать ssh home,и последний момент, которой наверное не стоит делать, это убрать ввод пароля при использовании sudo. Для этого достаточно добавить NOPASSWD в файл /etc/sudoerc что бы вышло так

%sudo   ALL=(ALL:ALL) NOPASSWD:ALL

Установка transmission-daemon

И так, начну с торрент-клиента transmiss-daemon.

$ sudo apt update && sudo apt upgrade -y && sudo apt install -y transmission-daemon

Так обновятся все пакеты и установится transmiss-daemon. Вообще не рекомендуется ставить без ведома все подряд, но так как у меня система только чтоустановленная, установятся только обновления системных пакетов, ивряд тличто-тосломается. После установки transmission-daemon нужно его выключить для того что произвести настройку.

$ sudo systemctl stop transmission-daemon.service 

Если править файл конфигурации /etc/transmission-daemon/settings.json в тот момент как демон работает, изменения не сохранятся. И так мне надо поправить несколько строк, но для начала создам папки куда будет всекачаться. Я создам все папки для медиа контента в /media и будет /media/torrent/downloads для загружаемых файлов и /media/torrent/complete для файлов которые уже загрузились

$ mkdir -p /media/torrent/{downloads,complete}

сразу сменю владельца на папки для загрузки на debian-transmission

$ sudo chown -R debian-transmission:debian-transmission /media/torrent

Теперь можно приступать к настройке transmission. Как уже говорилось, все настройки хранятся в файле /etc/transmission-daemon/settings.json. Нам необходимо поправить несколько строк, а именно

  • "download-dir": "/var/lib/transmission-daemon/downloads"- указываем папку куда будутпомещатьсязагруженныефайлы, у меня в /media/torrent/complete Лучше указывать полный путь

  • "incomplete-dir": "/var/lib/transmission-daemon/Downloads"- указываем папку в которойбудут хранитьсяне докачавшиесяфайлы, у меня /media/torrent/downloads

  • "incomplete-dir-enabled": false- указываем true что бы хранитьскачавшиесяфайлы отдельно

  • "rpc-authentication-required": true - меняем на false для отключения авторизации по логину и паролю, это пожеланию

  • "rpc-host-whitelist-enabled": true- меняем на false для отключения использования "белого списка ip-адресов"

  • "rpc-password":"{a3edc70552a46d634e81ad9fabca6f51f9303197F8.No4L4"- указываем свой пароль для авторизации, если вы оставили авторизацию по паролю. Так же сразу хочу сказать, что после того как вы включите transmiss-daemon то пароль автоматическизашифруется

  • "rpc-username": "transmission"- указываем логин для авторизации

  • "rpc-whitelist-enabled": true-опять-такиотключаем авторизацию по разрешенным ip-адресам меняя true на false соответственно

Изапускtransmission

sudo systemctl start transmission-daemon.service

и захожу в web-панель transmission по ip-адресу и порту 9091, у меня это 192.168.31.5:9091

web-интерфейс transmissionweb-интерфейс transmission

transmission почти готов теперь настрою проксированиечерез NGINX

Установка NGINX и настройка проксирования

Я поставил весь nginx,нов теории достаточно и самого пакета nginx

sudo apt install -y nginx-full

После установкипроверю, работаетли веб сервер зайдя на ip-адрес машины

Приветственное сообщение от NginxПриветственное сообщение от Nginx

Теперь выполню настройку nginx. Всеконфигурационныефайлы находятся в /etc/nginx/*. Для начала я удалю все лишнее из файла /etc/nginx/nginx.conf и приведу его к такому виду

nginx.conf
user www-data;worker_processes auto;pid /run/nginx.pid;include /etc/nginx/modules-enabled/*.conf;events {        worker_connections 768;}http {        sendfile on;        tcp_nopush on;        types_hash_max_size 2048;        include /etc/nginx/mime.types;        default_type application/octet-stream;        access_log /var/log/nginx/access.log;        error_log /var/log/nginx/error.log;        include /etc/nginx/conf.d/*.conf;        include /etc/nginx/sites-enabled/home.conf;}

И создам файл /etc/nginx/sites-enabled/home.conf с базовым содержимым

home.conf
server {        listen 80;        root /var/www/home;        server_name home.ru;        location / {                index index.html;                try_files $uri $uri/ =404;        }}

Немногообъяснюфайл.listen80 указываем на каком порту будет слушаться сайт, root /var/wwww/home я создал папку от имени root и поместил туда простую страницу html взятую из интернетадляпроверки как будет все работать.

html страница скачанная из интернетаhtml страница скачанная из интернета

Так же я добавил строку 192.168.31.5 home.ru в рабочей машине в файл /etc/hosts для того что бы сайт моготкрыватьсяв браузере с рабочей машины по адресу home.ru что и указал в файле home.conf server_name home.ru

Настройка проксирования transmission-daemon черезnginx

NGINXвообще мощная фигня, и для того что бы открывать web-панель transmisson через http://home.ru/transmission/ его будет более чем достаточно. Все правки я буду делать в файле /etc/nginx/sites-enabled/home.conf для начала добавлю новый location и в нём укажу что бы онпроксировалзапрос с home.ru/transmission/ на 127.0.0.1:9091

блок location для transmission
        location /transmission/ {                proxy_pass http://localhost:9091 ;        }
transmission-daemon просит X-Transmission-Session-Idtransmission-daemon просит X-Transmission-Session-Id

Перенаправление пошло, но вот только transmission жалуется на отсутствие хедера X-Transmission-Id. Немного поковырявши хедеры, так же добавив 2 блока location мне удалось завести transmission. Вот полный файл конфигурации

полный файл home.conf
server {        listen 80;        root /var/www/home;        server_name home.ru;        location / {                index index.html;                try_files $uri $uri/ =404;        }        location /transmission/ {                proxy_read_timeout 300;                proxy_pass_header  X-Transmission-Session-Id;                proxy_set_header   X-Forwarded-Host $host;                proxy_set_header   X-Forwarded-Server $host;                proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;                proxy_pass         http://127.0.0.1:9091/transmission/web/;        }        location /rpc {                proxy_pass         http://127.0.0.1:9091/transmission/rpc;        }               location /upload {                proxy_pass         http://127.0.0.1:9091/transmission/upload;        }}
transmission заработалtransmission заработал

Написание скрипта

Так сложилось, что у меня телевизор Samsun толком не воспроизводит видео в формате avi, а если и воспроизводитто как то криво. Нельзя перемотать, длительность фильма такаячто люди стока не живут, да и самое неприятное заключается в том что посреди фильма он можетпростооборваться. А вот формат mkv он ест нормально. И поэтому я решил написатьнебольшойскрипт на bash который будет запускать сам transmission-daemon после загрузки файла. Немного подумав над логикой и посмотрев как качаются разные файлы, выстроил себе цепочку действий.Во-первыхнадо понять,скачаласьпапка или файл. Если папка, тосоздатьтакую жепапку в директории для DNLA сервера, если файл, то пропускаем. Возвращаемся к папке, теперь смотрим на содержимое папки, если файлы avi то конвертируем их через ffmpeg в конечную папку, если в папке файлы mkv то просто перемещаю их, но если там то-то другое, просто кидаю в /tmp для дальнейшего анализа. Теперь возвращаемся к началу скрипта, если же это непапка, афайл то, смотрим на формат и при необходимости конвертируем. Вроде ничего сложного, вот сам скрипт

скрипт download.sh
#!/bin/bashCOMPLETE="путь_к_конечным_файлам"CHAT_ID="сюда_чат_айди"TELEGRAM_BOT="сюда_айди_телеграм_бота"#Функция для обработки папкиdir () {        #Создаю папку для медиасервера        mkdir $COMPLETE/$TR_TORRENT_NAME        #Прохожу по всем файлам в папке        for movie in $(ls $TR_TORRENT_DIR/$TR_TORRENT_NAME)        do                #Если фильм ави                if [[ $movie == *avi ]]; then                        #Конвертирую его в mkv                        ffmpeg -i $TR_TORRENT_DIR/$TR_TORRENT_NAME/$movie $COMPLETE/$TR_TORRENT_NAME/$movie.mkv                #Если фильм mkv                elif [[ $movie == *mkv ]]; then                        #Перемещаю фильм в конечную папку                        mv $TR_TORRENT_DIR/$TR_TORRENT_NAME/$movie $COMPLETE/$movie                #Если непонятный файл, отправляю его в /tmp                else                        mkdir /tmp/$TR_TORRENT_NAME                        mv $TR_TORRENT_DIR/$TR_TORRENT_NAME/$movie /tmp/$TR_TORRENT_NAME/$movie                        echo -e "$movie\nбыл непонятен скрипту. Нужно сюда заглянуть\n/tmp/$TR_TORRENT_NAME/$movie" >> /tmp/$TR_TORRENT_NAME.message                fi        done        echo -e "Торент\n$TR_TORRENT_NAME\nзагружен" >> /tmp/$TR_TORRENT_NAME.message}#Если скачался просто файлfile () {        #Так же проверяю на формат        if [[ $TR_TORRENT_DIR/$TR_TORRENT_NAME == *avi ]]; then                #Крнвертирую                ffmpeg -i $TR_TORRENT_DIR/$TR_TORRENT_NAME $COMPLETE/$TR_TORRENT_NAME.mkv        elif [[ $TR_TORRENT_DIR/$TR_TORRENT_NAME == *mkv ]]; then                #Перемещаю                mv $TR_TORRENT_DIR/$TR_TORRENT_NAME $COMPLETE/$TR_TORRENT_NAME        else                mkdir /tmp/$TR_TORRENT_NAME                mv $TR_TORRENT_DIR/$TR_TORRENT_NAME /tmp/$TR_TORRENT_NAME/$TR_TORRENT_NAME                echo -e "Торрент\n$TR_TORRENT_NAME\nзагружен и не понятен скрипту" >> /tmp/$TR_TORRENT_NAME.message        fi        echo -e "Торрент\n$TR_TORRENT_NAME\nзагружен" >> /tmp/$TR_TORRENT_NAME.message}send_message () {        curl https://api.telegram.org/bot$TELEGRAM_BOT/sendMessage?parse_mode=markdown -d chat_id=$CHAT_ID -d text="$(</tmp/$TR_TORRENT_NAME)"}if [[ -d $TR_TORRENT_DIR/$TR_TORRENT_NAME ]]; then        direlse        filefisend_message#Удаляю старые файлы если они естьrm -rf $TR_TORRENT_DIR/$TR_TORRENT_NAMErm -rf /tmp/$TR_TORRENT_NAME.message

Скрипт далек от идеала, может потом его доработаю под собственные нужды, например что бы скачанные файлы.iso перемещались сразу в папку дляшаринга. Так же он отправляет в телеграмуведомлениео загруженном файле, и если есть ошибки, он тоже об этом сообщит. Скрипт писал на быструю руку, и он не проходит повложеннымпапкам, если будет в этом необходимость то его поправлю. На данный момент он выполняет то что мне и надо. После того как скрипт написан, и яопределилсяс конечными папками, перемещаю скрипт к transmission

sudo mv download.sh /etc/transmission-daemon/ 

даю скрипту права на исполнения

sudo chmod +x download.sh

и меняю владельца

sudo chown debian-transmission:debian-transmission /etc/transmission-daemon/download.sh

После останавливаю transmission-daemon

sudo systemctl stop transmission-daemon

и меняю 2 параметра в /etc/transmission-daemon/setings.json

  • "script-torrent-done-enabled": false

  • "script-torrent-done-filename": ""

done-enabled перевожу на true,то-естьвключаю активацию скрипта по окончаниюзагрузки, аdone-file указываю полный путь к файлу, можно указать только название файла если файл лежит в папке transmission, но мне легче указать полный путь.

"script-torrent-done-enabled": true,"script-torrent-done-filename": "/etc/transmission-daemon/download.sh",

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

В качествемедиа сервераDNLA я выбрал minidlna. Ставится из репозитория ubuntu, прост в настройке.

sudo apt install -y minidlna

Файл конфигурации находится /etc/minidlna.conf, я в нем внес несколько правок, а именно, указал где находятся медиаконтент

  • media_dir=A,/media/gerbera/music

  • media_dir=P,/media/gerbera/photo

  • media_dir=V,/media/gerbera/movie

Указал где хранить базуданныхфайлов minidlna

db_dir=/var/cache/minidlna

Указал куда писать логи

log_dir=/var/log/minidlna

и указал имя сервера

friendly_name=Home_Media

и в конце перезапустил сервер

sudo systemctl restart minidlna

И получил результат

Напоследок

Писать о том как я настроил временно samba не буду. Есть очень много мануалов в которомвсе описано куда лучше, чем смогу описать я, но хочу поделится несколькими мыслями на будущие) Целый комп для этого излишнее, тем более у меня есть роутер с прошивкой OpenWRT на котором можно все этореализовать, но жена мне раз дала идею сделать мониторинг цен. Ятакоеуже делал, написал небольшой скрипт на bash которыйпарсилстраницу, брал оттуда название товара, картинку и цену. Скриптзапускалсячерез cron. Идея неплохая, но для каждого сайта необходимо делать свой шаблон. И скорее всего сайты по типу Wildberries при авторизации будут показывать другую цену. Идею думаю реализовать на C++, которые будут братьссылкудля просмотра из БД и смотреть нацену. Если цена записанная в БД будет больше, тоотправлять сообщение в том жетелеграмео том что на такой-то товар ценнаизменилась. Да и для разных экспериментов и обучения можноподнимать серверевиртуальную машину и её ковырять. Так же есть идея реализовать некую запись телепередач. У нас нет обычного телевидения, есть всего 1 канал XD, и жена смотрит несколько передач. Думаю просто настроить запись их в тот же медиа сервер. Но все это планы, желание на исполнения не много.

P.S.

Я впервые пишу такой длинный текст, и вообще в первые куда-то пишу по мимо личных сообщений. Не судите строго.

Подробнее..

Recovery mode IT-стыд 2020

04.06.2021 04:18:40 | Автор: admin
Каин послеубийства своего брата Авелявзгляда на российское айти в 2020 коллаж автораКаин послеубийства своего брата Авелявзгляда на российское айти в 2020 коллаж автора

Это реинкарнация поста, наделавшего шума в начале года, но затем распубликованного по моей же вине. Но выводы сделаны и карнавал стыда продолжается. :-)

Привет, Хабр! В конце каждого года, на многих сайтах принято подводить различные итоги и Хабр не стал исключением. Лента наполняется темами типа топ ЯП по итогам 2020, топ 10 технологий, топ 20 работодателей, тысячи их. Но чего нет так это списка зашкваров года, которые подарили нам IT-компании и которые вызывают чувство испанского стыда. Надо сделать, подумал я и составил такой топ сам. Почему, зачем и собственно сами герои под катом. И прошу не судить строго, это мой первый, чисто развлекательный и субъективный пост. Сразу предупрежу о моей довольно специфичной манере изъясняться, которая возможно напомнит вам те времена, когда рунет был юн, дик и более-менее свободен.


Это уже было в Симпсонах

К моему удивлению, я не обнаружил такого подобного позорного списка ни на одном из более-менее известных ресурсов. А такого, чтобы кто-то делал это на регулярной основе каждый год и вовсе нет. В 2007 были жалкие попытки в виде создания Антипремии Рунета, которая задумывалась как поощрение тех, кому на небезызвестной и мусорной Премии Рунета не хватило места. Продержалась Антипремия 7 лет, после чего цирк сгорел, аклоуныоргкомитет разбежался. Впрочем, даже к лучшему. С тех пор никаких попыток не делалось.

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

Если пост зайдёт общественности каждый год буду его актуализировать. Более чем уверен, что на данную тему компании натравят сочувствующих гребцов и заминусят в хлам, а некоторые и без указки придут отстаивать родную галеру, но на такие жертвы придётся пойти, ради всеобщего блага (и я ни капли не шучу, когда так говорю), как говорится чтобы помнили. Некоторые действия забывать нельзя, покуда они не искуплены равноценными делами. Так мы подошли к пояснению, зачем я это делаю.

Зачем

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

Сразу оговорюсь, что некоторые инциденты из перечисленных начались ещё в 2019, но основное развитие получили в 2020, что и позволяет зачислить их в этот перечень. Вся информация взята из публичных источников, а вольная трактовка событий являет собой набор оценочных суждений автора. Посмотрим же, какой стыд нам подарил уходящий год.

Опенсорсеры года Wargaming

Ещё 17 декабря 2019 года картофельная танковая фабрика оскандалилась тем, что начала юридически преследовать своих бывших сотрудников в судах Беларуси, Кипра и США за работу над опенсорс проектом движка Framework/DAVA Engine, который она официально развивала до весны 2018 года. Поддержка проекта в качестве опенсорс официально декларировалась Wargaming на многих конференциях, в статьях, интервью и других источниках (пруф). Но потом, пять незадачливых выходцев из Wargaming, которые на данный момент работают в белорусской компании БлицТим, получили персональные иски и требование компенсации в виде $1 690 000.

Вышеупомянутые сотрудники ранее работали в минском центре разработки кипрской группы компаний Wargaming и принимали непосредственное участие в разработке игры World of Tanks Blitz и движка DAVA Framework/DAVA Engine. Затем после ухода данных сотрудников в собственный проект, картофельная фабрика золотых снарядов, предъявила им иск за размещение на гитхабе копий исходных текстов DAVA Framework и создание форков от этих копий на личные аккаунты в GitHub. Такой вот суровый опенсорц.

Сейчас сутяги из Wargaming делают вид, что ничего криминального не происходит, не комментируют ситуацию и всё также клепают посты в своембложикепро то, какие они молодцы. В Верховном суде Беларуси танкофилы уже проиграли. Пожелаю парням из БлицТим удачи, а киприотам-золотоснарядникам из Wargaming дарю своё презрение. Кто хочет следить за процессом может делать этотут.

Работники года ДИТ

Или Департамент информационных технологий города Москвы (чувствуете, как повеяло номенклатурой?). Про дела этих ребят вы наверняка слышали, ведь 2020 для них выдался ну очень продуктивный. Если пандемия застала вас в Москве, то, возможно, вы даже были вынуждены пользоваться их говносервисами (под угрозой штрафа).

Криво работающие Цифровые пропускаполучающие разрешениена отправку рекламы на следующие 10 лет, забагованный Социальный мониторинг, наспех собранный из кодатрекера мусоровозови обязывающий людей раз в 3 часа делать селфи и отсылать на проверку, откровенно шпионское Госуслуги СТОП Коронавирус (это реальное название от богов нейминга) все это поделки этих ребят. При этом их бюджет в 2020 составил 80 млрд руб. Как говорится, делайте выводы господа. Что забавно, по собственной оценке бракоделов из ДИТ все это позволило Москве избежать самого опасного сценария, который был в Италии и других европейских странах. Ага, конечно, особенно если отчётность по заболевшимможно подкручивать.

Бурление говн от этих сервисов было такое, что владыка ДИТ, амбассадор Бенгальского залива Эдик Лысенко, при поддержке своих миньонов дажеснизошел до диалогас сообществом, в котором ничего толком и не сказал, а возможность уличить дитовцев во лжи была бездарно слита, в виду нежелания Хабра вставать ни на какую позицию, а предоставить площадку и быть рупором. Ну вот это мы вне политики, ага. Дошло до того, что пообещали даже второй эпизод диалога, но уже с приглашённым экспертом, который будет притягивать дитовцев за слова, но разговоры так и остались разговорами, а ДИТ прекратили активность на Хабре. Действительно, чего этим холопам пояснять, господам некогда, нужно бюджеты осваивать.

Перевозчики года Яндекс.Такси

В рамках освещения ситуации поделу Ивана Голуноваиздание Медиазона получило комментарий пресс-секретаря ЯТНаташи Рожковойо том, что сервис передаёт силовикам данные о поездках своих клиентов по первому же запросу, даже если он прислан с почты на мейлру, а не в официальном виде. Именно от ЯТ один из экс-полицейских, сейчас проходящий обвиняемым по делу Голунова, узнал его домашний адрес.

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

Впрочем я не удивлен такими речами Наташи. До того, как стать прессеком Я.Такси, Наташаподвизалась на госслужбеначальницей специально для нее созданного, отдела информационной политики администрации Орловской области, прославившись там тем, что написала заявление в прокуратуру на учредителя одной из орловских газет и пыталась строить главреда другой (не были лояльны губеру), в перерывах радуя людей инструкциями в своем FB, как процедить воду через лифчик, если вы вдруг оказались в тайге. Пост был удален, но чиновница успела попасть в очередной скандал и втянуть туда своего бывшего начальника, врио губернатора Андрея Клычкова. В Яндекс берут только лучших, да? Вскоре после всего этого Наташа из Яги перескочила в Сбер. К чему тут такая большая вставка про эту даму, спросите вы? А к тому, чтобы вы знали, какие персонажи занимаются отмыванием имиджа.

Чуть позже, главарь пресс-службы ЯТ Вова Исаев заявил, что бумажный запрос таки был. Но в виду аффиляции Яши с госами я лично склонен думать, что им никто не мешает выдать данные хоть по свистку, не то что по запросу с личной почты на мейлру, а бумажный запрос можно и задним числом сделать. Тем более, что бумагу эту можно никому не показывать, ведь раскрывать содержание таких запросов запрещает, например, статья 161 УПК.

Яндекс.Такси нет в реестре организаторов распространения информации, то есть сервис имеет право не предоставлять правоохранителям никаких данных, но всё равно делает это, видимо, по дружбе. Отмечу, что отдельного блога на Хабре у компании нет, может, даже и к лучшему.

Клиентоориентированность года Яндекс.Go

В 2020 году сервис Я.Такси сменил название на Яндекс.Go, вобрав в себя еще несколько желтопузых сервисов. Поменялась оклейка машин, реклама, но не главное люди и процессы. А ведь не зря говорят, когда в борделе плохо идут дела менять нужно девочек, а не кровати. Именно потому, что люди и процессы остались теми же, зашквар ЯГи не заставил себя долго ждать.

25 декабря появиласьстатьяот клиента сервиса, который обнаружил, что цена за поездки на такси, для клиентов имеющих подписку Яндекс.Плюс (своеобразная программа лояльности) выше на 10%, чем цена для клиентов без Плюса. Что примечательно именно эти 10% хитрецы из ЯГи обещали своим клиентам в виде кэшбека баллами, которые можно потратить на желтые сервисы. По сути клиент сам переводит свои деньги в баллы, а ЯГой это подается как офигенный бонус.

От такого маневра, общественность натурально начала превращаться в нефритовый стержень и ринулась сравнивать цену на такси с Плюсом и без оного все сходилось с показаниями топикстартера. Так как топикстартер выбрал наиболее благоприятную дату и время публикации (вечер пятницы, время когда пиарщики ЯГи спокойно квасят в барах) то тема осталась без должного ответа со стороны сервиса. Впрочем, ответить было по факту нечего.

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

Связисты года Yota

Можно как угодно относиться к деятельности ФБК и их фюреру-блогеру (мне, например, нравится их развлекательный бложик на YouTube), но в 2020 с их подачи в этом посте можно упомянуть компанию Yota, котораянезаконно отключила связьодному из самых известных сотрудников ФБК Руслану Шаведдинову в момент, когда силовики ломали дверь в его квартиру, чтобы не дать ему возможности оповестить родных или адвоката о происходящем, и спустя несколько дней даже не удосужилась прокомментировать эту постыдную историю. Позже стало известно, что Yota ещё и установила особый режим для номера Шаведдинова, при звонке, на который сообщается, что абонент находится не в сети. Также к номеру прикреплена плашка с пометкой обо всех действиях по номеру сообщать в PR.

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

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

Карьерная возможность года Wildberries

Случай, который на Хабре не освещен был вообще. В конце сентября закрыли крупный (по их меркам) проект с командой из 30 человек, а от руководства поступило указание сократить на некоторых проектах 30% штата. Такое случается, конечно, но это было лишь начало.

29 ноября руководительница группы мобильной разработки в Wildberries Татьяна Аверьяноварассказалао массовых сокращениях в компании. По её словам, маркетплейсвыпинывал на морозувольнял сотрудников целыми командами по собственному желанию, не выплачивая компенсации. А уже 30 ноября в офисе увольнялось столько людей, что отдел кадров и бухгалтерия не справлялись с обработкой и выдачей документов, часть попросили прийти за доками позже.

Так же она рассказала о том, что Wildberries задерживает зарплату, технику для работы выдаёт сотрудникам только в аренду за плату. Компания приостановила набор новых сотрудников и иногда отзывает уже отправленные кандидатам предложения. В Wildberries, конечно же, все отрицают. Но рейтинг и отзывы об этом корейском онлайн-базаре на Хабр.Карьерекак бы намекаютна истинное положение дел.

Стыд и срам года Рамблер

В этой номинации и не могло быть другой компании, ведь инциденты подобные этому происходят чуть ли не раз в несколько лет. Данная история была широко освещена на самом Хабре и за его пределами, поэтому я не буду детально ее пересказывать. Кратко напомню, что в декабре 2019 Рамблер спустя 18 лет начал судебное преследование своих бывших сотрудников Игоря Сысоева и Максима Коновалова, пытаясь отжать у них права на самый популярный в мире веб-сервер Nginx, который был создан в 2002, когда Сысоев работал в Рамблере сисадмином. Незадолго до этого Nginx был куплен американской корпорацией F5 Networks за $670 млн. Узнав об этом, в Рамблере жутко возбудились, и посчитав что Nginx был создан в служебное время, и заявили о своих правах на проект. Немедленно было возбуждено уголовное дело, а в офисе у Сысоева и Коновалова даже прошли обыски. Претензии предъявила компания Рамблер, хотя формально обвинителем стала Lynwood Investments CY Ltd, которой передали на это права. Последняя связана с совладельцем группировки Rambler Сашей Мамутом.

Реакция общественности не заставила себя долго ждать и IT-аудитория облила Рамблер таким потоком говна, что долетело даже до самого Германа Грефа, который был вынужден вмешаться в ситуацию через своего зампреда Льва Хасиса, который на тот момент был председателем совета директоров Рамблера. В итоге в 2020 Рамблер попросил прекратить уголовное дело о правах на Nginx, исключив себя из числа потерпевших. Правда теперь истцом станет кипрская Lynwood,продолжив разбирательствоуже в международном суде.

На Хабре у этой группировки клоунов естьаккаунтиблог, который они в последнее время начали оживлять и практически никто уже не припоминает компании этот стыд.

Что забавно, сам Хабр показал себя в этой истории довольно неоднозначно. Ситуация с Nginx сподвигла написать осуждающий пост самогоДенискина, редакция велахронологию инцидента, а на главной была даже специальная ссылка, позволяющая держать руку на пульсе ситуации. Но это всё не помешало пригласить Рамблер в марафон удалёнки. Как говорится, вы не понимаете, это другое. В целом понятно желание быть IT-Швейцарией, но тут либо крестик снимите, либо трусы наденьте.

Заключение

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

Спасибо, что прочитали этот пост и увидимся в следующем году. Если я упустил кого-то, за кого тоже в 2020 было стыдно пишите о них в комментариях. Также буду рад почитать вашу объективную критику, с ней получится делать Хабрастыд-2021 менее стыдным. Всех с Новым Годом и Рождеством!

Подробнее..

ФЗ-152 надоел, простое решение c хранением персональных данных на nginx

17.04.2021 18:19:17 | Автор: admin

Всем привет,

Я последние несколько лет очень часто сталкиваюсь с проектами по адаптации под 152-ФЗ и он мне, честно, порядком надоел. Поэтому, прочитав опять весь закон, все комментарии различных ведомств и трактовки уважаемых людей, а также проанализировав ряд решений, которые прошли успешно аудит. Я, кажется, нашел простой технический вариант как сделать ваш web-site, API или приложение, соответствующее закону о персональных данных 152-ФЗ в разрезе требования о сборе пнд на территории России.

Я даже автоматизировал развертывание этой штуки и это занимает не больше 10-ти минут. Давайте обсудим применимость данного подхода!?

Чуть-чуть про сам закон, 152-ФЗ

Я уже и не помню зачем он на самом деле создавался, толи для того, что бы фейсбук и твиттер хранили данные о Российских граждан в РФ и таким образом к ним был бы более простой способ доступа у правоохранительных органов. Возможно, для того, чтобы они были в безопасности. Назначение самого закона оставим за скобками, а углубимся сразу в его требования. Сам закон обширный, но самый большой камень преткновения описан в статье 18, п.5:

"При сборе персональных данных, в том числе посредством информационно-телекоммуникационной сети "Интернет", оператор обязан обеспечить запись, систематизацию, накопление, хранение, уточнение (обновление, изменение), извлечение персональных данных граждан Российской Федерации с использованием баз данных, находящихся на территории Российской Федерации, за исключением случаев, указанных впунктах 2,3,4,8 части 1 статьи 6настоящего Федерального закона."

Хочу обратить внимание именно на слова "при сборе персональных данных". Если попробовать перевести в простой язык, то его можно трактовать так, что собирать данные нужно используя базу данных в РФ и еще держать ее актуальной. Никаких ограничений для других операций в законе нет и ограничений, что можно делать дальше с этими данными так же нет. Это самый тонкий момент тут, так как пояснений, что такое база-данных или другие понятия закон не описывает, то это все трактуют очень по разному. Вот самые распространенные трактовки, которые я слышу:

  • Собирать персональные данные нужно в РФ, дальше можно их отправлять куда захочется.

  • Вообще нельзя отправлять персональные данные за пределы РФ.

  • Вообще ничего нельзя никуда отправлять и нельзя нигде ничего размещать за пределами РФ.

Но на основе того как уже реализуются системы и как проходят проверки, можно смело заявить, что первый вариант точно рабочий и валидный. Теперь разобравшись чуть-чуть с требованиями перейдем к тому как это можно реализовать и уже выдохнуть.

Варианты реализации

Ниже список вариантов как компании этот закон исполняют с технической стороны и что я встречал:

  1. Вообще его игнорируют. Привет Facebook и Twitter.

  2. Доказывают себе и другим, что у них нет персональных данных. Закон, кстати, так себе описывает, что такое персональные данные.

  3. Арендуют сервер или виртуальную машину в РФ, кладут на него excel фаил - "персональные данные.xls" и отчитываются документально перед проверяющими органами.

  4. Вносят руками данные локально в excel или в локальную систему перед тем как внести их в систему размещенную за пределами РФ.

  5. Просто разворачивают копию системы в РФ и пользователей маршрутизируют на уровне DNS между площадками.

  6. Выносят из приложения часть логики и размещают ее в РФ, где происходит сбор и хранение персональных данных. (Самый крутой вариант с моей точки зрения).

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

Описание реализации - Reverse Proxy

Концептуально выглядит он примерно как на архитектуре ниже.

Ничего сложного и логика работы следующая:

  1. DNS на основе гео принадлежности пользователя маршрутизирует запросы. Если пользователь из РФ, то он идет по правой части картинки на прокси сервер. В данном случае на картинке Route53, но может быть и другой DNS сервис.

  2. Reverse proxy, в данном случае это nginx принимает HTTP/HTTPS запрос и если он (POST, PUT, DELETE и тд), то кладет его локально в лог и в базу данных (PostgreSQL) с помощью плагина - rsyslog-pgsql в json формате.

  3. Дальше запрос отсылается в первичную систему, получает ответ и отдает его пользователю.

  4. Для пользователя процесс выглядит совершенно прозрачным и не нужно менять никакой логики у него, он как-будто взаимодействует с оригинальной системой.

"Постойте, так ведь это просто сбор логов HTTP/S запросов, разве это считается?" - спросите вы. С точки зрения закона он никак не регламентирует формат и структуру сбора и хранения данных и только говорит про использование базы-данных. Поэтому с логической точки зрения тут все по букве закона. Похоже, примерно так же SAP доработал свою систему, вот тут можно почитать более подробно в их блоге. Там очень часто упоминается, что пишется лог изменений в РФ.

В базе данных будут храниться HTTP/S логи запросов на создание и изменения, что-то вроде changefeed. Но это легко можно расширить если известна модель пересылаемых данных и встроить эту логику в виде python скрипта или просто сделать trigger в базе данных, которая будет схлопывать changefeed в нужную структуру, где запись об одном человеке будет одна.

Как развернуть это себе?

Я автоматизировал развертывания и настройки этого модуля и опубликовал его тут на Github как open source проект. Если кто-то хочет дополнить или расширить этот проект то смело пишите мне или создавайте pull request.

Краткое пояснения как развернуть:

  1. Пропишите в вашей DNS записи A-record с айпи адресом на ваш сервер, который будет выступать reverse-proxy.

  2. Зайдите на вашу виртуальную машину или свой сервер и сделайте: git clone https://github.com/Gaploid/FZ-152-Reverse-Proxy

  3. Сделайте файл executable: chmod +x install.sh

  4. Запустите скрипт: sudo ./install.sh <incoming_domain> <url_to_forward_traffic> Пример: sudo ./install.sh example.com http://example.com где <incoming_domain>это домен на который пользователь будет заходить, он может быть существующим. <url_to_forward_traffic> это адрес первоначальной системы.

  5. Все, после этого если вы зайдете на <incoming_domain> все запросы POST, DELETE, PUT буду складываться локально в лог фаил - /var/log/nginx/reverse-access.log и в базу: proxy_logs в таблицу: accesslog.

Если вы хотите добавить HTTPS, то это можно сделать несколькими способами:

  • Добавить свой существующий сертификат в nginx. Вот пример инструкции.

  • Добавить новый сертификат от let's encrypt. Я для этого сделал скрипт ./add_ssl.sh вам нужно просто его будет запустить на этом же сервере. Сертификат получается с помощью бота от let's encrypt автоматически, но валидацию, что это действительно ваш домен он проводит путем проверки доступности по указанному домену <incoming_domain> вашего сервера, который вы указали на первом шаге.

Все на этом ваше мобильное приложение или даже веб-сайт если он работает через ваше API будет работать так как и работал и ничего в них не нужно менять.

Как еще это можно улучшить?

  1. На nginx можно дополнительно выставить фильтр какие запросы перехватывать и вы можете указать, например, что нужно перехватывать если URL - myapp.com/profile/ в таком случае только запросы связанные с профилем будут сохраняться, что сильно уменьшит объем хранимых данных.

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

  3. Можно еще добавить веб интерфейс с поиском, который будет показывать все записи по поисковой строке, например по ФИО или айди человека.

Послесловие

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

Причина по которой я написал эту статью - желание обсудить валидность и применимость данного варианта с комьюнити, поэтому смело пишите ваши мысли в комментариях.

Подробнее..

Вышел релиз nginx 1.20.0

21.04.2021 14:19:32 | Автор: admin

С момента выхода прошлой версии nginx прошел целый год. Сейчас представлена новая стабильная ветка nginx 1.20.0. По словам разработчиков, в нее вошли все изменения, которые ранее были накоплены в ветке 1.19.x. В новой ветке изменения будут связаны с ликвидацией серьезных ошибок и уязвимостей.

В ближайшее время разработчики сформируют основную ветку 1.21, которая будет получать новые возможности. Тем пользователям, кому не нужна совместимость со сторонними модулями, рекомендуется использовать основную ветку. На ее основе раз в квартал формируются выпуски коммерческого продукта Nginx Plus.

Немного статистики


Согласно данным компании Netcraft, nginx используется на 20,15% всех активных сайтов. Год назад этот показатель составлял 19,56%, два года назад 20,73%. Nginx находится на втором месте после Apache, доля которого составляет 25,38% (год назад 27,64%). На третьем месте находится Google с 10,09%, а на четвертом Cloudflare (8,51%).

Если учитывать все сайты, то nginx является лидером с 35,34% рынка. У Apache 25,98%, у OpenResty (платформа на базе nginx и LuaJIT.) 6.55%, Microsoft IIS 5.96%.

Если же оценивать использование nginx самыми посещаемыми сайтами в мире, то доля nginx составляет 25,55% (год назад 25,54%, два года назад 26,22%). Количество сайтов, которые работают под управлением nginx, составляет 419 млн. В России nginx используется на 79,1% самых посещаемых сайтов (год назад 78,9%).

Ну а теперь об изменениях


Новый релиз получил сразу несколько заметных улучшений:

  • Возможность проверки клиентских сертификатов с привлечением внешних служб на базе протокола OCSP (Online Certificate Status Protocol). Для включения проверки предложена директива ssl_ocsp, для настройки размера кэша ssl_ocsp_cache, для переопределения URL OCSP-обработчика, указанного в сертификате, ssl_ocsp_responder.
  • В состав новой версии вошел модуль ngx_stream_set_module, позволяющий присвоить значение переменной:

    server {        listen 12345;        set    $true 1;    }


  • Разработчики добавили директиву proxy_cookie_flags для указания флагов для Cookie в проксируемых соединениях.
  • Добавлены и директивы ssl_conf_command, proxy_ssl_conf_command, grpc_ssl_conf_command и uwsgi_ssl_conf_command, при помощи которых можно задать произвольные параметры для настройки OpenSSL. Так, при необходимости приоритизировать шифры ChaCha и выполнить расширенную настройку шифров TLSv1.3 можно указать:

   ssl_conf_command Options PrioritizeChaCha;   ssl_conf_command Ciphersuites TLS_CHACHA20_POLY1305_SHA256;

  • Еще одно важное обновление добавление директивы ssl_reject_handshake, которая предписывает отвергать все попытки согласования SSL-соединений (например, можно использовать для отклонения всех обращений с неизвестными именами хостов в поле SNI).

   server {        listen 443 ssl;        ssl_reject_handshake on;    }     server {        listen 443 ssl;        server_name example.com;        ssl_certificate example.com.crt;        ssl_certificate_key example.com.key;    }

  • В почтовый прокси вошла директива proxy_smtp_auth, которая дает возможность аутентифицировать пользователя на бэкенде при помощи команды AUTH и механизма PLAIN SASL.
  • Добавлена директива keepalive_time. Ее задача ограничение общего времени жизни каждого keep-alive соединения, по истечении которого соединение будет закрыто (не путать с директивой keepalive_timeout, определяющей время неактивности, после которого keep-alive соединение закрывается).
  • Появилась переменная $connection_time, через которую можно получить информацию о продолжительности соединения в секундах с миллисекундной точностью.
  • В уже имеющиеся директивы proxy_cache_path, fastcgi_cache_path, scgi_cache_path и uwsgi_cache_path добавлен параметр min_free, регулирующий размер кэша на основе определения минимального размера свободного дискового пространства.
  • Директивы lingering_close, lingering_time и lingering_timeout адаптированы для работы с HTTP/2.
  • Разработчики также приблизили код обработки соединений в HTTP/2 к реализации HTTP/1.x. Поддержка отдельных настроек http2_recv_timeout, http2_idle_timeout и http2_max_requests прекращена в пользу общих директив keepalive_timeout и keepalive_requests. Удалены настройки http2_max_field_size и http2_max_header_size, вместо них нужно использовать large_client_header_buffers.
  • Появилась новая опция командной строки "-e". Она дает возможность указать альтернативный файл для записи лога ошибок, который будет использоваться вместо лога, заданного в настройках. Вместо имени файла можно указать специальное значение stderr.

Подробнее..

DDoS атаки на 7 уровень защита сайтов

09.05.2021 22:19:11 | Автор: admin
DDoS атаки на 7 уровень (на уровень приложения) наиболее простой способ привести в нерабочее состояние сайт и навредить бизнесу. В отличие от атак на другие уровни, когда для отказа сайта необходимо организовать мощный поток сетевого трафика, атаки на 7 уровень могут проходить без превышения обычного уровня сетевого трафика. Как это происходит, и как от этого можно защищаться я рассмотрю в этом сообщении.

Атаки 7 уровня на сайты включают атаки на уровень веб-сервера (nginx, apache и т.д.) и атаки на уровень сервера приложений (php-fpm, nodejs и т.д.), который, как правило, расположен за проксирующим сервером (nginx, apache и т.д.). С точки зрения сетевых протоколов, оба варианта являются атакой на уровень приложения. Но нам, с практической точки зрения, нужно разделить эти два случая. Веб-сервер (nginx, apache и т.д.), как правило, самостоятельно отдает статические файлы (картинки, стили, скрипты), а запросы на получение динамического контента проксирует на сервер приложений (php-fpm, nodejs и т.д.). Именно эти запросы становятся мишенью для атак, так как в отличие от запросов статики, серверы приложений при генерировании динамического контента требуют на несколько порядков больше ограниченных системных ресурсов, чем и пользуются атакующие.

Как ни банально звучит, чтобы защититься от атаки, ее нужно сначала выявить. На самом деле, к отказу сайта могут привести не только DDoS атаки, но и другие причины, связанные с ошибками разработчиков и системных администраторов. Для удобства анализа, необходимо добавить в формат логов nginx (сорри, варианта с apache у меня нет) параметр $request_time, и логировать запросы к серверу приложений отдельный файл:

      log_format timed '$remote_addr - $remote_user [$time_local] '            '$host:$server_port "$request" $status $body_bytes_sent '            '"$http_referer" "$http_user_agent" ($request_time s.)';      location /api/ {           proxy_pass http://127.0.0.1:3000;           proxy_http_version 1.1;           proxy_set_header Upgrade $http_upgrade;           proxy_set_header Connection 'upgrade';           proxy_set_header Host $host;           proxy_cache_bypass $http_upgrade;           proxy_set_header X-Real-IP $remote_addr;           proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;           proxy_set_header X-NginX-Proxy true;           access_log /var/log/ngunx/application_access.log timed;       }


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

Выявив атаку можно, приступать к защите.

Очень часто, системные администраторы пытаются защитить сайт, ограничив количество запросов с одного IP-адреса. Для этого используют 1) Директиву limit_req_zone nginx (см. документацию), 2) fail2ban и 3) iptables. Безусловно, эти способы нужно использовать. Однако, такой способ защиты уже как 10-15 лет является малоэффективным. На это есть две причины:

1) Трафик, который генерирует сеть ботов при атаке на 7 уровень, по своему объему может быть меньше чем трафик обычного посетителя сайта, так как у обычного посетителя сайта на один тяжелый запрос к серверу приложений (php-fpm, nodejs и т.д.) приходится примерно 100 легких запросов на загрузку статических файлов, которые отдаются веб-сервером (nginx, apache и т.д.). От таких запросов iptables не защищает, так как может ограничивать трафик только по количественным показателям, и не учитывает разделение запросов на статику и динамику.

2) Вторая причина распределённость сети ботов (первая буква D в аббревиатуре DDoS). В атаке обычно принимает участие сеть из нескольких тысяч ботов. Они в состоянии делать запросы реже, чем обычный пользователь. Как правило, атакуя сайт, злоумышленник опытным путем вычисляет параметры limit_req_zone и fail2ban. И настраивает сеть ботов так, чтобы эта защита не срабатывала. Часто, системные администраторы начинают занижать эти параметры, отключая таким образом реальных клиентов, при этом без особого результата с точки зрения защиты от ботов.

Для успешной защиты сайта от DDoS, необходимо, чтобы на сервере были задействованы в комплексе все возможные средства защиты. В моем предыдущем сообщении на эту тему Защита от DDoS на уровне веб-сервера есть ссылки на материалы, как настроить iptables, и какие параметры ядра системы нужно привести в оптимальное значение (имеется в виду, прежде всего, количество открытых файлов и сокетов). Это является предпосылкой, необходимым, но не достаточным условием для защиты от ботов.

Кроме этого необходимо построить защиту, основанную на выявлении ботов. Все, что нужно для понимания механики выявления ботов, было подробно описано в исторической статье на Хабре Модуль nginx для борьбы с DDoS автора kyprizel, и реализована в библиотеке этого же автора testcookie-nginx-module

Это библиотека на С, и она продолжает развиваться небольшим сообществом авторов. Наверное, не все системные администраторы готовы на продакшин сервере прикомпилировать незнакомую библиотеку. Если же потребуется дополнительно внести изменения в работу библиотеки то это и вовсе выходит за рамки рядового системного администратора или разработчика. К счастью, в настоящее время появились новые возможности: скриптовый язык Lua, который может работать на сервере nginx. Есть две популярные сборки nginx со встроенным скриптовым движком Lua: openresty, разработка которого изначально споснировалась Taobao, затем Cloudfare, и nginx-extras, который включен в состав некоторых дистирибутивов Linux, например Ubuntu. Оба варианта используют одни и те же библиотеки, поэтому не имеет большой разницы, который из них использовать.

Защита от ботов может быть основана на определении способности веб-клиента: 1) выполнять код JavaScript, 2) делать редиректы и 3) устанавливать cookie. Из всех этих способов, выполнение кода JavaScript оказалось наименее перспективным, и от него я отказался, так как код JavaScript не выполняется, если контент загружается фоновыми (ajax) запросами, а повторная загрузка страницы средствами JavaScript искажает статистику переходов на сайт (так как теряется загловок Referer). Таким образом, остаются редиректы которые устанавливают cookie, значения котрых подчиняются логике, которая не может быть воспроизведена на клиенте, и не допускают на сайт клиентов без этих cookie.

В своей работе я основывался на библиотеке leeyiw/ngx_lua_anticc, которая в настоящее время не развивается, и я продолжил доработки в своем форке apapacy/ngx_lua_anticc, так как работа оригинальной библиотеки не во всем устраивала.

Для работы счетчиков запросов в библиотеке используются таблицы памяти, которые поддерживают удобные для наращивания значения счетчиков методы incr, и установку значений с TTL. Например, в приведенном ниже фрагменте кода наращивается значение счетчика запросов с одного IP-адреса, если у клиента не установлены cookie с определенным именем. Если счетчик еще не был инициализирован, он инициализируется значением 1 с TTL 60 секунд. После превышения количества запросов 256 (за 60 секунд), клиент на сайт не допускается:

local anticc = ngx.shared.nla_anticclocal remote_id = ngx.var.remote_addrif not cookies[config.cookie_name] then  local count, err = anticc:incr(remote_id, 1)  if not count then    anticc:set(remote_id, 1, 60)    count = 1   end   if count >= 256 then     if count == 256 then       ngx.log(ngx.WARN, "client banned by remote address")     end     ngx.exit(444)     return  endend


Не все боты являются вредными. Например, на сайт нужно пропускать поисковые боты и боты платежных систем, которые сообщают об изменении статусов платежей. Хорошо, если можно сформировать список всех IP-адресов, с которых могут приходить такие запросы. В этом случае можно сформировать белый список:

local whitelist = ngx.shared.nla_whitelistin_whitelist = whitelist:get(ngx.var.remote_addr)if in_whitelist then    returnend


Но не всегда это возможно. Одной из проблем является неопределенность с адресами ботов Google. Пропускать всех ботов подделывающихся под боты Google, равносильно снятию защиты с сайта. Поэтому, воспользуемся модулем resty.exec для выполнения команды host:

local exec = require 'resty.exec'if ngx.re.find(headers["User-Agent"],config.google_bots , "ioj") then    local prog = exec.new('/tmp/exec.sock')    prog.argv = { 'host', ngx.var.remote_addr }    local res, err = prog()    if res and ngx.re.find(res.stdout, "google") thenngx.log(ngx.WARN, "ip " .. ngx.var.remote_addr .. " from " .. res.stdout .. " added to whitelist")whitelist:add(ngx.var.remote_addr, true)        return    end    if res then        ngx.log(ngx.WARN, "ip " .. ngx.var.remote_addr .. " from " .. res.stdout .. "not added to whitelist")    else        ngx.log(ngx.WARN, "lua-resty-exec error: " .. err)    endend


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

apapacy@gmail.com
9 мая 2021 года
Подробнее..

Категории

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

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru