Перейти к содержанию

Секреты интеграций — хранение и чтение

1. Назначение

Модуль Integration Secrets обеспечивает централизованное хранение секретов внешних сервисов (логины, пароли, API-ключи, токены). Секрет идентифицируется по строковому ключу serviceKey и содержит произвольный payload — набор пар «поле → значение».

Все серверные компоненты читают секреты через единый read-only фасад ISecretsProvider. Прямая работа с таблицами *Credentials / *Settings в новом коде не допускается.

Структура сущности

Каждый секрет содержит:

  • serviceKey — уникальный строковый ключ-идентификатор секрета.
  • displayName — отображаемое имя (обязательно, ограничено по длине).
  • payload — набор пар «поле → значение» (Login, Password, ApiKey, AccessToken и др.). Значения хранятся в зашифрованном виде.

2. Ключевые файлы

Файл Назначение
Valhalla.Interfaces/Secrets/ISecretsProvider.cs Контракт провайдера
Valhalla.Secrets/Provider/CompositeSecretsProvider.cs Реализация: new → legacy fallback
Valhalla.Secrets/Provider/LegacySecretsAdapter.cs Адаптер legacy-таблиц
Valhalla.Integration/Valhalla.Integration/Contracts/Secrets/SecretServiceKeys.cs Константы префиксов serviceKey
Valhalla.ExternalServices/Configuration/*ExternalServicesConfigurationService.cs Потребители ISecretsProvider

3. Custom setting UseIntegrationSecrets

Читается в CompositeSecretsProvider как feature flag.

Значение Поведение
true Сначала ищет в IntegrationSecrets; при отсутствии записи — fallback в legacy (*Credentials / *Settings)
false (по умолчанию) Всегда идёт только в legacy

Настраивается через Admin UI → Настройки системы → Custom settings, ключ UseIntegrationSecrets.


4. Архитектура провайдера

ISecretsProvider
    └── CompositeSecretsProvider
            ├── IIntegrationSecretsStore   (новое хранилище, зашифрованный payload)
            └── LegacySecretsAdapter       (legacy *Credentials / *Settings)

LegacySecretsAdapter парсит serviceKey как {prefix}-{serviceId}, резолвит нужный ILegacySecretResolver через DI и возвращает payload из legacy-таблицы. Аудит при legacy-чтении не пишется.


5. Сервисы с готовой поддержкой ISecretsProvider

Все 14 сервисов уже инжектируют ISecretsProvider. При включении UseIntegrationSecrets = true они автоматически начнут читать из нового хранилища — изменений кода не требуется.

Префикс serviceKey Configuration-сервис Legacy-резолвер
ldap ActiveDirectoryExternalServicesConfigurationService, SynchronizationProfilesService ActiveDirectoryLegacySecretResolver
openldap OpenLDAPExternalServicesConfigurationService OpenLDAPLegacySecretResolver
radius RadiusExternalServicesConfigurationService RadiusLegacySecretResolver
diadoc DiadocExternalServicesConfigurationService DiadocLegacySecretResolver
sbis SBISExternalServicesConfigurationService SbisLegacySecretResolver
ews EwsExternalServicesConfigurationService EwsLegacySecretResolver
oauth OAuthExternalServicesConfigurationService OAuthLegacySecretResolver
cryptopro-dss CryptoProExternalServicesConfigurationService CryptoProDssLegacySecretResolver
azure-cognitive AzureExternalServicesConfigurationService AzureCognitiveLegacySecretResolver
pt-sandbox PTSandboxExternalServicesConfigurationService, PTSandboxScanService PTSandboxLegacySecretResolver
kontur-cloud KonturCloudServicesConfigurationService KonturCloudLegacySecretResolver
kontur-kedo KonturKEDOServicesConfigurationService KonturKEDOLegacySecretResolver
multifactor MultifactorExternalServicesConfigurationService MultifactorLegacySecretResolver
jitsi JitsiExternalServicesConfigurationService JitsiLegacySecretResolver

6. Миграция конкретного сервиса

Кодовых изменений не требуется. Достаточно:

  1. Включить UseIntegrationSecrets = true в custom settings (однократно для всего экземпляра).
  2. Создать запись в IntegrationSecrets с нужным serviceKey через Admin API или смарт-скрипт:
UTILS.set_secret_payload("diadoc-1", { Login: "user@domain.ru", Password: "secret" });

После этого CompositeSecretsProvider начнёт возвращать значения из нового хранилища. Legacy-запись продолжает работать как fallback для остальных сервисов, у которых записи ещё нет.


7. Admin API

Endpoint: api/admin/secrets (только god-admin).

Метод URL Назначение
GET api/admin/secrets Список секретов (без значений payload)
GET api/admin/secrets/{serviceKey} Карточка секрета (только payloadKeys)
GET api/admin/secrets/{serviceKey}/payload Расшифрованный payload (фиксируется в аудите)
PUT api/admin/secrets/{serviceKey} Создание / обновление
DELETE api/admin/secrets/{serviceKey} Деактивация (не физическое удаление)
GET api/admin/secrets/contracts Список зарегистрированных контрактов payload
GET api/admin/secrets/{serviceKey}/contract Контракт для конкретного ключа
POST api/admin/secrets/{serviceKey}/audit Аудит по секрету
POST api/admin/secrets/audit Общий аудит с фильтрацией

Разделение метаданных и значений. Списковые и карточечные методы возвращают только payloadKeys — имена полей payload без их значений. Расшифрованные значения доступны исключительно через отдельный endpoint (/payload). Каждый вызов этого endpoint фиксируется в аудите.

Деактивация vs удаление. DELETE выполняет деактивацию записи, а не физическое удаление. Это сохраняет историю в журнале аудита и позволяет отменить операцию при необходимости.


8. Контракты payload

Для зарезервированных префиксов зарегистрированы контракты — допустимые поля payload. При создании/обновлении секрет валидируется против контракта. Список префиксов совпадает с таблицей в п. 5.

Если serviceKey не относится ни к одному зарезервированному префиксу, контракт отсутствует и валидация по полям не выполняется.


9. Валидация при создании и обновлении

При PUT api/admin/secrets/{serviceKey} платформа проверяет:

  • serviceKey — должен соответствовать допустимому формату ключа.
  • displayName — обязателен, ограничен по длине.
  • payload — должен быть непустым объектом.
  • Для зарезервированных префиксов дополнительно проверяется соответствие payload зарегистрированному контракту (допустимые поля).

10. Аудит

Каждое обращение к payload фиксируется с полями: serviceKey, тип действия, пользователь, момент, IP, источник (adminApi / smartScript). Legacy-чтение через LegacySecretsAdapter в аудит не попадает.


11. Смарт-скрипты

// JavaScript — чтение
var apiKey = UTILS.get_secret_value("sbis-1", "ApiKey");
var creds  = UTILS.get_secret_payload("sbis-1");

// JavaScript — запись
UTILS.set_secret_value("sbis-1", "ApiKey", newKey);
UTILS.set_secret_payload("sbis-1", { ApiKey: newKey, Login: "user" });
-- Lua — чтение
local apiKey = UTILS:get_secret_value("sbis-1", "ApiKey")
local creds  = UTILS:get_secret_payload("sbis-1")

-- Lua — запись
UTILS:set_secret_value("sbis-1", "ApiKey", newKey)
UTILS:set_secret_payload("sbis-1", { ApiKey = newKey, Login = "user" })

Если секрет или поле не найдены — возвращается null, исключение не бросается. Каждый вызов фиксируется в аудите с источником smartScript.