Templating (Рендеринг шаблонов)
Templating позволяет создавать файлы конфигурации, наполненные секретами из Stronghold, используя язык шаблонов Consul Template для рендеринга файлов.
Существует два режима шаблонов:
template(рендер в файл) — Agent генерирует/обновляет файл на диске (например,application.properties,nginx.conf,*.pem) и при необходимости выполняет команду (command) для reload сервиса.env_template+exec(рендер в ENV и запуск процесса) — Agent формирует значения переменных окружения и запускает приложение как дочерний процесс (exec). При изменении секретов процесс может быть перезапущен.
Механизм работы Agent:
- Читает файл-шаблон с плейсхолдерами.
- Запрашивает секреты из Stronghold.
- Рендерит финальный файл, подставляя реальные значения.
- Сохраняет файл с указанными правами доступа.
- (Опционально) Выполняет команду для перезагрузки приложения.
Когда используется:
- Legacy приложения, читающие конфигурационные файлы.
- Приложения без поддержки Stronghold/Vault API.
- Необходимость доставки секретов в стандартные форматы (.properties, .conf, .ini, .yaml).
- Динамические database credentials.
- PKI сертификаты.
Синтаксис шаблонов
Базовая структура:
{{ with secret "path/to/secret" }}
{{ .Data.field_name }}
{{ end }}
Для KV v2 (secret/data/…):
{{ with secret "secret/data/myapp" }}
username = {{ .Data.data.username }}
password = {{ .Data.data.password }}
{{ end }}
Для динамических секретов (database, PKI):
{{ with secret "database/creds/myapp" }}
DB_USER={{ .Data.username }}
DB_PASS={{ .Data.password }}
{{ end }}
Основные функции:
| Функция | Описание | Пример |
|---|---|---|
secret |
Получение секрета | {{ with secret "secret/data/myapp" }}{{ .Data.data.password }}{{ end }} |
base64Encode |
Кодирование в base64 | {{ "password" \| base64Encode }} |
base64Decode |
Декодирование из base64 | {{ .Data.cert \| base64Decode }} |
toJSON |
Преобразование в JSON | {{ .Data \| toJSON }} |
toYAML |
Преобразование в YAML | {{ .Data \| toYAML }} |
toLower / toUpper |
Изменение регистра | {{ .Data.name \| toUpper }} |
trim |
Удаление пробелов | {{ .Data.value \| trim }} |
range |
Итерация по массиву | {{ range .Items }}{{ .Name }}{{ end }} |
env |
Получение ENV переменной | {{ env "HOME" }} |
timestamp |
Получение текущего времени | {{ timestamp "2006-01-02 15:04:05" }} |
Настройка Templating: пошаговый пример
Сценарий: Legacy Java-приложение читает database credentials из application.properties
Шаг 1: Сохранение секретов в Stronghold
# Создайте статический секрет.
stronghold kv put secret/myapp/config \
db_host=postgres.prod.example.com \
db_port=5432 \
db_name=production \
db_user=app_user \
db_password=SecureP@ssw0rd
Шаг 2: Создание файла-шаблона
Создайте /etc/myapp/templates/application.properties.ctmpl:
# Database Configuration.
{{ with secret "secret/data/myapp/config" }}
spring.datasource.url=jdbc:postgresql://{{ .Data.data.db_host }}:{{ .Data.data.db_port }}/{{ .Data.data.db_name }}
spring.datasource.username={{ .Data.data.db_user }}
spring.datasource.password={{ .Data.data.db_password }}
{{ end }}
# Подключение пула.
spring.datasource.hikari.maximum-pool-size=10
spring.datasource.hikari.minimum-idle=5
Шаг 3: Настройка Agent конфигурации
Создаем /etc/stronghold-agent/agent.hcl:
# Подключение к Stronghold.
stronghold {
address = "https://stronghold.example.com:8200"
}
# Auto-Auth с AppRole.
auto_auth {
method {
type = "approle"
config = {
role_id_file_path = "/etc/stronghold-agent/role-id"
secret_id_file_path = "/etc/stronghold-agent/secret-id"
remove_secret_id_file_after_reading = false
}
}
sink {
type = "file"
config = {
path = "/var/run/stronghold-agent/token"
}
}
}
# Templating блок.
template {
# Путь к шаблону.
source = "/etc/myapp/templates/application.properties.ctmpl"
# Путь к финальному файлу.
destination = "/etc/myapp/application.properties"
# Права доступа (обязательно 0600 или 0400 для секретов).
perms = "0600"
# Владелец файла (опционально).
user = "myapp"
group = "myapp"
# Команда для перезагрузки приложения после изменения.
command = "systemctl reload myapp"
# Таймаут выполнения команды.
command_timeout = "30s"
# Выполнять команду только при изменении содержимого.
# (не при каждом обновлении lease).
wait {
min = "2s"
max = "10s"
}
# Ошибка, если ключ отсутствует.
error_on_missing_key = true
}
Шаг 4: Запустите Agent
# Проверка конфигурации через пробный запуск:
# Agent читает конфиг, проходит аутентификацию, создает файл и сразу завершается.
stronghold-agent -config=/etc/stronghold-agent/agent.hcl -exit-after-auth -log-level=debug
Что происходит при выполнении команды:
- Agent читает и парсит конфигурацию (
agent.hcl) - Подключается к Stronghold серверу
- Выполняет аутентификацию (AppRole: читает role-id и secret-id)
- Получает токен и сохраняет в sink (
/var/run/stronghold-agent/token) - Запрашивает секреты из Stronghold
- Рендерит шаблон и создает файл (
/etc/myapp/application.properties) - Завершается с кодом 0 (успех) благодаря флагу
-exit-after-auth
Проверка успешности:
# 1. Проверьте, что токен создан.
ls -la /var/run/stronghold-agent/token
# Должен быть файл с недавней датой.
# 2. Проверьте, что целевой файл создан.
ls -la /etc/myapp/application.properties
# Должен быть файл с правами 0600.
# 3. Проверьте содержимое (осторожно - там пароли!).
sudo cat /etc/myapp/application.properties
# Должны быть реальные значения секретов, не {{ ... }}.
После успешной проверки - запуск как systemd service:
systemctl start stronghold-agent
systemctl status stronghold-agent
# Проверка логов.
journalctl -u stronghold-agent -f
Продвинутые сценарии
Пример 1: Динамические database credentials
# Шаблон: /etc/myapp/db-config.conf.ctmpl
{{ with secret "database/creds/myapp-role" }}
# Auto-generated credentials (TTL: 1h)
# Rotation: automatic
DB_USER={{ .Data.username }}
DB_PASS={{ .Data.password }}
DB_LEASE_ID={{ .LeaseID }}
DB_LEASE_DURATION={{ .LeaseDuration }}
{{ end }}
Agent автоматически:
- Запрашивает временные credentials.
- Обновляет файл при ротации (перед истечением TTL).
- Выполняет команду перезагрузки приложения.
Пример 2: PKI сертификаты
# Шаблон: /etc/nginx/ssl/cert.pem.ctmpl
{{ with secret "pki/issue/web-server" "common_name=app.example.com" "ttl=720h" }}
{{ .Data.certificate }}
{{ .Data.ca_chain }}
{{ end }}
# Шаблон: /etc/nginx/ssl/key.pem.ctmpl
{{ with secret "pki/issue/web-server" "common_name=app.example.com" "ttl=720h" }}
{{ .Data.private_key }}
{{ end }}
Agent-конфигурация:
template {
source = "/etc/nginx/ssl/cert.pem.ctmpl"
destination = "/etc/nginx/ssl/cert.pem"
perms = "0644"
}
template {
source = "/etc/nginx/ssl/key.pem.ctmpl"
destination = "/etc/nginx/ssl/key.pem"
perms = "0600"
command = "systemctl reload nginx"
}
Пример 3: Условная логика и циклы
# Шаблон с условиями
{{ with secret "secret/data/myapp/config" }}
{{ if eq .Data.data.environment "production" }}
LOG_LEVEL=ERROR
DEBUG_MODE=false
{{ else }}
LOG_LEVEL=DEBUG
DEBUG_MODE=true
{{ end }}
API_KEY={{ .Data.data.api_key }}
{{ end }}
# Цикл по списку
{{ with secret "secret/data/myapp/allowed-ips" }}
{{ range $index, $ip := .Data.data.ips }}
allow {{ $ip }};
{{ end }}
{{ end }}
Пример 4: Множественные секреты в одном файле
# Database credentials
{{ with secret "database/creds/app" }}
DB_USER={{ .Data.username }}
DB_PASS={{ .Data.password }}
{{ end }}
# API Keys
{{ with secret "secret/data/myapp/api-keys" }}
STRIPE_KEY={{ .Data.data.stripe_key }}
SENDGRID_KEY={{ .Data.data.sendgrid_key }}
{{ end }}
# Redis credentials
{{ with secret "secret/data/myapp/redis" }}
REDIS_HOST={{ .Data.data.host }}
REDIS_PASSWORD={{ .Data.data.password }}
{{ end }}
Важные параметры template блока
| Параметр | Описание | Пример |
|---|---|---|
source |
Путь к файлу-шаблону | /etc/app/template.ctmpl |
destination |
Путь к результирующему файлу | /etc/app/config.conf |
perms |
Права доступа (восьмеричная система) | "0600", "0644" |
user |
Владелец файла | "myapp" |
group |
Группа файла | "myapp" |
command |
Команда после рендеринга | "systemctl reload app" |
command_timeout |
Таймаут команды | "30s" |
error_on_missing_key |
Ошибка при отсутствии ключа | true / false |
wait.min |
Минимальное время между обновлениями | "2s" |
wait.max |
Максимальное время между обновлениями | "10s" |
backup |
Создавать backup перед перезаписью | true / false |
template vs env_template — в чём разница и что выбрать
template — это рендеринг секретов в файл на диске.
- Используйте
template, если приложение/сервис читает конфигурацию из файлов:.conf/.ini/.yaml/.properties, TLS*.pem, ключи, сертификаты и т.п. - В
templateдоступен полный “файловый” набор опций:destination,perms,user/group,backup,wait, а такжеcommandдля перезагрузки сервиса после изменения (напримерsystemctl reload nginx).
env_template + exec — это запуск приложения как дочернего процесса, где секреты попадают в переменные окружения процесса.
- Используйте
env_template, если приложение читает конфигурацию из ENV (12-factor style) и допустим перезапуск при ротации секретов. - В Stronghold Agent каждый
env_templateзадаёт значение ровно одной переменной окружения и всегда пишется какenv_template "VAR_NAME" { ... }. - Важно:
env_templateне создаёт.envфайл. Поляdestination/perms/command/wait/...дляenv_templateне поддерживаются (они доступны только вtemplate).
Практические нюансы:
- Если вы используете
templateи запускаете Agent под systemd с hardening (ProtectSystem=strict), убедитесь чтоReadWritePathsвключает директориюtemplate.destination(иначе рендеринг будет падать из-за запрета записи). - Если вы используете
env_templateдля запуска Docker-контейнера, переменные окружения нужно явно пробрасывать вdocker runчерез--env VAR_NAME(или другим способом), иначе они останутся только в окружении самого Agent.
Режим супервизора процессов (Process Supervisor Mode)
Режим Process Supervisor позволяет Agent запускать приложение как дочерний процесс и инжектировать секреты напрямую в переменные окружения.
В этом режиме Agent:
- Запускается как родительский процесс.
- Запрашивает секреты из Stronghold.
- Формирует переменные окружения из шаблона.
- Запускает приложение как дочерний процесс с этими переменными.
- Отслеживает изменения секретов.
- При изменении секретов - перезапускает приложение с новыми значениями.
Ограничения режима:
execдолжен использоваться вместе хотя бы с однимenv_template.env_templateнельзя комбинировать сtemplateиapi_proxyв одном конфиге (это разные режимы работы).env_templateвсегда задается какenv_template "VAR_NAME" { ... }и формирует значение ровно одной переменной окружения.
Преимущества:
- Секреты никогда не записываются на диск.
- Автоматический перезапуск при обновлении секретов.
- Изоляция секретов на уровне процесса.
- Подходит для 12-factor приложений.
- Простая миграция legacy-приложений на ENV-конфиги.
Когда использовать:
- Приложения, читающие конфигурацию из ENV переменных.
- Высокие требования к безопасности (секреты не должны касаться диска).
- Контейнеризированные приложения на VM.
- Динамические credentials с частой ротацией.
- Разработка и тестирование.
Настройка Process Supervisor: Пошаговый пример
Сценарий: Java Spring Boot приложение читает секреты из переменных окружения.
Шаг 1: Подготовка приложения.
Spring Boot приложение должно читать конфигурацию из ENV:
Конфигурация application.properties:
# application.properties - использует ENV переменные.
server.port=8080
# Database - значения берутся из ENV.
spring.datasource.url=${DB_URL}
spring.datasource.username=${DB_USERNAME}
spring.datasource.password=${DB_PASSWORD}
spring.datasource.driver-class-name=org.postgresql.Driver
# JPA.
spring.jpa.hibernate.ddl-auto=validate
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
# API Key.
api.key=${API_KEY}
Шаг 2: Сохранение секретов в Stronghold.
# Настройте Database Secrets Engine для динамических credentials.
stronghold write database/config/postgresql \
plugin_name=postgresql-database-plugin \
allowed_roles="myapp-role" \
connection_url="postgresql://{{username}}:{{password}}@postgres.prod:5432/myapp?sslmode=require" \
username="vault_admin" \
password="admin_password"
# Создайте роль для приложения.
stronghold write database/roles/myapp-role \
db_name=postgresql \
creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; \
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO \"{{name}}\";" \
default_ttl="1h" \
max_ttl="24h"
# Статические секреты (API ключи).
stronghold kv put secret/myapp/config \
api_key=sk_live_1234567890abcdef
Шаг 3: Настройка Agent в режиме Supervisor.
Создаем /etc/stronghold-agent/agent.hcl:
# Подключение к Stronghold.
stronghold {
address = "https://stronghold.example.com:8200"
}
# Auto-Auth с AppRole
auto_auth {
method {
type = "approle"
config = {
role_id_file_path = "/etc/stronghold-agent/role-id"
secret_id_file_path = "/etc/stronghold-agent/secret-id"
remove_secret_id_file_after_reading = false
}
}
}
# Process Supervisor - запуск Spring Boot.
exec {
# Команда для запуска приложения.
command = ["/usr/bin/java", "-jar", "/opt/myapp/demo-application.jar"]
# Перезапуск при изменении секретов (ротация database credentials).
restart_on_secret_changes = "always"
# Сигнал для остановки процесса (по умолчанию SIGTERM).
restart_stop_signal = "SIGTERM"
}
# Шаблон переменных окружения.
# Важно: `env_template` задается в виде `env_template "ИМЯ_ПЕРЕМЕННОЙ" { ... }`.
# Каждый блок формирует значение одной ENV переменной, которая будет передана дочернему процессу.
env_template "DB_URL" {
contents = "jdbc:postgresql://postgres.prod:5432/myapp"
}
env_template "DB_USERNAME" {
contents = "{{ with secret \"database/creds/myapp-role\" }}{{ .Data.username }}{{ end }}"
}
env_template "DB_PASSWORD" {
contents = "{{ with secret \"database/creds/myapp-role\" }}{{ .Data.password }}{{ end }}"
}
env_template "API_KEY" {
contents = "{{ with secret \"secret/data/myapp/config\" }}{{ .Data.data.api_key }}{{ end }}"
}
env_template "JAVA_OPTS" {
contents = "-Xmx2g -Xms512m -XX:+UseG1GC"
}
env_template "SPRING_PROFILES_ACTIVE" {
contents = "production"
}
Шаг 4: Запуск Agent
# Команда для Agent, он запустит Java приложение с секретами в ENV.
stronghold-agent -config=/etc/stronghold-agent/agent.hcl
Agent автоматически:
- Аутентифицируется в Stronghold.
- Получает database credentials и API ключи.
- Запускает Java приложение с секретами в переменных окружения.
- При ротации credentials - перезапускает приложение с новыми значениями.
Примеры для разных языков программирования
Go приложение
exec {
command = ["/opt/myapp/myapp-server"]
restart_on_secret_changes = "always"
restart_stop_signal = "SIGTERM"
}
env_template "DB_HOST" {
contents = "{{ with secret \"secret/data/myapp/config\" }}{{ .Data.data.db_host }}{{ end }}"
}
env_template "DB_PORT" {
contents = "{{ with secret \"secret/data/myapp/config\" }}{{ .Data.data.db_port }}{{ end }}"
}
env_template "DB_NAME" {
contents = "{{ with secret \"secret/data/myapp/config\" }}{{ .Data.data.db_name }}{{ end }}"
}
env_template "DB_USER" {
contents = "{{ with secret \"secret/data/myapp/config\" }}{{ .Data.data.db_user }}{{ end }}"
}
env_template "DB_PASSWORD" {
contents = "{{ with secret \"secret/data/myapp/config\" }}{{ .Data.data.db_password }}{{ end }}"
}
env_template "API_KEY" {
contents = "{{ with secret \"secret/data/myapp/config\" }}{{ .Data.data.api_key }}{{ end }}"
}
env_template "LOG_LEVEL" {
contents = "info"
}
Docker контейнер на VM
exec {
command = [
"/usr/bin/docker", "run", "--rm",
"--name", "myapp",
"-p", "8080:8080",
# Проброс переменных окружения из окружения Stronghold Agent внутрь контейнера:
"--env", "DOCKER_ENV_API_KEY",
"--env", "DOCKER_ENV_DATABASE_URL",
"myapp:latest"
]
restart_on_secret_changes = "always"
restart_stop_signal = "SIGTERM"
}
env_template "DOCKER_ENV_API_KEY" {
contents = "{{ with secret \"secret/data/myapp/config\" }}{{ .Data.data.api_key }}{{ end }}"
}
env_template "DOCKER_ENV_DATABASE_URL" {
contents = "{{ with secret \"secret/data/myapp/config\" }}{{ .Data.data.database_url }}{{ end }}"
}
Важные параметры exec блока
| Параметр | Описание | Значение по умолчанию |
|---|---|---|
command |
Команда запуска приложения (массив) | (обязательный) |
restart_on_secret_changes |
Перезапуск при изменении секретов: never, always |
always |
restart_stop_signal |
Сигнал для остановки процесса | SIGTERM |
Важные параметры env_template блока
| Параметр | Описание | Пример |
|---|---|---|
contents |
Inline шаблон переменных окружения | <<-EOT ... EOT |
source |
Путь к файлу-шаблону (альтернатива contents) |
"/etc/app/env.ctmpl" |
error_on_missing_key |
Ошибка при отсутствии ключа | true / false |
Примечание: в Stronghold Agent блок
env_templateвсегда имеет имя переменной окружения:env_template "MY_VAR" { ... }. Поляdestination/perms/command/wait/...вenv_templateне поддерживаются (они доступны только в обычномtemplateблоке).
Управление жизненным циклом процесса
При изменении секретов (например, ротация database credentials) Agent:
- Получает новые секреты.
- Формирует новые ENV переменные.
- Отправляет
SIGTERMдочернему процессу (приложение должно корректно обрабатыватьSIGTERM). - Перезапускает процесс с обновленными переменными.
Кэширование и ротация токенов
Кэширование токена (Token Caching):
- Кеширование токена после аутентификации.
- Использование кешированного токена для всех запросов.
- Уменьшение нагрузки на Stronghold сервер.
Автоматическое обновление токена (Token Renewal):
Agent получает токен с ограниченным сроком действия (например, 1 час) и заранее продлевает его. Если продление невозможно — Agent повторно аутентифицируется. Это нужно для того, чтобы приложение могло работать непрерывно, без ручного вмешательства.
Автоматическое обновление секретов (Lease Renewal):
Динамические секреты (например, database credentials) тоже имеют срок действия. Agent автоматически продлевает их перед истечением, затем обновляет файлы конфигурации и может перезагрузить приложение. Таким образом, сredentials всегда актуальны, приложение не падает из-за истекших паролей.
API Proxy
Agent может выступать в роли прокси для Stronghold API:
Возможности:
- Локальный HTTP(S) endpoint для приложений.
- Автоматическое добавление токена аутентификации.
- Кеширование ответов (опционально).
- Снижение сетевой нагрузки.
Конфигурация:
api_proxy {
use_auto_auth_token = true
}
listener "tcp" {
address = "127.0.0.1:8200"
tls_disable = true
}
Использование приложения:
# Приложение обращается к локальному Agent`у.
curl http://127.0.0.1:8200/v1/secret/data/myapp
# Agent автоматически добавляет токен и проксирует на Stronghold сервер.
Auto-Auth (Автоматическая аутентификация)
Auto-Auth — это ключевая возможность Stronghold Agent, которая полностью автоматизирует процесс получения и обновления токена аутентификации.
Как это работает:
- Agent запускается с настроенным методом аутентификации.
- Автоматически проходит аутентификацию в Stronghold.
- Получает токен и использует его для своих операций (templating, API proxy).
- Если настроен sink — записывает токен в файл для использования другими процессами. Sink — это файл, куда Agent записывает полученный токен.
- Автоматически обновляет токен перед истечением TTL.
- При необходимости повторно аутентифицируется
Настройка sink опциональна:
- Если sink настроен – токен записывается в файл (например,
/var/run/stronghold-agent/token), доступный для чтения другими процессами. - Если sink не настроен – токен используется Agent только для внутренних операций (templating, caching).
Поддерживаемые методы аутентификации:
- AppRole — рекомендуемый для VM/bare-metal.
- Token — для простых сценариев.
- JWT/OIDC — для интеграции с identity providers.
- Облачные провайдеры.
AppRole (Рекомендуемый для VM/Bare-Metal)
AppRole — это метод аутентификации, предназначенный для машин и приложений.
Концепция:
- Role ID — идентификатор роли (аналог username).
- Secret ID — секретный идентификатор (аналог password).
- Оба ID требуются для аутентификации.
Преимущества:
- Разделение обязанностей (Role ID и Secret ID доставляются разными путями).
- Гибкая настройка политик.
- Поддержка CIDR restrictions.
- Secret ID может быть одноразовым.
Настройка на Stronghold сервере:
# Включите AppRole.
stronghold auth enable approle
# Создайте роль.
stronghold write auth/approle/role/myapp \
token_ttl=1h \
token_max_ttl=4h \
policies="myapp-policy"
# Получите Role ID.
stronghold read auth/approle/role/myapp/role-id
# Создайте Secret ID.
stronghold write -f auth/approle/role/myapp/secret-id
Конфигурация в Agent:
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"
remove_secret_id_file_after_reading = true
}
}
}
Доставка и хранение credentials:
Role ID:
- Что это: Публичный идентификатор роли, не является секретом.
- Как доставляется: Через configuration management (Ansible, Puppet), в образе VM, или вручную.
- Где хранится: Путь задается в конфигурации Agent параметром
role_id_file_path.- Типичный путь:
/etc/stronghold-agent/role-id. - Права доступа: 0640, owner: stronghold-agent.
- Можно использовать любой путь по вашему выбору.
- Типичный путь:
- Удаление: НЕ удаляется после использования, используется повторно при переаутентификации.
- Безопасность: Можно хранить в git репозитории, не критично если будет скомпрометирован (без Secret ID бесполезен).
Secret ID:
- Что это: Секретный идентификатор, аналог пароля.
- Как доставляется:
- Вручную администратором при первом запуске сервера.
- Через защищенное SSH соединение.
- Через внутренний портал самообслуживания (если есть).
- Через encrypted переменную в CI/CD системе.
- НЕ должен быть в git или configuration management.
- Где хранится: Путь задается в конфигурации Agent параметром
secret_id_file_path.- Типичный путь:
/etc/stronghold-agent/secret-id. - Права доступа: 0640, owner: stronghold-agent.
- Можно использовать любой путь по вашему выбору.
- Типичный путь:
- Удаление: Может быть удален после использования (параметр
remove_secret_id_file_after_reading = trueв конфиге Agent). - Безопасность: критичный секрет, должен быть защищен.
Типы Secret ID:
Параметры secret_id_num_uses и secret_id_ttl задаются на Stronghold сервере при создании роли или генерации Secret ID. Agent просто использует уже созданный Secret ID.
-
Одноразовый (num_uses=1):
# На Stronghold сервере при создании роли: stronghold write auth/approle/role/myapp \ secret_id_num_uses=1 \ policies="myapp-policy" # Или при генерации конкретного Secret ID: stronghold write -f auth/approle/role/myapp/secret-id num_uses=1
Особенности:
- Используется только один раз для аутентификации.
- После использования становится невалидным.
- Наиболее безопасный вариант для production.
- Agent должен иметь
remove_secret_id_file_after_reading = trueв конфигураионном файле
-
Многоразовый (num_uses=0):
# На Stronghold сервере: stronghold write auth/approle/role/myapp \ secret_id_num_uses=0 \ policies="myapp-policy"
Особенности:
- Может использоваться множество раз.
- Удобен для тестирования и разработки.
- Менее безопасен (если скомпрометирован - требуется ручная ротация).
-
С ограниченным TTL:
# На Stronghold сервере: stronghold write auth/approle/role/myapp \ secret_id_ttl=24h \ policies="myapp-policy"
Особенности:
- Истекает через указанное время (24 часа в примере).
- Баланс между безопасностью и удобством.
- После истечения TTL требуется новый Secret ID.
Полный пример настройки и доставки credentials:
# ШАГ 1: Настройка на Stronghold сервере (выполняет администратор).
# Включитe AppRole метод аутентификации.
stronghold auth enable approle
# Создайте политику доступа для приложения.
stronghold policy write myapp-policy - <<EOF
path "secret/data/myapp/*" {
capabilities = ["read"]
}
path "database/creds/myapp" {
capabilities = ["read"]
}
EOF
# Создайте роль AppRole с настройками.
stronghold write auth/approle/role/myapp \
token_ttl=1h \ # Время жизни токена (обновляется автоматически).
token_max_ttl=4h \ # Максимальное время жизни токена (после этого требуется переаутентификация).
policies="myapp-policy" \ # Политика доступа (какие секреты может читать).
secret_id_num_uses=1 \ # Количество использований Secret ID (1 = одноразовый).
secret_id_ttl=24h # Время жизни Secret ID (истекает через 24 часа).
# Получите Role ID.
stronghold read auth/approle/role/myapp/role-id
# Вывод: role_id abc123-def456-ghi789.
# Сгенерируйте одноразовый Secret ID.
stronghold write -f auth/approle/role/myapp/secret-id
# Вывод: secret_id xyz789-abc123-def456.
# ШАГ 2: Доставка credentials на целевой сервер.
# Создайте директорию на целевом сервере.
ssh root@app-server.example.com << 'ENDSSH'
mkdir -p /etc/stronghold-agent
chown root:stronghold-agent /etc/stronghold-agent
chmod 750 /etc/stronghold-agent
ENDSSH
# Доставьте Role ID (можно через automation или вручную).
ssh root@app-server.example.com << 'ENDSSH'
echo -n "abc123-def456-ghi789" > /etc/stronghold-agent/role-id
chown stronghold-agent:stronghold-agent /etc/stronghold-agent/role-id
chmod 0640 /etc/stronghold-agent/role-id
ENDSSH
# Доставьте Secret ID через защищенный канал (одноразовый).
ssh root@app-server.example.com << 'ENDSSH'
echo -n "xyz789-abc123-def456" > /etc/stronghold-agent/secret-id
chown stronghold-agent:stronghold-agent /etc/stronghold-agent/secret-id
chmod 0640 /etc/stronghold-agent/secret-id
ENDSSH
# ШАГ 3: Настройка конфигурации Agent на целевом сервере.
# Создайте конфигурационный файл.
cat > /etc/stronghold-agent/agent.hcl <<EOF
stronghold {
address = "https://stronghold.example.com:8200"
}
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"
remove_secret_id_file_after_reading = true
}
}
sink "file" {
config = {
path = "/var/run/stronghold-agent/token"
mode = 0640
}
}
}
EOF
# Установите права на конфигурацию.
chown root:stronghold-agent /etc/stronghold-agent/agent.hcl
chmod 0640 /etc/stronghold-agent/agent.hcl
# ШАГ 4: Запуск Agent`а.
# Запустите Agent.
systemctl start stronghold-agent
# Проверьте успешную аутентификацию.
journalctl -u stronghold-agent -n 50 | grep -i "authentication successful"
# Проверьте наличие токена.
ls -la /var/run/stronghold-agent/token
# Проверьте, что Secret ID удален (если remove_secret_id_file_after_reading = true).
ls -la /etc/stronghold-agent/secret-id
# Должно быть: No such file or directory.
# ШАГ 5: Последующая работа.
# При перезапуске Agent будет использовать только Role ID.
# Токен обновляется автоматически каждые ~59 минут (за 1 минуту до истечения TTL).
# Secret ID больше не требуется.
Рекомендации:
- Используйте одноразовые Secret ID для production.
- Разделяйте доставку: Role ID через automation, Secret ID через защищенный канал.
- Ограничивайте доступ по CIDR (
secret_id_bound_cidrs). - Логируйте все использования Secret ID в Stronghold для аудита.
Token (для простых сценариев)
Прямое использование токена — самый простой метод аутентификации. Agent читает готовый токен из файла и использует его.
Концепция:
- Токен создается заранее администратором на Stronghold сервере.
- Доставляется на целевой сервер любым удобным способом.
- Agent просто читает токен из файла и использует.
Когда использовать:
- Тестовые окружения и разработка.
- Временные инсталляции.
- Сценарии, где нельзя использовать AppRole.
- Простые cases без высоких требований к безопасности.
Недостатки:
- Менее безопасно (токен — это долгоживущий credential).
- Нет разделения обязанностей (как в AppRole).
- При компрометации требуется ручная ротация.
- Не рекомендуется для production окружений.
Полный пример настройки:
# ШАГ 1: Создание токена на Stronghold сервере
# Создайте политику доступа.
stronghold policy write myapp-policy - <<EOF
path "secret/data/myapp/*" {
capabilities = ["read"]
}
path "database/creds/myapp" {
capabilities = ["read"]
}
EOF
# Создайте токен с параметрами.
stronghold token create \
-policy=myapp-policy \
-ttl=720h \ # Время жизни 30 дней.
-renewable=true \ # Можно обновлять.
-display-name="myapp-agent" \
-format=json
# Вывод:
# {
# "auth": {
# "client_token": "hvs.CAES...xyz123",
# "policies": ["default", "myapp-policy"],
# "renewable": true,
# "lease_duration": 2592000
# }
# }
# Сохраните токен.
export AGENT_TOKEN="hvs.CAES...xyz123"
# ШАГ 2: Доставка токена на целевой сервер
# Создайте директорию.
ssh root@app-server.example.com << 'ENDSSH'
mkdir -p /etc/stronghold-agent
chown root:stronghold-agent /etc/stronghold-agent
chmod 750 /etc/stronghold-agent
ENDSSH
# Доставьте токен через защищенный канал.
echo -n "$AGENT_TOKEN" | ssh root@app-server.example.com 'cat > /etc/stronghold-agent/token'
ssh root@app-server.example.com << 'ENDSSH'
chown stronghold-agent:stronghold-agent /etc/stronghold-agent/token
chmod 0640 /etc/stronghold-agent/token
ENDSSH
# ШАГ 3: Настройка конфигурации Agent
cat > /etc/stronghold-agent/agent.hcl <<EOF
stronghold {
address = "https://stronghold.example.com:8200"
}
auto_auth {
method "token_file" {
config = {
token_file_path = "/etc/stronghold-agent/token"
}
}
# Sink опционален для token метода.
sink "file" {
config = {
path = "/var/run/stronghold-agent/token"
mode = 0640
}
}
}
# Пример шаблона.
template {
source = "/etc/stronghold-agent/templates/database.conf.ctmpl"
destination = "/etc/myapp/database.conf"
perms = "0600"
}
EOF
chown root:stronghold-agent /etc/stronghold-agent/agent.hcl
chmod 0640 /etc/stronghold-agent/agent.hcl
# ШАГ 4: Запуск Agent
systemctl start stronghold-agent
# Проверьте работу.
journalctl -u stronghold-agent -n 50
# Токен будет автоматически обновляться до истечения max_ttl.
Важные параметры токена:
- ttl — начальное время жизни токена.
- renewable — можно ли обновлять токен (должен быть true для Agent).
- period — если задан, токен обновляется на этот период (например,
period=24h— токен обновляется каждые 24 часа). - explicit-max-ttl — абсолютное максимальное время жизни (после этого токен перестает обновляться).
Рекомендации по безопасности:
- Используйте токены с
renewable=trueдля автоматического обновления. - Установите разумный TTL (например, 30 дней).
- Настройте explicit-max-ttl для ограничения общего времени жизни.
- Регулярно проверяйте и отзывайте неиспользуемые токены.
- Храните токен с минимальными правами доступа (0640).
- Для production рассмотрите использование AppRole вместо token.
JWT/OIDC (Для интеграции с Identity Providers)
JWT/OIDC аутентификация позволяет использовать существующую инфраструктуру identity management для аутентификации в Stronghold.
Концепция:
- Приложение получает JWT токен от Identity Provider (Keycloak, Azure AD, Google, etc.).
- JWT токен содержит claims (утверждения) о пользователе или сервисе.
- Stronghold проверяет подпись JWT и извлекает claims.
- На основе claims выдается Stronghold токен с соответствующими политиками.
Сценарии использования:
- Интеграция с корпоративным SSO (Single Sign-On).
- Использование service accounts из Identity Provider.
- Федеративная аутентификация между организациями.
- CI/CD интеграция через OIDC (GitHub Actions, GitLab CI).
Преимущества:
- Централизованное управление идентификацией.
- Не нужно создавать отдельные credentials для каждого приложения,
- Автоматическая ротация JWT токенов через Identity Provider.
- Поддержка MFA и других возможностей IdP.
Полный пример настройки (с Keycloak):
# ШАГ 1: Настройка JWT auth метода на Stronghold сервере.
# Включите JWT auth метод
stronghold auth enable jwt
# Настройте JWT метод с параметрами Keycloak,
stronghold write auth/jwt/config \
oidc_discovery_url="https://keycloak.example.com/realms/myrealm" \
oidc_client_id="stronghold" \
oidc_client_secret="client-secret-from-keycloak" \
default_role="default"
# Создайте политику доступа.
stronghold policy write myapp-jwt-policy - <<EOF
path "secret/data/myapp/*" {
capabilities = ["read"]
}
path "database/creds/myapp" {
capabilities = ["read"]
}
EOF
# Создайте роль для JWT аутентификации.
stronghold write auth/jwt/role/myapp-role \
role_type="jwt" \
bound_audiences="stronghold" \
user_claim="sub" \
bound_subject="service-account-myapp" \
token_ttl=1h \
token_max_ttl=4h \
token_policies="myapp-jwt-policy"
# Параметры роли:
# - bound_audiences: какие audience должны быть в JWT.
# - user_claim: какой claim использовать как username.
# - bound_subject: конкретное значение subject (опционально).
# - bound_claims: дополнительные требования к claims.
# Пример с более сложными условиями:
stronghold write auth/jwt/role/myapp-role \
role_type="jwt" \
bound_audiences="stronghold" \
user_claim="sub" \
bound_claims='{"environment":"production","app":"myapp"}' \
claim_mappings='{"department":"dept"}' \
token_policies="myapp-jwt-policy"
# ШАГ 2: Получение JWT токена от Identity Provider.
# Пример 1: Keycloak service account.
curl -X POST "https://keycloak.example.com/realms/myrealm/protocol/openid-connect/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "client_id=myapp-service" \
-d "client_secret=service-secret" \
-d "grant_type=client_credentials" \
| jq -r '.access_token' > /tmp/jwt-token.txt
# Пример 2: GitHub Actions OIDC token.
# В GitHub Actions workflow:
# - uses: actions/checkout@v3
# - name: Get OIDC token
# run: |
# curl -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
# "$ACTIONS_ID_TOKEN_REQUEST_URL" | jq -r '.value' > jwt-token.txt
# ШАГ 3: Доставка JWT токена на целевой сервер.
# Создайте директорию.
ssh root@app-server.example.com << 'ENDSSH'
mkdir -p /etc/stronghold-agent
chown root:stronghold-agent /etc/stronghold-agent
chmod 750 /etc/stronghold-agent
ENDSSH
# Доставьте JWT токен.
scp /tmp/jwt-token.txt root@app-server.example.com:/etc/stronghold-agent/jwt-token
ssh root@app-server.example.com << 'ENDSSH'
chown stronghold-agent:stronghold-agent /etc/stronghold-agent/jwt-token
chmod 0640 /etc/stronghold-agent/jwt-token
ENDSSH
# ШАГ 4: Настройка конфигурации Agent
cat > /etc/stronghold-agent/agent.hcl <<EOF
stronghold {
address = "https://stronghold.example.com:8200"
}
auto_auth {
method "jwt" {
mount_path = "auth/jwt"
config = {
path = "/etc/stronghold-agent/jwt-token"
role = "myapp-role"
}
}
sink "file" {
config = {
path = "/var/run/stronghold-agent/token"
mode = 0640
}
}
}
template {
source = "/etc/stronghold-agent/templates/database.conf.ctmpl"
destination = "/etc/myapp/database.conf"
perms = "0600"
}
EOF
chown root:stronghold-agent /etc/stronghold-agent/agent.hcl
chmod 0640 /etc/stronghold-agent/agent.hcl
# ШАГ 5: Запуск Agent.
systemctl start stronghold-agent
# Проверьте успешность аутентификации.
journalctl -u stronghold-agent -n 50 | grep -i "authentication successful"
# Проверьте Stronghold токен.
cat /var/run/stronghold-agent/token
Особенности JWT метода:
- JWT токен vs Stronghold токен:.
- JWT токен — это credential от Identity Provider (короткоживущий, обычно 5-60 минут).
- После аутентификации Agent получает Stronghold токен.
- Stronghold токен обновляется автоматически (как в AppRole).
- JWT токен НЕ обновляется Agent автоматически.
- Периодическое обновление JWT:.
- Если JWT истекает, нужно получить новый от IdP.
-
Можно настроить cron job для периодического обновления:
# /etc/cron.d/refresh-jwt */30 * * * * stronghold-agent /usr/local/bin/refresh-jwt-token.sh
Проверка JWT токена:
# Декодируйте JWT для проверки claims.
cat /etc/stronghold-agent/jwt-token | cut -d. -f2 | base64 -d | jq
# Вывод покажет claims:
# {
# "sub": "service-account-myapp",
# "aud": "stronghold",
# "iss": "https://keycloak.example.com/realms/myrealm",
# "exp": 1234567890,
# "iat": 1234567800
# }
Рекомендации:
- Использовать короткий TTL для JWT токенов (5-15 минут).
- Настройте bound_audiences для защиты от переиспользования токенов.
- Используйте bound_subject или bound_claims для строгой проверки.
- Для production используйте OIDC discovery (автоматическое обновление ключей).
- Логируйте все аутентификации для аудита.