step-ca в Docker Compose: готовый файл
Когда внутренних сервисов на сервере больше пяти — Grafana, Portainer, pgAdmin, внутренние API — самоподписанные сертификаты и ручное подтверждение "Продолжить, я знаю о риске" в браузере превращаются в рутину, а Let's Encrypt для сервисов без белого домена и вовсе не подходит. Решение — поднять свой центр сертификации на базе step-ca от Smallstep: он выдаёт сертификаты по протоколу ACME, как Let's Encrypt, но для внутренней сети, и умеет ротировать их автоматически. Ниже — рабочий docker-compose.yml, инициализация и подключение к Traefik.
Содержание
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →Что такое step-ca и когда он нужен на своём сервере
step-ca — это открытый (Apache 2.0) центр сертификации от команды Smallstep, тот же движок, что используется в Netflix и в ряде корпоративных PKI. По сути это ACME-сервер, который вы полностью контролируете: он подписывает сертификаты для доменов и IP-адресов, которые не видны из интернета, и делает это без ручного участия — клиенты запрашивают сертификат по тому же протоколу, что используют certbot или acme.sh для Let's Encrypt, только точка входа — не acme-v02.api.letsencrypt.org, а ваш собственный сервер.
Типичные сценарии, где это оправдано:
- mTLS между микросервисами — каждый контейнер получает клиентский сертификат, и сервисы проверяют друг друга, а не просто доверяют сетевой изоляции Docker.
- Внутренние панели без белого домена — Grafana, Portainer, Kibana доступны только по VPN или из локальной сети, но браузер всё равно должен показывать закрытый замок, а не предупреждение.
- Короткоживущие сертификаты вместо длинных паролей — вместо API-ключа сервис предъявляет сертификат со сроком жизни 24 часа, украденный ключ бесполезен уже на следующий день.
Если у вас один-два внутренних сервиса и есть возможность прокинуть DNS через Cloudflare с DNS-01 challenge — проще обойтись обычным Let's Encrypt (см. статью про certbot и acme.sh). step-ca нужен, когда сервисов много, домены внутренние, а сертификаты должны обновляться без вашего участия.
Готовый docker-compose.yml для step-ca
Ниже минимальная, но production-пригодная конфигурация. Она использует встроенный в официальный образ механизм автоинициализации через переменные DOCKER_STEPCA_INIT_* — при первом запуске контейнер сам создаст root- и intermediate-сертификаты и провижионеры, без интерактивного диалога.
services:
step-ca:
image: smallstep/step-ca:latest
container_name: step-ca
restart: unless-stopped
ports:
- "9000:9000"
volumes:
- step-data:/home/step
environment:
DOCKER_STEPCA_INIT_NAME: "Internal CA"
DOCKER_STEPCA_INIT_DNS_NAMES: "step-ca,step-ca.internal,10.0.0.5"
DOCKER_STEPCA_INIT_PROVISIONER_NAME: "admin"
DOCKER_STEPCA_INIT_ACME: "true"
DOCKER_STEPCA_INIT_PASSWORD_FILE: /home/step/secrets/password
secrets:
- source: stepca_password
target: /home/step/secrets/password
networks:
- internal
healthcheck:
test: ["CMD", "step", "ca", "health"]
interval: 30s
timeout: 5s
retries: 3
secrets:
stepca_password:
file: ./secrets/stepca_password.txt
volumes:
step-data:
networks:
internal:
driver: bridge
Что здесь важно:
DOCKER_STEPCA_INIT_DNS_NAMES— все имена и IP, по которым к CA будут обращаться клиенты: DNS-имя контейнера в сети Compose (step-ca), внутреннее доменное имя, при необходимости — IP сервера. Этот список попадёт в SAN сертификата самого API step-ca.DOCKER_STEPCA_INIT_ACME: "true"— сразу поднимает ACME-провижионер с именемacme, через него и будут выдаваться сертификаты клиентам (Traefik, certbot, acme.sh).- Пароль через Docker secret, а не голым текстом в environment — это тот же принцип, что разобран в статье про Docker secrets: пароль шифрования приватных ключей CA не должен светиться в
docker inspectили логах. - Отдельная сеть
internal— step-ca не нужно публиковать наружу вообще, если все клиенты — контейнеры на этом же сервере; порт 9000 можно даже убрать изports, оставив сервис доступным только внутри Docker-сети (тип bridge разобран в статье про сети Docker).
Файл с паролем создайте заранее и закройте права:
mkdir -p secrets
openssl rand -base64 32 > secrets/stepca_password.txt
chmod 600 secrets/stepca_password.txt
Дальше — docker compose up -d. При первом старте в логах (docker compose logs step-ca) появится вывод инициализации с отпечатком (fingerprint) корневого сертификата — сохраните его, он понадобится для bootstrap-а клиентов.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверИнициализация: root CA, intermediate CA и провижионеры
step-ca по умолчанию создаёт двухуровневую иерархию: root CA подписывает intermediate CA, а уже intermediate подписывает конечные (leaf) сертификаты сервисов. Это стандартная практика PKI — приватный ключ root можно после инициализации вынести в офлайн-хранилище, а онлайн-сервис step-ca работает только с ключом intermediate. Если он скомпрометирован — вы пересоздаёте только его, не трогая root и не переустанавливая доверие на всех клиентах.
После автоинициализации внутри volume step-data появится структура:
/home/step/
├── certs/
│ ├── root_ca.crt
│ └── intermediate_ca.crt
├── secrets/
│ ├── root_ca_key
│ ├── intermediate_ca_key
│ └── password
└── config/
└── ca.json
Отпечаток root-сертификата, если нужно получить его повторно:
docker compose exec step-ca step certificate fingerprint /home/step/certs/root_ca.crt
Провижионеры — это способы аутентификации клиента перед выдачей сертификата. По умолчанию создаются два: admin (JWK-провижионер, вход по логину/паролю через step ca certificate) и acme (для автоматических клиентов). Посмотреть список:
docker compose exec step-ca step ca provisioner list
Добавить ещё один ACME-провижионер под конкретную группу сервисов (например, отдельно для mTLS между бэкендами) можно так:
docker compose exec step-ca step ca provisioner add acme-internal --type ACME
ACME-провижионер: автоматическая выдача и ротация
Главное отличие step-ca от самоподписанного сертификата, сгенерированного openssl один раз — сертификаты здесь короткоживущие по умолчанию (обычно 24 часа для leaf-сертификатов, если не менять ca.json) и ротируются автоматически.
Вручную, для проверки, что всё работает — выпуск сертификата провижионером admin:
docker compose exec step-ca step ca bootstrap \
--ca-url https://step-ca:9000 \
--fingerprint <FINGERPRINT_ИЗ_ЛОГОВ>
docker compose exec step-ca step ca certificate \
service.internal service.crt service.key \
--provisioner admin
Флаг bootstrap сохраняет корневой сертификат в локальный $STEPPATH клиента и добавляет его в доверенные — без этого шага TLS-клиент откажется доверять сертификатам вашего CA, как браузер ругается на самоподписанный сертификат.
Автоматическая ротация — через демон обновления, который следит за сроком жизни и перевыпускает сертификат заранее (обычно в последней трети срока жизни):
step ca renew --daemon \
--exec "nginx -s reload" \
service.crt service.key
Для сервисов, которые сами говорят по ACME (Traefik, Caddy, certbot, acme.sh), ротацию делает сам клиент — step-ca для них ничем не отличается от настоящего Let's Encrypt, кроме URL directory-эндпоинта:
https://step-ca:9000/acme/acme/directory
Подключение Traefik и других сервисов к step-ca
Самый частый кейс — отдать step-ca в качестве CA-сервера для Traefik, чтобы он сам получал и обновлял сертификаты для внутренних поддоменов. Если Traefik уже настроен как реверс-прокси (см. Traefik как реверс-прокси для Docker), добавление step-ca — это ещё один certificatesResolver:
# traefik.yml
certificatesResolvers:
stepca:
acme:
caServer: "https://step-ca:9000/acme/acme/directory"
email: "admin@internal.local"
storage: "/letsencrypt/acme-stepca.json"
httpChallenge:
entryPoint: web
Ключевой нюанс: Traefik (как и большинство ACME-клиентов на базе библиотеки lego) должен доверять сертификату самого API step-ca, иначе TLS-хендшейк к caServer не пройдёт. Для этого в контейнер нужно смонтировать root_ca.crt и указать переменную окружения:
traefik:
environment:
- LEGO_CA_CERTIFICATES=/certs/root_ca.crt
volumes:
- step-data:/certs:ro
где step-data — тот же volume, что у step-ca (можно смонтировать read-only, чтобы Traefik видел certs/root_ca.crt). После этого в лейблах роутера указываете traefik.http.routers.<имя>.tls.certresolver=stepca — и Traefik сам запросит, а потом будет обновлять сертификат.
Для клиентов вне Docker-сети (например, certbot на другом сервере) сначала нужно установить root-сертификат в системное хранилище доверия, иначе сама проверка TLS до directory-эндпоинта завершится ошибкой:
cp root_ca.crt /usr/local/share/ca-certificates/step-ca.crt
update-ca-certificates
certbot certonly --server https://ca.internal:9000/acme/acme/directory \
--standalone -d service.internal
acme.sh умеет то же самое через флаг --ca-bundle, без установки в системное хранилище:
acme.sh --server https://ca.internal:9000/acme/acme/directory \
--issue -d service.internal --standalone \
--ca-bundle /path/to/root_ca.crt
Бэкап, безопасность ключей и типичные ошибки
step-ca — это не просто ещё один контейнер, это корень доверия для всей внутренней инфраструктуры: если потерян intermediate_ca_key, придётся переустанавливать доверие на всех клиентах заново, а это дольше, чем кажется на бумаге.
Что бэкапить обязательно:
| Файл | Критичность | Комментарий |
|---|---|---|
secrets/root_ca_key | максимальная | Лучше вообще вынести в офлайн-хранилище после инициализации |
secrets/intermediate_ca_key | высокая | Компрометация = переподписание всей цепочки |
secrets/password | высокая | Без него ключи выше бесполезны, но хранить отдельно от ключей |
config/ca.json | средняя | Конфигурация провижионеров, легко восстановить руками, но проще с бэкапом |
Простейший вариант — регулярный docker run --rm -v step-data:/data -v $(pwd):/backup alpine tar czf /backup/step-ca-backup.tar.gz -C /data . в cron, с последующей загрузкой архива в отдельное защищённое хранилище, а не рядом с самим сервером.
Частые ошибки:
- "x509: certificate signed by unknown authority" — клиент не прошёл
step ca bootstrapили не получилroot_ca.crtв доверенные. Это самая частая жалоба новичков со step-ca. - Контейнер не стартует после перезапуска — если переменные
DOCKER_STEPCA_INIT_*остались в файле, а volume уже проинициализирован, entrypoint видит существующийca.jsonи пропускает повторную инициализацию. Проблема начинается, если volume удалили, а переменные — нет: CA пересоздастся с новым root-сертификатом, и все ранее выпущенные сертификаты перестанут быть доверенными. - Рассинхронизация времени — короткоживущие сертификаты (сутки) чувствительны к дрейфу часов на клиентах; NTP на всех узлах обязателен.
- Один провижионер на все сервисы — практичнее сразу завести отдельные ACME-провижионеры под разные группы (внешние сервисы, mTLS, CI-раннеры), чтобы при компрометации одного отозвать доступ можно было точечно.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверНужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.
Частые вопросы
Нужен ли step-ca, если сервисы и так закрыты VPN или firewall-ом?
Сетевая изоляция не даёт браузеру зелёного замка и не решает mTLS-задачи — step-ca закрывает уровень TLS-доверия, а не сетевой доступ, это дополняющие меры.
Можно ли использовать step-ca вместе с публичными сертификатами Let's Encrypt на том же сервере?
Да, это разные certificatesResolver в Traefik или отдельные вызовы certbot/acme.sh с разным --server — публичные домены идут через Let's Encrypt, внутренние — через step-ca, конфликта нет.
Что будет с уже выпущенными сертификатами, если контейнер step-ca упадёт?
Они продолжат работать до истечения срока (обычно 24 часа), но новые выдачи и продления прекратятся, поэтому мониторинг доступности step-ca важен не меньше, чем мониторинг самих сервисов.
Как отозвать скомпрометированный сертификат?
step ca revoke <serial> добавляет его в CRL/OCSP-ответ CA; для коротких сроков жизни это часто менее критично, чем для годовых сертификатов, но механизм работает так же.
Стоит ли выносить step-ca на отдельный сервер?
Для небольшой инфраструктуры не обязательно, достаточно отдельного volume с регулярным бэкапом; для крупной PKI root CA стоит держать офлайн, а online CA — на изолированном хосте.
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →