Есть два типа инфраструктурных инженеров:

- те, у кого сертификаты лежат в

/root/certs-final-final-REAL/

- и те, кто уже поставил себе нормальный PKI на HashiCorp Vault.

Сегодня как раз про вторых. Ну или про тех, кто очень скоро ими станет. Потому что рано или поздно любая инфраструктура приходит к простой мысли:

“А может хватит руками генерировать сертификаты в три часа ночи через openssl?”

И тут появляется Vault PKI.

Что вообще такое PKI в Vault

Vault умеет быть полноценным центром сертификации:

  • выпускать сертификаты;
  • отзывать их;
  • хранить CRL;
  • выдавать short-lived сертификаты;
  • автоматически подписывать CSR;
  • работать как Root CA и Intermediate CA;
  • интегрироваться с Kubernetes, Nomad, Consul, PostgreSQL, Nginx, Envoy, Istio и вообще всем, что умеет TLS.

И самое приятное:

сертификаты становятся частью инфраструктуры, а не ZIP-архивом “certs_new2_ok”.

Что будем строить

Сделаем:

  • Root CA;
  • Intermediate CA;
  • роли для сервисов;
  • автоматическую выдачу сертификатов;
  • примеры для:
    • Kubernetes;
    • PostgreSQL;
    • Nginx;
    • внутреннего API;
    • mTLS;
    • SSH;
    • Docker Registry;
    • GitLab;
    • VPN.

Да, сегодня будет много Bash. Прям вот реальный “DevOps Bash Developer”.

Почему Root CA нельзя держать онлайн

Вот тут важный момент.

Правильная схема выглядит так:

Root CA (offline) -> Intermediate CA (Vault) -> Service certificates
Почему?

Потому что если кто-то украдёт ваш Root CA - всё.
Это уже не инцидент. Это лапки к верху и уходим обратно в тех.поддержку.

Поэтому:

  • Root CA держим отдельно;
  • Intermediate CA - в Vault;
  • Vault подписывает сервисные сертификаты. 

Поднимаем Vault

Самый простой dev-mode:

docker run \
  --cap-add=IPC_LOCK \
  -e VAULT_DEV_ROOT_TOKEN_ID=root \
  -p 8200:8200 \
  hashicorp/vault

Но так делать в production нельзя. Вообще! Совсем нельзя, даже если очень хочется!

Production Vault - это:

  • Raft storage;
  • auto-unseal;
  • TLS;
  • HA;
  • audit;
  • policies;
  • backups;
  • replication.

Иначе это не Vault. Это “секреты в контейнере”.

Включаем PKI engine

vault secrets enable pki

Увеличиваем TTL:

vault secrets tune -max-lease-ttl=87600h pki

87600 часов = 10 лет.

Теперь генерируем Root CA.

Генерация Root CA

vault write -field=certificate \
  pki/root/generate/internal \
  common_name="corp.local Root CA" \
  ttl=87600h > root_ca.crt

Vault создаст:

  • private key;
  • root certificate;
  • CA chain.

Настройка URL

Очень многие забывают это. А потом CRL и AIA выглядят как “localhost:8200”.

Красиво. Но бесполезно.

Настраиваем URLs

vault write pki/config/urls \
  issuing_certificates="https://vault.corp.local/v1/pki/ca" \
  crl_distribution_points="https://vault.corp.local/v1/pki/crl"

Создаём роль

Например для внутренних сервисов.

vault write pki/roles/internal-services \
    allowed_domains="svc.cluster.local,corp.local" \
    allow_subdomains=true \
    max_ttl="72h"

Теперь Vault сможет выпускать сертификаты.

Выпуск сертификата

vault write pki/issue/internal-services \
    common_name="api.corp.local"

Получаем:

  • certificate;
  • private_key;
  • issuing_ca;
  • serial_number.

И всё это JSON’ом. Как мы любим.

Kubernetes + Vault PKI

Вот тут начинается магия.

Вместо:

  • cert-manager;
  • self-signed chaos;
  • “а где ключ?”;
  • “а кто обновит ingress?”;

можно использовать Vault напрямую.

ServiceAccount для Vault

