Веб-серверу на виртуальной машине не нужен сертификат, выпущенный вручную на год. Stronghold Agent запрашивает сертификат в механизме секретов PKI, записывает его на диск, перевыпускает до истечения срока действия и перезагружает Nginx. Закрытый ключ создаётся в Stronghold и передаётся только Agent на ВМ.

Схема выпуска и обновления TLS-сертификата веб-сервера

Цель

Настроить на ВМ Stronghold Agent, который выпускает для Nginx сертификат www.example.com сроком на 72 часа из промежуточного CA pki_int, автоматически перевыпускает его и выполняет перезагрузку Nginx без разрыва соединений.

Предварительные требования

Перед началом подготовьте:

  • Промежуточный CA в Stronghold, подключённый по пути pki_int, как в руководстве «Внутренний PKI на Stronghold».
  • Виртуальная машина Linux с systemd и Nginx.
  • Stronghold Agent, установленный на ВМ и входящий в Stronghold по AppRole, как в руководстве «Приложение на ВМ со Stronghold Agent».
  • Токен Stronghold с правами на настройку PKI, AppRole и политик.

Шаг 1. Создайте роль PKI

Роль ограничивает имена и срок жизни сертификатов, которые может получить веб-сервер:

d8 stronghold write pki_int/roles/web-server allowed_domains="example.com" allow_subdomains=true allow_bare_domains=true max_ttl=720h
  • allow_subdomains=true разрешает имена в домене example.com, например www.example.com.
  • allow_bare_domains=true разрешает само имя example.com. Без этого параметра Stronghold отклоняет запрос, в котором example.com указан в alt_names.

Короткий срок жизни сертификата снижает последствия компрометации ключа: Agent обновляет сертификат автоматически, поэтому ручной перевыпуск не нужен.

Шаг 2. Создайте политику и роль AppRole

Выдайте ВМ доступ только к выпуску сертификатов и доставьте на неё учётные данные AppRole.

  1. Создайте политику, которая разрешает выпуск сертификатов только по роли web-server:

    d8 stronghold policy write web-server-tls - <<'POLICY'
    path "pki_int/issue/web-server" {
      capabilities = ["update"]
    }
    POLICY
  2. Создайте роль AppRole для ВМ:

    d8 stronghold write auth/approle/role/web-server token_policies=web-server-tls token_ttl=1h token_max_ttl=24h secret_id_ttl=720h secret_id_bound_cidrs="10.0.10.20/32"
  3. Доставьте role_id и обёрнутый secret_id роли web-server на ВМ в директорию /etc/stronghold-agent/, как описано в шаге 3 руководства «Приложение на ВМ со Stronghold Agent». В командах этого шага замените myapp-vm на web-server.

Шаг 3. Создайте шаблон

Создайте файл /etc/stronghold-agent/templates/www.pem.ctmpl. Шаблон записывает закрытый ключ, сертификат и цепочку промежуточных CA в один файл, поэтому ключ и сертификат всегда соответствуют друг другу:

{{ with secret "pki_int/issue/web-server" "common_name=www.example.com" "alt_names=example.com" "ttl=72h" }}
{{ .Data.private_key }}
{{ .Data.certificate }}
{{ range .Data.ca_chain }}{{ . }}
{{ end }}{{ end }}

Каждый вызов secret "pki_int/issue/..." выпускает сертификат с новым ключом. Если нужны отдельные файлы для сертификата и ключа, как в примере из раздела «Основные возможности», используйте в обоих шаблонах вызов с одинаковыми аргументами.

Одинаковые вызовы (тот же путь и те же аргументы) Agent выполняет один раз и использует результат во всех шаблонах, поэтому ключ и сертификат в разных файлах будут парой.

Вместо secret можно использовать функцию шаблона pkiCert. Она читает сертификат из файла destination шаблона и запрашивает новый сертификат, только если файла нет или срок действия сертификата в нём скоро истечёт. Поэтому перезапуск Agent не приводит к выпуску нового сертификата. Функция возвращает поля .Cert, .Key, .CA и .CAChain, которые шаблон записывает в файл destination.

Шаг 4. Настройте Agent

Настройте Agent на выпуск сертификата и перезагрузку Nginx и выдайте Agent нужные права.

  1. Создайте файл /etc/stronghold-agent/agent.hcl:

    stronghold {
      address = "https://stronghold.example.com"
    }
    
    auto_auth {
      method "approle" {
        mount_path = "auth/approle"
        config = {
          role_id_file_path                   = "/etc/stronghold-agent/role-id"
          secret_id_file_path                 = "/etc/stronghold-agent/secret-id"
          secret_id_response_wrapping_path    = "auth/approle/role/web-server/secret-id"
          remove_secret_id_file_after_reading = true
        }
      }
    }
    
    template {
      source               = "/etc/stronghold-agent/templates/www.pem.ctmpl"
      destination          = "/etc/nginx/tls/www.pem"
      perms                = "0600"
      command              = "/usr/bin/systemctl reload nginx"
      command_timeout      = "30s"
      error_on_missing_key = true
    }
    • secret_id_response_wrapping_path — путь создания обёрнутого secret_id роли web-server. Agent проверяет этот путь перед развёртыванием secret_id.
    • perms = "0600" — файл содержит закрытый ключ. Мастер-процесс Nginx читает его с правами root.
    • command — выполняется после каждой записи файла. При перезагрузке мастер-процесс Nginx проверяет новую конфигурацию, перечитывает сертификат и не разрывает текущие соединения. Если в конфигурации есть ошибка, Nginx продолжает работать с прежней конфигурацией.
  2. Разрешите пользователю stronghold-agent выполнять только перезагрузку nginx.service. Unit Agent запускается от пользователя stronghold-agent с NoNewPrivileges=true, поэтому sudo в command не работает. Создайте файл /etc/polkit-1/rules.d/50-stronghold-agent-nginx.rules:

    polkit.addRule(function(action, subject) {
        if (action.id == "org.freedesktop.systemd1.manage-units" &&
            subject.user == "stronghold-agent" &&
            action.lookup("unit") == "nginx.service" &&
            action.lookup("verb") == "reload") {
            return polkit.Result.YES;
        }
    });

    polkit читает файлы правил автоматически. Параметры unit и verb передаёт systemd версии 226 и выше.

  3. Создайте директорию для сертификата:

    sudo install -d -o stronghold-agent -g root -m 0750 /etc/nginx/tls
  4. Разрешите Agent запись в /etc/nginx/tls и /etc/stronghold-agent. Unit Agent использует ProtectSystem=strict, поэтому добавьте эти директории в ReadWritePaths в drop-in-файле. Выполните sudo systemctl edit stronghold-agent и добавьте строки:

    [Service]
    ReadWritePaths=/etc/stronghold-agent /etc/nginx/tls

    Владельцем директории /etc/stronghold-agent должен быть пользователь stronghold-agent. Иначе Agent не сможет удалить файл secret_id после чтения.

