Секреты¶
Имена входят. Значения никогда не выходят обратно.
Ни один API-эндпоинт не возвращает сохранённое значение секрета. Ни один экран UI не предлагает кнопку «показать». Тот, кто потерял значение, заменяет его — это тот же вызов, что и создание, через ту же форму. Это не решение уровня политики: путь чтения попросту отсутствует в коде. (REQ-1558)
Синтаксис ссылок¶
Три формы ссылок допустимы везде, где Provisa разрешает учётные данные:
| Форма | Откуда разрешается | Кто может её использовать |
|---|---|---|
${env:VAR_NAME} |
Окружение серверного процесса | Только конфигурация развёртывания |
${secret:NAME} |
Хранилище секретов организации — общее для всех участников | Любое поле, принимающее ссылку на учётные данные |
${user:NAME} |
Личное хранилище секретов действующего лица | Любое поле, принимающее ссылку на учётные данные |
Разрешение везде работает по принципу fail-closed (отказ в доступе по умолчанию). Неизвестное имя провайдера, незаданное имя и недоступный бэкенд — каждый случай вызывает ошибку. Ссылка, которую не удалось разрешить, никогда не подменяется молча пустой строкой. (REQ-1557) [tool-verified: provisa/core/secrets.py:92-117]
Формат имени¶
Имена секретов должны соответствовать [A-Za-z_][A-Za-z0-9_]* — буквы, цифры и подчёркивания, начиная с буквы или подчёркивания. Ограничение продиктовано практикой: ${secret:NAME} разбирается грамматикой ссылок, которая читает всё до закрывающей }. Имя, содержащее фигурную скобку, пробел или двоеточие, дало бы ссылку, которая разберётся как что-то другое. [tool-verified: provisa/core/secrets_store.py:61]
Два хранилища, одна служба¶
У каждой организации два хранилища секретов. Оба живут внутри одной службы секретов. (REQ-1560)
Хранилище организации — учётные данные, которые администратор организации кладёт сюда, общие. Каждый участник, ссылающийся на ${secret:DATABASE_TOKEN}, получает одно и то же значение. Это для учётных данных, которыми владеет организация: общий пароль базы данных, ключ сервисного аккаунта, токен развёртывания. Для чтения или записи в хранилище организации требуется возможность org_settings.
Личное хранилище — учётные данные, положенные сюда, принадлежат ровно одному человеку. Когда GIT_TOKEN есть у двоих, ${user:GIT_TOKEN} разрешается в тот, чей владелец действует сейчас. Один и тот же текст ссылки выдаёт каждому его собственные учётные данные. Тот, кто ничего не сохранил, получает ошибку, а не чужое значение. Личное хранилище не закрыто никакой возможностью — держать собственные учётные данные не привилегия, которую выдаёт администратор. И нет синтаксиса запроса, чтобы назвать чужое хранилище. [tool-verified: provisa/api/admin/secrets_router.py:86-103]
Область — часть самой ссылки, а не разрешение вокруг неё. ${secret:NAME} и ${user:NAME} никогда не отвечают друг за друга.
Выбор службы секретов¶
Admin → Security → Secrets service. Панель видна всем, кто обладает возможностью platform_settings. Перечислен каждый бэкенд, известный сборке, независимо от того, установлен ли SDK. Затенённая строка сообщает, какого пакета Python не хватает — панель называет его, а не прячет вариант целиком.
В поставке пять бэкендов:
| Ключ | Подпись | Требуется |
|---|---|---|
provisa |
Provisa (built-in, encrypted) | Ничего; это значение по умолчанию |
hashicorp_vault |
HashiCorp Vault (KV v2) | hvac |
aws_secrets_manager |
AWS Secrets Manager | boto3 |
gcp_secret_manager |
Google Secret Manager | google-cloud-secret-manager |
azure_key_vault |
Azure Key Vault (secrets) | azure-keyvault-secrets |
[tool-verified: provisa/core/secrets_registry.py:161-299]
Выбор работает по принципу fail-closed: неизвестный или недоступный бэкенд вызывает ошибку при старте, а не молча откатывается на другой. (REQ-1557)
Собственные учётные данные бэкенда¶
Учётные данные подключения к центральному бэкенду — это конфигурация процесса. Они берутся только из ${env:...} — никогда из ${secret:...}. Служба секретов, чьи собственные учётные данные лежат внутри неё самой, не может быть открыта, поэтому цепочка доверия по замыслу оканчивается в окружении хоста. Реестр обеспечивает это: любое значение конфигурации в спецификации бэкенда разрешается с providers=("env",) до того, как бэкенд будет сконструирован. [tool-verified: provisa/core/secrets_registry.py:128-141]
Пример — конфигурация Vault в provisa.yaml:
secrets:
provider: hashicorp_vault
hashicorp_vault:
url: https://vault.internal:8200
token: ${env:VAULT_TOKEN} # process env only — never ${secret:...}
mount: secret
Центральная служба против встроенной¶
Когда настроена центральная служба, Provisa читает из неё, но не пишет в неё. Создание и удаление записей принадлежит центральной службе — эти операции выполняет её собственный инструментарий. Страница Secrets так и сообщает и не предлагает кнопку создания. (REQ-1557)
Когда активен встроенный бэкенд provisa, страница Secrets полностью доступна на запись: создание, замена и удаление из UI или через API.
Встроенное хранилище Provisa¶
Значение по умолчанию, когда центральная служба не настроена. Каждая строка в secrets_store содержит зашифрованный конверт-блоб — колонка value двоичная, а не текстовая, и ключ расшифровки живёт в окружении процесса, а не в базе данных. Копия плоскости управления без мастер-ключа развёртывания содержит шифротекст и ничего больше. (REQ-1558)
Шифрование никогда не опционально. Когда ключ шифрования на уровне процесса не настроен, хранилище откатывается на локальную связку ключей. Если у хоста нет связки ключей, чтобы хранить ключ, хранилище отказывается писать, а не сохраняет значение в открытом виде. [tool-verified: provisa/core/secrets_store.py:130-159]
Форма хранения [tool-verified: provisa/core/schema_admin.py:493-505]:
| Колонка | Тип | Назначение |
|---|---|---|
org_id |
Text | Организация, владеющая этим секретом |
owner_id |
Text | "*" для хранилища организации; идентификатор пользователя для личного хранилища |
name |
Text | Имя ссылки |
value |
LargeBinary | Зашифрованный конверт-блоб |
description |
Text | Для чего этот секрет — никогда не выводится из значения |
updated_by |
Text | Кто задал его последним |
Колонка value не выбирается ни в одном запросе на перечисление. [tool-verified: provisa/core/secrets_store.py:214-235]
Эндпоинты API¶
Все маршруты находятся под /admin/orgs/{org_id}. Хранилище организации требует org_settings в этой организации. Личное хранилище не требует никакой возможности — владелец считывается из аутентифицированной личности; параметра запроса, чтобы назвать чужое хранилище, не существует.
| Метод | Путь | Что делает |
|---|---|---|
GET |
/secrets |
Перечислить имена и ссылки хранилища организации |
PUT |
/secrets/{name} |
Создать или заменить один секрет организации |
DELETE |
/secrets/{name} |
Удалить один секрет организации |
GET |
/my-secrets |
Перечислить личные имена и ссылки вызывающей стороны |
PUT |
/my-secrets/{name} |
Создать или заменить один секрет вызывающей стороны |
DELETE |
/my-secrets/{name} |
Удалить один секрет вызывающей стороны |
Каждый ответ возвращает метаданные — имя, описание, updated_at, updated_by и строку reference, готовую к вставке, — но никогда значение. Тело PUT несёт value (обязательно) и description (необязательно). Замена — тот же вызов, что и создание: идентичностью служит имя, а не отдельный ID.
Каждая запись фиксируется в журнале аудита. Запись журнала называет действующее лицо и имя секрета. Значение не записывается, даже его длина. [tool-verified: provisa/api/admin/secrets_router.py:106-117]
Где разрешается ${secret:NAME}¶
Разрешение происходит внутри операции, привязанной к контексту, а не во время импорта или старта. Хранилище читает и расшифровывает секреты организации один раз в начале этой операции и держит карту в ContextVar всё время её выполнения. Вне привязанной операции ${secret:NAME} вызывает ошибку. (REQ-1557) [tool-verified: provisa/core/secrets_store.py:269-290]
Привязку устанавливают два места вызова:
Операции с удалённым git. Когда URL удалённого репозитория организации содержит ссылку ${secret:...} или ${user:...} — например, токен push, встроенный в URL, — роутер окружений привязывает и хранилище организации, и личное хранилище действующего пользователя вокруг вызова git. Форма ${user:GIT_TOKEN} означает, что коммит ложится под учётными данными того, кто его отправил, а не общего сервисного аккаунта. [tool-verified: provisa/api/admin/environments_router.py:1263]
Чтение API-ключей AI-вендора. Когда Provisa читает ключ LLM-вендора организации и этот ключ хранится как ссылка ${secret:NAME}, bound_to_request_org устанавливает хранилище организации для этого запроса. Ссылка разрешается на выходе; сам текст ссылки вендору никогда не отправляется. (REQ-1580) [tool-verified: provisa/core/org_secrets.py:76-79]
Ключи AI-вендоров организации как ссылки на секреты¶
Ключ AI-вендора организации (Anthropic, OpenAI и другие) можно хранить как ссылку ${secret:NAME} вместо литерального ключа. (REQ-1580)
Сначала положите ключ в хранилище организации:
PUT /admin/orgs/{org_id}/secrets/OPENAI_KEY
{ "value": "sk-...", "description": "OpenAI production key" }
Затем настройте конфигурацию AI организации так, чтобы она ссылалась на него:
Ссылка хранится зашифрованной в org_secrets. Во время запроса Provisa разрешает ${secret:OPENAI_KEY} по хранилищу организации и передаёт литеральный ключ в SDK вендора. Ротация записи в хранилище вступает в силу немедленно — изменять конфигурацию на стороне настроек организации не нужно. [tool-verified: provisa/core/org_secrets.py:64-79]
Доступ администратора платформы¶
Администратор платформы, управляющий плоскостью управления, не имеет доступа на чтение к значениям секретов ни одной организации. Гард org_settings явно отказывает cross_org и обходу платформы: администрирование жизненного цикла организации — не чтение учётных данных, которые эта организация хранит. Сервер обеспечивает это независимо от UI. (REQ-1361) [tool-verified: provisa/api/admin/secrets_router.py:53-83]
Смотрите также¶
- Модель безопасности — многоуровневый контроль доступа, аутентификация и журналирование аудита
- Справочник по конфигурации — синтаксис
${env:VAR}для учётных данных уровня процесса