apiVersion: v1
kind: ServiceAccount
metadata:
  name: vault-auth
  namespace: vault

Включаем Kubernetes auth

vault auth enable kubernetes

Настраиваем auth

vault write auth/kubernetes/config \
  token_reviewer_jwt="$TOKEN" \
  kubernetes_host="https://10.96.0.1:443" \
  kubernetes_ca_cert=@ca.crt

Политика

path "pki/issue/internal-services" {
  capabilities = ["update"]
}

Роль

vault write auth/kubernetes/role/backend \
    bound_service_account_names=backend \
    bound_service_account_namespaces=prod \
    policies=backend-pki \
    ttl=1h

Получение сертификата из Pod

curl \
  --request POST \
  --data '{"common_name":"backend.prod.svc.cluster.local"}' \
  $VAULT_ADDR/v1/pki/issue/internal-services

Vault Agent Injector

А теперь красиво.

annotations:
  vault.hashicorp.com/agent-inject: "true"
  vault.hashicorp.com/role: "backend"

  vault.hashicorp.com/agent-inject-secret-cert.pem: "pki/issue/internal-services"

  vault.hashicorp.com/agent-inject-template-cert.pem: |
    {{- with secret "pki/issue/internal-services" "common_name=backend.prod.svc.cluster.local" -}}
    {{ .Data.certificate }}
    {{- end }}

Vault сам:

  • выпустит сертификат;
  • положит в контейнер;
  • обновит его.

Без shell-скриптов из 2017 года.

mTLS между сервисами

Вот это вообще топовая история. Каждый сервис получает свой сертификат:

frontend -> backend
backend -> postgres
worker -> rabbitmq

И всё общение:

  • шифруется;
  • проверяется;
  • валидируется.

Никаких:

ALLOW_ALL_INTERNAL=true

Потому что “мы же внутри сети”.

Да-да. А потом кто-то случайно делает

kubectl port-forward

на прод.

PostgreSQL + Vault PKI

Генерируем cert

vault write pki/issue/internal-services \
  common_name="postgres.corp.local"

PostgreSQL config

ssl = on
ssl_cert_file = '/etc/ssl/postgres.crt'
ssl_key_file = '/etc/ssl/postgres.key'
ssl_ca_file = '/etc/ssl/root_ca.crt'

pg_hba.conf

hostssl all all 0.0.0.0/0 cert clientcert=verify-full

Теперь PostgreSQL требует сертификаты клиентов.

Очень бодрит разработчиков. Особенно тех, кто привык подключаться как:

psql -U postgres

Без пароля. Без TLS. Из кофейни. Где-то в Индонезии.

Nginx + mTLS

Сервер

ssl_client_certificate /etc/nginx/root_ca.crt;
ssl_verify_client on;

Проверка CN

if ($ssl_client_s_dn !~ "CN=backend") {
    return 403;
}

Docker Registry с Vault CA

Docker registry очень любит TLS.

Генерируем cert

vault write pki/issue/internal-services \
  common_name="registry.corp.local"

registry config

http:
  addr: :5000
  tls:
    certificate: /certs/registry.crt
    key: /certs/registry.key

GitLab Runner + Vault CA

Когда Runner общается с GitLab через mTLS - это уже прям инфраструктурный luxury mode.

Настройка runner

tls-ca-file = "/etc/gitlab-runner/root_ca.crt"

VPN через Vault PKI

Например OpenVPN. Да-да! Знаю! 2026 год на дворе - какой нафиг OpenVPN...

Сертификат клиента

vault write pki/issue/vpn \
  common_name="user01"

Почему short-lived сертификаты - это кайф

Вместо:

cert valid until 2038

делаем:

ttl = 24h

Что это даёт:

  • украденный cert быстро умрёт;
  • меньше возни с revocation;
  • меньше blast radius;
  • проще аудит.

И это уже подход больших инфраструктур, а не “ну мы потом поменяем”.

Спойлер: не поменяете.

Intermediate CA

Теперь по-взрослому.

Включаем второй PKI engine

vault secrets enable -path=pki_int pki

CSR

