Перейти к основному содержимому

ADR-0049: Configuration, Secrets И Options Pattern

Статус

Принято.

Контекст

Pin сервисы имеют множество конфигурируемых параметров: connection strings, JWT secrets, provider credentials, feature flags, rate limits, retry policies, timeouts. Без явного стандарта возникают:

  • secrets в git репозитории (appsettings.json с паролями);
  • IConfiguration["key"] inline без типизации;
  • отсутствие валидации конфигурации при старте — сервис падает в runtime;
  • разные сервисы используют разные env var naming conventions;
  • provider credentials передаются через HttpContext или hardcoded в коде.

Решение

IOptions Pattern Everywhere

Вся конфигурация читается через strongly-typed IOptions<T> — никогда через IConfiguration["key"] в production коде.

// Options class с DataAnnotations
public sealed class JwtTokenOptions
{
public const string SectionName = "JwtToken";

[Required, MinLength(32)]
public required string SigningKey { get; init; }

[Required]
public required string Issuer { get; init; }

[Required, MinLength(1)]
public required string[] Audiences { get; init; }

[Range(1, 1440)]
public int AccessTokenTtlMinutes { get; init; } = 15;
}

// Регистрация с валидацией при старте
builder.Services
.AddOptions<JwtTokenOptions>()
.BindConfiguration(JwtTokenOptions.SectionName)
.ValidateDataAnnotations()
.ValidateOnStart(); // падает при старте, не в runtime

ValidateOnStart — Обязательно

Каждый IOptions<T> регистрируется с .ValidateOnStart(). Сервис не запускается с невалидной конфигурацией — лучше fail fast при старте, чем необъяснимые NullReferenceException в production.

Secrets — Vault И k8s Secrets

СредаСпособ
Local developmentdotnet user-secrets — не в git
CI/CDGitHub Actions / GitLab CI secrets
Staging / ProductionHashiCorp Vault или k8s Secrets + External Secrets Operator

Правило: никакой credential не попадает в appsettings.json, appsettings.Production.json или в git.

Secrets попадают в environment variables или secrets volume. IOptions<T> binding читает их из env vars через SECTION__KEY convention.

Environment Variable Naming

# Convention: SECTION__KEY (double underscore as separator)
JWT_TOKEN__SIGNING_KEY=...
JWT_TOKEN__ISSUER=...
PAYMENT_PROVIDER__API_KEY=...
PAYMENT_PROVIDER__WEBHOOK_SECRET=...
POSTGRES__CONNECTION_STRING=...
REDIS__CONNECTION_STRING=...

Feature Flags

Feature flags хранятся в PlatformSettings (PostgreSQL) и читаются через IFeatureFlagService. Нет hardcoded feature flags в appsettings.json — это не source of truth для runtime behaviour.

Provider Credentials

Каждый external provider имеет свой Options class:

public sealed class SomeProviderOptions
{
public const string SectionName = "SomeProvider";

[Required]
public required string ApiKey { get; init; }

[Required]
public required string WebhookSecret { get; init; }

[Required]
public required Uri BaseUrl { get; init; }

[Range(1, 30)]
public int TimeoutSeconds { get; init; } = 10;
}

Credentials никогда не передаются в HTTP query string или URL path. Только через header или request body по HTTPS.

Configuration Sections Standard

SectionИспользование
JwtTokenJWT signing/validation
PostgresPostgreSQL connection
RedisRedis connection
KafkaKafka bootstrap, SASL credentials
VaultVault URL, AppRole credentials
<ProviderName>Конкретный external provider
RateLimitRate limit параметры
ResilienceRetry, timeout, circuit breaker параметры
FeatureManagementFeature flags (только non-sensitive)

Локальная Разработка

# Использовать dotnet user-secrets для локальных secrets
dotnet user-secrets set "Postgres:ConnectionString" "Host=localhost;..."
dotnet user-secrets set "JwtToken:SigningKey" "local-dev-signing-key-..."

docker-compose.yml для локальной разработки может содержать публичные/non-sensitive dev credentials для PostgreSQL, Redis, Kafka. Production credentials никогда не попадают в docker-compose.

Последствия

  • Сервис с невалидной конфигурацией не запустится — обнаруживается в deployment, не в production трафике.
  • Secrets в git невозможны: CI pipeline scanning обнаруживает credentials.
  • Все параметры типизированы и задокументированы в Options классе.
  • Смена credential не требует изменения кода — только обновление secrets в Vault/k8s.

Ссылки