Шаг 5. Настройте Nginx

Укажите один и тот же файл в ssl_certificate и ssl_certificate_key:

server {
    listen 443 ssl;
    server_name www.example.com example.com;

    ssl_certificate     /etc/nginx/tls/www.pem;
    ssl_certificate_key /etc/nginx/tls/www.pem;
    ssl_protocols       TLSv1.2 TLSv1.3;

    location / {
        root /var/www/html;
    }
}

Запустите Agent до первого запуска Nginx с этой конфигурацией, чтобы файл сертификата уже существовал. Затем запустите Nginx:

sudo systemctl restart stronghold-agent
sudo systemctl start nginx

Если Nginx уже запущен, вместо systemctl start nginx выполните sudo systemctl reload nginx.

Шаг 6. Как работает обновление

Сертификат PKI выдаётся без продлеваемой аренды. Agent отслеживает срок действия сертификата и до его истечения повторно выполняет pki_int/issue/web-server, перезаписывает файл и выполняет command. Выбирайте ttl в шаблоне так, чтобы перевыпуск происходил не реже, чем вы готовы перезагружать Nginx, и не позже, чем вы успеете заметить сбой.

Agent перевыпускает сертификат, когда проходит около 90 % оставшегося срока его действия (со случайным разбросом ±5 %, чтобы клиенты не приходили одновременно). Для ttl = 24h это примерно через 21–22 часа.

Apache HTTP Server

Для Apache используйте тот же шаблон и тот же блок template со следующими отличиями:

  • в конфигурации виртуального хоста укажите файл в директивах SSLCertificateFile и SSLCertificateKeyFile. Apache 2.4.8 и выше читает промежуточные сертификаты из SSLCertificateFile:

    <VirtualHost *:443>
        ServerName www.example.com
        SSLEngine on
        SSLCertificateFile    /etc/apache2/tls/www.pem
        SSLCertificateKeyFile /etc/apache2/tls/www.pem
    </VirtualHost>
  • в destination укажите /etc/apache2/tls/www.pem, а в command — /usr/bin/systemctl reload apache2 или /usr/bin/systemctl reload httpd в зависимости от дистрибутива. При перезагрузке Apache выполняет плавный перезапуск и не разрывает текущие соединения;

  • в правиле polkit укажите unit apache2.service или httpd.service вместо nginx.service, а в ReadWritePaths — директорию /etc/apache2/tls вместо /etc/nginx/tls.

Проверка

Проверьте, что Nginx отдаёт сертификат, выпущенный Stronghold, и что сертификат перевыпускается.

  1. Проверьте журнал Agent — должны быть сообщения о рендеринге шаблона и выполнении команды:

    sudo journalctl -u stronghold-agent -n 50
  2. Проверьте сертификат, который отдаёт Nginx:

    openssl s_client -connect www.example.com:443 -servername www.example.com </dev/null 2>/dev/null | openssl x509 -noout -subject -issuer -serial -dates

    Издатель должен совпадать с промежуточным CA pki_int, а срок действия — с ttl из шаблона.

  3. Проверьте цепочку доверия с корневым сертификатом CA:

    curl --cacert root-ca.pem -sSI https://www.example.com
  4. Проверьте перевыпуск: временно задайте в шаблоне ttl=10m, перезапустите Agent и убедитесь, что серийный номер сертификата из шага 2 проверки меняется без ручных действий.

Очистка

Чтобы удалить конфигурацию примера, выполните следующие действия:

  1. Остановите Agent и удалите блок template и файл шаблона.

  2. Удалите файл /etc/nginx/tls/www.pem и уберите директивы ssl_certificate из конфигурации Nginx.

  3. Удалите файл /etc/polkit-1/rules.d/50-stronghold-agent-nginx.rules. Уберите /etc/nginx/tls из ReadWritePaths в drop-in-файле Agent: выполните sudo systemctl edit stronghold-agent.

  4. При необходимости отзовите выпущенный сертификат и удалите роли и политику:

    d8 stronghold write pki_int/revoke serial_number=<SERIAL_NUMBER>
    d8 stronghold delete pki_int/roles/web-server
    d8 stronghold delete auth/approle/role/web-server
    d8 stronghold policy delete web-server-tls

    <SERIAL_NUMBER> — серийный номер сертификата из шага 2 проверки, например 39:dd:2e:....

Дополнительные ресурсы