vault write \
  -format=json \
  pki_int/intermediate/generate/internal \
  common_name="corp.local Intermediate CA" \
  | jq -r '.data.csr' > pki_intermediate.csr

Подписываем CSR Root CA

vault write \
  -format=json \
  pki/root/sign-intermediate \
  csr=@pki_intermediate.csr \
  format=pem_bundle ttl="43800h"

Импортируем сертификат

vault write pki_int/intermediate/set-signed \
  certificate=@signed_intermediate.crt

Теперь Vault выпускает сертификаты через Intermediate CA.

Вот так уже делают взрослые инфраструктуры, где слово “compliance” перестало быть мемом.

Автоматическая ротация сертификатов

И вот тут Vault становится особенно прекрасным.

Можно:

  • перевыпускать сертификаты автоматически;
  • ротировать ключи;
  • обновлять sidecar’ы;
  • reload’ить Nginx;
  • обновлять Envoy;
  • интегрировать всё с Kubernetes rollout.

Vault Agent Template

template {
  destination = "/etc/tls/tls.crt"

  contents = <<EOH
{{ with secret "pki/issue/internal-services" "common_name=api.corp.local" }}
{{ .Data.certificate }}
{{ end }}
EOH
}

Envoy + Vault

Envoy вообще создан для таких историй.

mTLS + SPIFFE + short-lived certs + automatic rotation.

Это уже уровень:

“да, у нас service mesh, observability и zero trust”.

А не:

iptables + надежда на лучшее и вера в людей.

Аудит Vault

Обязательно включаем.

vault audit enable file file_path=/vault/logs/audit.log

Иначе потом: Кто выпустил wildcard *.prod.local ?

Что ещё умеет Vault PKI

Очень много всего:

  • OCSP;
  • CRL;
  • ACME;
  • SPIFFE;
  • cross-signing;
  • multi issuer;
  • external CA;
  • CSR signing;
  • hardware HSM;
  • auto renew;
  • enterprise replication;
  • namespaces;
  • PKCS#11.

Vault вообще штука из серии:

“мы хотели хранить секреты, а в итоге построили half of modern infrastructure”.

Где это реально используют

Практически везде:

  • Kubernetes;
  • Service Mesh;
  • internal API;
  • PostgreSQL;
  • Kafka;
  • RabbitMQ;
  • NATS;
  • MinIO;
  • Ceph;
  • GitLab;
  • Jenkins;
  • Docker Registry;
  • OpenVPN;
  • WireGuard PKI;
  • SSH CA;
  • Nomad;
  • Consul Connect.

И чем больше инфраструктура - тем быстрее приходит понимание:

сертификаты должны жить как сервис, а не как архив на сервере Пети.

Итоги

Vault PKI - это:

  • централизованный TLS;
  • автоматизация;
  • безопасность;
  • короткоживущие сертификаты;
  • mTLS;
  • нормальная ротация;
  • меньше ручного ада;
  • меньше “ой, сертификат вчера истёк”.

И самое главное:

инфраструктура начинает выглядеть как инженерная система, а не коллекция bash-скриптов с историческим наследием.

И напоследок

Блин… Я же забыл. Опять мысль убежала вперёд головы. Быстрее, чем terraform apply в пятницу вечером.

Мы вообще почти не поговорили про:

  • auth methods Vault;
  • Transit engine;
  • KV v2;
  • AppRole;
  • Vault Agent;
  • Response Wrapping;
  • Sentinel;
  • Raft storage;
  • Seal/Unseal;
  • namespaces;
  • policies;
  • OIDC;
  • LDAP;
  • dynamic secrets;
  • database engine;
  • SSH engine;
  • audit devices;
  • performance replication;
  • DR replication;
  • HSM;
  • plugins;
  • CSI provider;
  • Vault Secrets Operator;
  • Agent Injector;
  • integrated storage;
  • telemetry и Prometheus.

Короче, исправлюсь…

Давайте в следующей статье я уже нормально и подробно разложу весь HashiCorp Vault по полочкам.
Потому что там реально целая вселенная, и каждая кнопка может либо спасти инфраструктуру, либо подарить очень интересный созвон в четыре часа утра.