Skip to content

Latest commit

 

History

5 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ocsp-agent

SPOA (Stream Processing Offload Agent) для HAProxy: проверяет клиентский сертификат TLS через OCSP и возвращает HAProxy результат в виде переменной, которую можно использовать в ACL для reject/deny.

Как это работает

  1. HAProxy при mTLS-подключении завершает TLS handshake, и его SPOE-движок (filter spoe) по протоколу SPOP отправляет агенту сообщение с DER клиентского сертификата (ssl_c_der).
  2. Агент парсит сертификат, находит его OCSP responder из AIA-расширения сертификата, находит сертификат издателя в локальном CA bundle, собирает и отправляет OCSP-запрос.
  3. Результат (good / revoked / unknown / error) агент возвращает в SPOP-ответе как action set-var; HAProxy кладёт его в транзакционную переменную ocsp_status (+ ocsp_reason с деталями), читает её в ACL и решает, пропускать соединение или рвать его.
  4. Результаты кэшируются в памяти по (AuthorityKeyId, SerialNumber) на срок до NextUpdate из ответа OCSP responder'а (в границах min_cache_ttl / max_cache_ttl из config.yaml).

Почему SPOE-агент, а не встроенный Lua

Проверку OCSP можно сделать и без внешнего процесса - написать Lua-скрипт, встроенный в HAProxy (http-request lua.*, core.tcp() для сетевого запроса к responder'у). У варианта с отдельным SPOE-агентом перед этим есть конкретные преимущества:

  • Неблокирующий I/O. Lua-скрипты HAProxy выполняются в том же event loop, что обслуживает соединения; синхронный сетевой запрос к OCSP responder'у из Lua блокирует этот worker на время round-trip. SPOE-фреймы асинхронны и пайплайнятся - HAProxy не подвисает, ожидая ответ агента.
  • Готовый TLS/x509/OCSP-стек. Go stdlib (crypto/x509) и golang.org/x/crypto/ocsp дают протестированный разбор сертификатов, ASN.1 и сборку/валидацию OCSP-запросов и ответов "из коробки". У Lua внутри HAProxy нет встроенного x509/ASN.1-парсера и OCSP-клиента.
  • Независимое масштабирование и деплой. Агента можно поднять как backend с несколькими инстансами (use-backend + балансировка) и обновлять/перезапускать/катить новую версию отдельно от HAProxy, без reload и разрыва существующих соединений.
  • Кэш переживает reload HAProxy. Агент - постоянный процесс, его in-memory TTL-кэш результатов не сбрасывается при каждом reload конфига HAProxy, в отличие от состояния, которое держал бы Lua внутри воркера.

Запуск

Все настройки агента - в YAML-файле, программе передаётся только путь к нему:

./ocsp-agent -config config.yaml

-config по умолчанию ищет config.yaml в текущей директории.

Если файла по этому пути нет, агент создаёт его сам - копирует initconfig.yaml (лежит рядом с бинарником, в корне репозитория) в config.yaml и завершается с сообщением, что нужно отредактировать хотя бы ca_bundle и запустить агента заново:

$ ./ocsp-agent -config config.yaml
2026/08/20 01:52:24 no config found at config.yaml, created it from
initconfig.yaml - edit it (at least ca_bundle) and start the agent again

initconfig.yaml при развёртывании должен лежать рядом с бинарником (или в рабочей директории, из которой его запускают) - это просто шаблон на диске, а не то, что встроено в сам бинарник.

Оба файла - config.yaml и initconfig.yaml - полностью прокомментированы на русском прямо в самих файлах.

Параметры config.yaml

Ключ По умолчанию
listen 127.0.0.1:12345
ca_bundle (обязателен, значения по умолчанию нет)
message_name check-client-cert
cert_key cert
ocsp_timeout 4s
min_cache_ttl 5m
max_cache_ttl 1h
error_cache_ttl 30s

listen - адрес и порт, на которых агент слушает входящие SPOE-соединения от HAProxy. Обычно это localhost, так как HAProxy обращается к агенту с той же машины (или из того же network namespace/контейнера). Значение должно совпадать с адресом server в backend'е, который в конфиге HAProxy указан как use-backend для SPOE-агента (см. examples/haproxy.cfg).

ca_bundle (обязательный) - путь к PEM-файлу с сертификатами издающих (issuing) CA - теми же промежуточными/корневыми сертификатами, что указаны в ca-file на bind-строке HAProxy. Он нужен агенту, чтобы построить корректный OCSP-запрос: структура CertID требует hash имени и открытого ключа издателя, а не только сам клиентский сертификат.

Файл может содержать несколько сертификатов CA подряд (обычная конкатенация PEM-блоков) - если клиентские сертификаты выпускаются разными независимыми CA, положите сюда сертификаты всех издателей. Агент при проверке конкретного сертификата сам подберёт из этого набора подходящего издателя.

message_name - имя SPOE-сообщения, которое агент ожидает получить от HAProxy. Должно точно совпадать с именем в директиве spoe-message в SPOE-конфиге (см. examples/ocsp.spoe.cfg, секция spoe-message).

cert_key - имя ключа (KV) внутри SPOE-сообщения, по которому агент достаёт DER-представление клиентского сертификата. Должно совпадать с именем аргумента в args у spoe-message, например: args cert=ssl_c_der.

ocsp_timeout - сколько агент ждёт ответа от OCSP responder'а на один запрос, прежде чем считать попытку неудачной. Слишком маленькое значение даёт ложные ошибки при чуть более медленной сети до responder'а; слишком большое - долгую задержку обработки соединения в HAProxy, если responder завис или недоступен (актуально при fail-closed политике в ACL).

min_cache_ttl - TTL кэша результата, если ответ OCSP responder'а не содержит поля NextUpdate (не все responder'ы его отдают). Без этого поля агент не знает, на какой срок кэшировать ответ, и использует это значение как запасное.

