Настройка stunnel ГОСТ TLS КриптоПро
Недавно настраивал stunnel для СделкаРФ, постараюсь максимально подробно рассказать как настроить stunnel-gost который вы потом сможете c лёгкостью настроить у себя и запустить в вашем kubernetes.
Введение
Недавно наш партнёр sign.me, сообщил, что мы больше не сможем как люди ходить в API по https c токеном, а обязаны ходить по ГОСТ TLS. Инструкций особо не дали, сказали идите на сайт криптопро, там всё понятно. Как оказалось со знаком минус. Можете использовать Ngate, stunnel и ещё что-то. Ну и ещё добавили, что на текущий момент подойдёт любой сертификат из тестового УЦ ООО "КРИПТО-ПРО". С осознанием сего, я пошёл разбираться.
Никакой человеческой документации или инструкций я не нашёл, всё пришлось собирать по кусочкам. Какие то инструкции есть, но всё это с середины и вообще без контекста как например тут. На 2й день я нашёл на хабре статью Опыт интеграции с КриптоПро DSS поверх ГОСТ TLS и оформленный репозиторий. Огромное спасибо автору, это внесло кучу ясности, но не решило проблему так сразу.
Полный путь
Все действия локально я проводил на Ubuntu 22.04.
И так поехали, нам потребуется локально установленный КриптоПро, обязательно с действующей лицензией, иначе при попытке экспорта сертификата будет ошибка:
Error: export in PFX failed License for this product is expired, not yet valid or corrupt.
Купить лицензию можно у контура к примеру, полностью онлайн. Бессрочная лицензия на SCP 5.0 в июне 2023 стоит 2600₽.
Все плагины браузера и т.д. После установки для удобства можно обновить PATH.
export PATH="/opt/cprocsp/bin/amd64:/opt/cprocsp/sbin/amd64:${PATH}"
Идём в тестовый УЦ, скачиваем и устанавливаем CA
Многие действия локально можно сделать и через GUI, но я буду приводить в пример консольные команды.
certmgr -install -file CA.crt -store mCA
Теперь можно выпустить и установить ключ и сертификат
Заполняем все данные, обязательно выставляем галочку Пометить ключ как экспортируемый для удобства также можно Заданное пользователем имя контейнера ключа
Нужно будет поводить мышкой, нажимать клавиши и ключ сгенерируется
Ключ и сертификат установятся локально и запросят установить пароль, сильно тут не мудрите 1234000
будет окей.
Можно открыть GUI приложение и увидеть нужный контейнер и сертификат
Вы можете попробовать экспортировать ключи из GUI но у меня этот вариант не сработал, так как потом при импорте ругалось на не верный PIN, по этому рекомендую всё делать из консоли.
csptest -keys -enum -verifyc -fqcn -un | grep 'HDIMAGE'
Копируем имя контейнера и экспортируем ключ:
certmgr -export -pfx -container '\\.\HDIMAGE\HDIMAGE\\sdelkars.000\913B' -dest certificate.pfx -pin 1234000
Отлично, теперь можно собрать контейнер и проверить работу из него. В контейнере нам потребуется скаченный ранее cертификат CA.crt
и экспортированный ключ certificate.pfx
Я просто приведу в пример код своего докер файла, хотя вы можете сделать так, как предложил Максим у себя в репозитории https://github.com/maxineal/gost-stunnel.
Отличие состоит в том, что мы выпекам образ с сертификатом внутри, а Маским создаёт его в рантайме. Возможно позже мы тоже будем так делать, а пока просто периодически коммитим свежий CA и PFX в репозиторий и собираем новый контейнер.
FROM debian:11-slim WORKDIR /etc/stunnel/ ENV PATH="/opt/cprocsp/bin/amd64:/opt/cprocsp/sbin/amd64:${PATH}" # dependencies ENV DEBIAN_FRONTEND=noninteractive RUN set -x \ && apt-get update \ && apt-get install --no-install-recommends -y ca-certificates opensc openssl procps tzdata tar gzip curl wget lsb-base \ && dpkg-reconfigure --frontend noninteractive tzdata \ && rm -rf /var/lib/apt/lists/* # install cryptopro csp COPY cprocsp/linux-amd64_deb.tgz /tmp/linux-amd64_deb.tgz RUN set -x \ && tar -xzvf /tmp/linux-amd64_deb.tgz -C /tmp \ && /tmp/linux-amd64_deb/install.sh cprocsp-stunnel \ && rm -rf /tmp/* COPY conf/ /etc/stunnel/ COPY bin/entrypoint.sh /entrypoint.sh ARG STUNNEL_CERTIFICATE_PIN_CODE="12340000" # Экспорт сертификата RUN set -x \ # импорт CA && certmgr -install -file /etc/stunnel/tls/CA.crt -store mCA \ # импорт сертификата с закрытым ключом && certmgr -install -pfx -file /etc/stunnel/tls/certificate.pfx -pin "${STUNNEL_CERTIFICATE_PIN_CODE}" -silent \ # определение контейнера-хранилища закрытых ключей && containerName=$(csptest -keys -enum -verifyc -fqcn -un | grep 'HDIMAGE' | awk -F'|' '{print $2}' | head -1) \ # установка сертификата клиента && certmgr -install -cont "${containerName}" -silent \ # экспорт сертификата для stunnel && certmgr -export -dest /etc/stunnel/tls/client.crt -container "${containerName}" ENTRYPOINT ["/entrypoint.sh"] CMD ["stunnel_thread", "/etc/stunnel/stunnel.conf"]
также приведу пример конфига для stunnel.conf
pid=/run/stunnel.pid foreground=yes debug=5 [https] client= yes accept= 8080 connect= gost.test.sign.me:443 CAFile=/etc/stunnel/tls/CA.cer cert=/etc/stunnel/tls/client.crt
Посмотреть описание сертификата и его время действия можно через openssl
openssl x509 -in client.crt -text -inform DER -noout Certificate: Data: Version: 3 (0x2) ... Validity Not Before: Mar 22 10:55:43 2023 GMT Not After : Jun 22 11:05:43 2023 GMT ...
Проверка работы
docker run --rm --net host stunnel-gost:latest
curl -sSL -XPOST -d "{}" -H "Host: gost.sign.me" http://localhost:8080/register/precheck/ -m 10
Должно вернуться http 401 а в теле Invalid API key
Сессия вяжется не сразу, по этому может потребоваться 3-4 первых запроса.
По этому в кубе для startupProbe мы сделали вот так
exec: command: - "bash" - "-c" - 'curl -sSL -XPOST -d "{}" -H "Host: gost.test.sign.me" http://localhost:8080/register/precheck/ -m 10 2>&1 | grep -q "Invalid API key"'
В логах будет что то типа этого:
Configuring stunnel... Starting stunnel 2023.03.23 09:56:02 LOG5[1:140305597050688]: stunnel 4.18 on x86_64-pc-linux-gnu 2023.03.23 09:56:02 LOG5[1:140305597050688]: Threading:PTHREAD Sockets:POLL,IPv6 2023.03.23 09:56:02 LOG5[1:140305597050688]: 0 clients allowed 2023.03.23 09:57:07 LOG5[1:140305596815104]: https connected from 127.0.0.1:42768 2023.03.23 09:57:07 LOG5[1:140305596815104]: try to read the client certificate 2023.03.23 09:57:07 LOG3[1:140305596815104]: Credentials complete 2023.03.23 09:57:08 LOG5[1:140305596815104]: 3607 bytes of handshake(in handshake loop) data received. 2023.03.23 09:57:08 LOG5[1:140305596815104]: 1619 bytes of handshake data sent 2023.03.23 09:57:08 LOG5[1:140305596815104]: 1582 bytes of handshake(in handshake loop) data received. 2023.03.23 09:57:08 LOG5[1:140305596815104]: Handshake was successful 2023.03.23 09:57:08 LOG5[1:140305596815104]: PerformClientHandshake finish 2023.03.23 09:57:08 LOG5[1:140305596815104]: Server subject: CN=ООО СМ, C=RU, S=77 Москва, L=Москва, STREET=Москва Мичуринский проспект 45, O=ООО СМ, OU=IT, ИНН ЮЛ=7719795020, ОГРН=1117746908597 2023.03.23 09:57:08 LOG5[1:140305596815104]: Server issuer: ОГРН=1105260001175, ИНН=005260270696, STREET="ул. Радио, дом 24, корпус 1, помещение V, комната 23", E=ca@iecp.ru, L=г. Москва, S=77 Москва, C=RU, OU=Удостоверяющий центр, O="Акционерное общество ""Аналитический Центр""", CN="АО ""Аналитический Центр""" 2023.03.23 09:57:08 LOG5[1:140305596815104]: CA subject: ОГРН=1105260001175, ИНН=005260270696, STREET="ул. Радио, дом 24, корпус 1, помещение V, комната 23", E=ca@iecp.ru, L=г. Москва, S=77 Москва, C=RU, OU=Удостоверяющий центр, O="Акционерное общество ""Аналитический Центр""", CN="АО ""Аналитический Центр""" 2023.03.23 09:57:08 LOG5[1:140305596815104]: CA issuer: E=dit@minsvyaz.ru, C=RU, S=77 Москва, L=г. Москва, STREET="улица Тверская, дом 7", O=Минкомсвязь России, ОГРН=1047702026701, ИНН=007710474375, CN=Минкомсвязь России 2023.03.23 09:57:19 LOG5[1:140305581950720]: https connected from 127.0.0.1:37770 2023.03.23 09:57:19 LOG5[1:140305581950720]: try to read the client certificate 2023.03.23 09:57:19 LOG3[1:140305581950720]: Credentials complete
Заключение
Не сложно догадаться, что далее мы просто подняли ингресс и включили аннотацию nginx.ingress.kubernetes.io/upstream-vhost
. Переключили эндпоинт в конфиге бэкенда и продолжили работать как раньше, только теперь есть 1 дополнительный сервис/прослойка/точка отказа. Хотя можно было бы и посадить этот туннель сайткаром в под с каждым бэкендом, но тогда мониторить это станет значительно сложнее да и незачем плодить количество процессов. Вопрос мониторинга срока действия данных сертификатов тоже актуален, есть идея поднять nginx в этом же контейнере на отдельном порту и настроить пробу этого эндпоинта через blackbox exporter, или написать свой экспортер для этого случая, но это в другой серии ... :)