devops
March 29, 2023

Настройка 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, но я буду приводить в пример консольные команды.

Установка 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, или написать свой экспортер для этого случая, но это в другой серии ... :)