max_cache_ttl - верхняя граница жизни закэшированного результата, даже если NextUpdate из ответа responder'а указывает на более далёкий срок. Это защита от responder'а с некорректной или подозрительно долгой настройкой NextUpdate: агент всё равно перепроверит статус не позже, чем через max_cache_ttl.

error_cache_ttl - если запрос к OCSP responder'у не удался (недоступен, таймаут, невалидный ответ), результат error кэшируется на этот срок - чтобы при потоке новых соединений не долбить лежащий responder повторными запросами на каждое из них.

Конфигурация HAProxy

Смотри examples/ocsp.spoe.cfg и examples/haproxy.cfg.

Ключевые моменты:

  • bind ... ssl ca-file client-ca.pem verify required - HAProxy сам проверяет цепочку доверия и подпись; агент добавляет поверх этого проверку отзыва через OCSP (то, что штатно HAProxy для клиентских сертификатов не делает).
  • option var-prefix ocsp в SPOE-конфиге - переменные агента оказываются доступны как var(txn.ocsp.ocsp_status).
  • Fail-open vs fail-closed. Если агент недоступен, упал или не ответил вовремя, HAProxy по умолчанию просто не получит переменную - и ACL по revoked её не увидит, то есть трафик пройдёт (fail-open). Это стандартное поведение SPOE. Если нужна fail-closed-политика (блокировать при недоступности агента), явно проверяйте var(txn.ocsp.ocsp_status) -m found, как в примере конфига.

Тестирование OCSP responder'а

test/check-ocsp.sh - не тест самого агента, а быстрая проверка того, что конкретный OCSP responder вообще доступен и корректно отвечает по протоколу (независимо от того, отозван ли конкретный сертификат). Нужен только openssl и curl.

test/check-ocsp.sh                                   # URL по умолчанию
test/check-ocsp.sh -u http://tlss.lv.local:8080/ocsp # свой URL
test/check-ocsp.sh -c client.pem -i ca.pem            # с реальным сертификатом/издателем

Без -c/-i скрипт сам генерирует одноразовую пару CA+сертификат - responder её не узнает, но корректный ответ unauthorized/unknown уже означает PASS (эндпоинт живой и говорит по OCSP). С -c/-i скрипт печатает настоящий Cert Status (good/revoked/unknown).

About

SPOA Stream Processing Offload Agent for ocsp check users certs in HAProxy

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Contributors

Languages