Реализация описана для PHP, но подходит для всех.
Конфиги
Начнём с контейнера, из которого будем общаться с ГИС ЖКХ. Тут приведён конфиг контейнера с продакшена, поэтому есть лишние (для вас) пакеты
Пока просто посмотрим, пояснения будут после кода
FROM php:8.1-fpm-alpine # это не критично, но мне нравится zsh RUN apk add zsh # пакеты для работы openssl RUN apk add \ git \ alpine-sdk \ cmake \ wget \ bash \ libxml2-dev \ libssl1.1 # пакеты для работы с zip-архивами; будут нужны для работы с xml-файлами RUN apk add \ libzip-dev \ zip \ unzip # пакеты для использования gd RUN apk add \ libpng \ libpng-dev \ freetype-dev # postgres RUN apk add \ postgresql-dev # общение с oracle ADD https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions /usr/local/bin/ RUN chmod +x /usr/local/bin/install-php-extensions RUN install-php-extensions oci8 # кофигурируем php RUN docker-php-ext-configure zip RUN docker-php-ext-configure gd --with-freetype RUN docker-php-ext-install soap pdo pdo_pgsql zip bcmath gd intl # composer RUN php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" RUN php composer-setup.php RUN php -r "unlink('composer-setup.php');" RUN mv composer.phar /usr/local/bin/composer # установка движка ГОСТ для openssl WORKDIR /opt COPY docker/production/php/openssl_gost.conf /opt/openssl_gost.conf RUN git clone --branch=openssl_1_1_1 https://github.com/gost-engine/engine.git gost-engine WORKDIR /opt/gost-engine RUN mkdir build WORKDIR /opt/gost-engine/build RUN cmake -DCMAKE_BUILD_TYPE=Release -DOPENSSL_ROOT_DIR=/usr/ssl -DOPENSSL_LIBRARIES=/usr/ssl/lib -DOPENSSL_ENGINES_DIR=/usr/ssl/lib/engines-3 .. RUN cmake --build . --config Release RUN cmake --build . --target install --config Release # на моей машине openssl лежит в папке /etc/ssl, на проде в папке /etc/ssl1.1 - не стал разбираться почему RUN OPENSSL_DIRECTORY="$([[ -d /etc/ssl1.1 ]] && echo '/etc/ssl1.1' || echo '/etc/ssl')" && \ sed -i '1s;^;openssl_conf = openssl_def\n;' "$OPENSSL_DIRECTORY/openssl.cnf" && \ cat /opt/openssl_gost.conf >> "$OPENSSL_DIRECTORY/openssl.cnf" # ставим сертификаты для stunnel COPY docker/production/php/stunnel.conf /etc/stunnel.conf RUN mkdir -p /etc/crypto # публичный ключ стенда ГИС ЖКХ COPY gis/certs/ca-ppak.pem /etc/crypto/ca-ppak.pem # публичный ключ вашего сертификата COPY gis/certs/certificate.pem /etc/crypto/certificate.pem # приватный ключ вашего сертификата COPY gis/certs/private.key /etc/crypto/private.key RUN chmod -R 700 /etc/crypto # ставим сам stunnel WORKDIR /opt RUN wget https://www.stunnel.org/downloads/stunnel-5.66.tar.gz -O stunnel.tar.gz RUN tar -xvf stunnel.tar.gz WORKDIR /opt/stunnel-5.66 RUN sed -i '4745 i SSL_library_init();' src/options.c RUN ./configure --disable-libwrap RUN make && make install # бутстрапим cron COPY docker/production/php/crontab /opt/crontab RUN chmod 0644 /opt/crontab && crontab /opt/crontab RUN crond WORKDIR /app # запускаем все нужные процессы COPY docker/production/php/startup.sh /opt/startup.sh RUN chmod +x /opt/startup.sh CMD ["/opt/startup.sh"]
На строке 47 ссылаюсь на openssl_gost.conf, вот он:
# gost support [openssl_def] engines = engine_section [engine_section] gost = gost_section [gost_section] engine_id = gost dynamic_path = /usr/ssl/lib/engines-3/gost.so default_algorithms = ALL CRYPT_PARAMS = id-Gost28147-89-CryptoPro-A-ParamSet
На строке 89 ссылаюсь на /opt/startup.sh, вот он:
stunnel /etc/stunnel.conf php-fpm
Где stunnel.conf это:
socket=lsocket=l:TCP_NODELAY=1 socket=r:TCP_NODELAY=1 CAFile=/etc/crypto/ca-ppak.pem engine=gost sslVersion=TLSv1 engineDefault=ALL output=/var/log/stunnel.log DEBUG=7 client=yes [pseudo-https] accept=127.0.0.1:3000 connect=api.dom.gosuslugi.ru:443 ciphers=GOST2012-GOST8912-GOST8912 verify=0 TIMEOUTclose=0 cert=/etc/crypto/certificate.pem key=/etc/crypto/private.keyTCP_NODELAY=1socket=r:TCP_NODELAY=1CAFile=/etc/crypto/ca-ppak.pemengine=gostsslVersion=TLSv1engineDefault=ALLoutput=/var/log/stunnel.logDEBUG=7client=yes[pseudo-https]accept=127.0.0.1:3000connect=api.dom.gosuslugi.ru:443ciphers=GOST2012-GOST8912-GOST8912verify=0TIMEOUTclose=0cert=/etc/crypto/certificate.pemkey=/etc/crypto/private.key
Примечания к конфигам
-
Пути до файлов, указанные относительно корня проекта:
-
docker/production/php — путь до Dockerfile контейнера
-
gis/certs — путь до папок с сертификатом
-
-
В вашем контейнере, с которого вы будете делать запросы должен быть openssl v1.1.1, с версией 3.0 у меня нет времени разбираться) может добрые люди в комментах расскажут как это делается
-
Если контейнер не запускается и ругается на то, что не может найти stunnel, то поднимите версию, чекнув текущую на официальном сайте. К сожалению, у них нет ссылки на самую свежую стабильную версию и если выходит новая версия, то ссылки со старыми начинают выдавать 404.
Примечания к сертификатам
Нам нужны открытый и закрытый ключ. Из КриптоПРО можно выгрузить сертификат в формате cert.000. После этого идём сюда и, следуя инструкциям, делаем себе приватный ключ — https://github.com/ddruganov/get-cpcert. Публичный ключ можно достать через openssl x509 -in cert.crt -out cert.pem -outform PEM
Примечание к криптотуннелю
Для того, чтобы проксировать все запросы через криптотуннель, в php нужно допилить SoapClient:
final class CustomSoapClient extends SoapClient { private Closure $requestHandlerCallback; public function __construct(string $wsdl, Closure $requestHandlerCallback) { $this->requestHandlerCallback = $requestHandlerCallback; parent::__construct($wsdl, [ 'trace' => true, 'exceptions' => false, 'use' => SOAP_LITERAL, 'style' => SOAP_DOCUMENT, ]); } public function __doRequest(string $request, string $location, string $action, int $version, bool $oneWay = false): ?string { $request = ($this->requestHandlerCallback)($request); $location = str_replace('https://api.dom.gosuslugi.ru', Yii::$app->params['gis']['tlsTunnelAddress'], $location); $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => $location, CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_POSTFIELDS => $request, CURLOPT_CONNECTTIMEOUT => 20, CURLOPT_TIMEOUT => 20, CURLOPT_HTTPHEADER => [ "SOAPAction: $action" ] ]); $response = curl_exec($ch); curl_close($ch); return $response; } }
В этом коде самое важное это строка 21, где в запросе по soap идёт замена прямого адреса ГИС ЖКХ на адрес криптотуннеля
Заключение
Запуская этот контейнер вы получаете готовое решение, в котором настроен криптотуннель и openssl+gost и в принципе, это всё что требуется, чтобы начать работать с ГИС ЖКХ
Мой подход уже успешно работает на продакшене около 10 месяцев, обрабатывая до 60к запросов в месяц с крайне вариативной по дням загрузкой, за это время сбоев не было (хотя вообще-то откуда им быть, это не бизнес-логика)
Ещё одна важная часть всего взаимодействия это подпись запросов по XMLDSig при помощи полученных сертификатов, но об этом чуть позже, так как там больше прикладного кода и неплохо бы его оформить в репозиторий для наглядности.
Если есть примечания или что-то непонятно — обязательно пишите, я обновлю гайд и всё поясню. Я сам долго всё это собирал, я знаю, что огромное количество людей страдает с этой интеграцией постоянно (https://gitter.im/springjazzy/GIS_JKH_Integration)
ссылка на оригинал статьи https://habr.com/ru/post/710462/
Добавить комментарий