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

Секреты

Имена входят. Значения никогда не выходят обратно.

Ни одна конечная точка API не возвращает значение сохранённого секрета, и ни один интерфейс не предлагает кнопку «показать». Тот, кто потерял значение, заменяет его тем же вызовом и той же формой, которыми оно создавалось. Это не политическое решение: пути чтения в коде просто нет.


Синтаксис ссылок

Три формы ссылок допустимы везде, где Provisa разрешает учётные данные:

Форма Откуда разрешается Кто может её использовать
${env:VAR_NAME} Окружение серверного процесса Только конфигурация развёртывания
${secret:NAME} Хранилище секретов организации — общее для всех участников Любое поле, принимающее ссылку на учётные данные
${user:NAME} Личное хранилище секретов действующего лица Любое поле, принимающее ссылку на учётные данные

В многоарендном развёртывании храните учётные данные платформы в сервисе секретов, а не в переменных окружения. Администратор арендатора может вписать ${env:...} в собственное поле учётных данных, поэтому любое значение из окружения процесса доступно каждому арендатору. Оставьте в окружении только собственные учётные данные бэкенда секретов.

Разрешение везде работает по принципу fail-closed (отказ в доступе по умолчанию). Неизвестное имя провайдера, незаданное имя и недоступный бэкенд — каждый случай вызывает ошибку. Ссылка, которую не удалось разрешить, никогда не подменяется молча пустой строкой.

Формат имени

Имена секретов должны соответствовать [A-Za-z_][A-Za-z0-9_]*: буквы, цифры и подчёркивания, начиная с буквы или подчёркивания. Причина практическая. ${secret:NAME} разбирается грамматикой ссылок, которая читает до закрывающей }, поэтому имя с фигурной скобкой, пробелом или двоеточием дало бы ссылку, которая разбирается как что-то другое.


Два хранилища, одна служба

У каждой организации два хранилища секретов. Оба живут внутри одной службы секретов.

Хранилище организации. Учётные данные, которые администратор организации кладёт сюда, общие, поэтому каждый участник, ссылающийся на ${secret:DATABASE_TOKEN}, получает одно и то же значение. Используйте его для учётных данных, которыми владеет организация, например общего пароля базы данных, ключа сервисного аккаунта или токена развёртывания. Для чтения и записи в хранилище организации нужна возможность org_settings.

Личное хранилище. Учётные данные, сохранённые здесь, принадлежат ровно одному человеку. Когда у двоих есть свой GIT_TOKEN, ${user:GIT_TOKEN} разрешается в токен того, кто действует, поэтому один и тот же текст ссылки даёт каждому его собственные данные. Тот, кто ничего не сохранил, получает ошибку, а не чужое значение. Личное хранилище не защищено никакой возможностью: хранить собственные учётные данные не является привилегией, которую выдаёт администратор, и синтаксиса запроса, который назвал бы чужое хранилище, нет.

Область действия является частью самой ссылки, и никакое разрешение её не окружает. ${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

Выбор устроен по принципу «закрыто при отказе»: неизвестный или недоступный бэкенд останавливает запуск с ошибкой, и Provisa никогда молча не переключается на другой.

Собственные учётные данные бэкенда

Учётные данные подключения центрального бэкенда относятся к конфигурации процесса и берутся только из ${env:...}. ${secret:...} не может их дать: сервис секретов, чьи собственные учётные данные хранятся в нём самом, никогда не удалось бы открыть. Цепочка доверия намеренно заканчивается в окружении хоста. Provisa разрешает каждое значение конфигурации бэкенда из окружения до того, как создаёт бэкенд.

Например, конфигурация 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 читает из него и никогда в него не пишет. Создание и удаление записей остаются за собственными инструментами сервиса, поэтому страница секретов так и сообщает и не предлагает кнопки создания.

При активном встроенном бэкенде provisa страница секретов полностью доступна для записи. Вы создаёте, заменяете и удаляете секреты в интерфейсе или через API.


Секреты организации в центральном сервисе

Развёртывание, которое обслуживает много организаций и читает центральный сервис, читает его для каждой организации внутри собственного префикса этой организации. Ссылка остаётся коротким именем (${secret:db#password}); префикс добавляется при чтении.

Сервис Где хранится секрет db организации acme
HashiCorp Vault (KV v2) orgs/acme/db
AWS Secrets Manager orgs/acme/db
GCP Secret Manager org-acme--db
Azure Key Vault o4-acme-db (o, длина id организации, id с _, записанным как -, затем имя)

Ссылка отклоняется по имени, когда:

  • её имя вышло бы за пределы префикса: начинается с /, содержит сегмент .. или обратную косую черту либо содержит / в GCP или Azure, где имена плоские;
  • её имя слишком длинное для сервиса после добавления префикса (GCP 255 символов, Azure 127); это отклоняется при сохранении ссылки с указанием предела;
  • к операции, которая её читает, не привязана никакая организация.

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

Переход на префикс. В развёртывании с многими организациями каждый секрет, на который ссылается организация, должен находиться под префиксом этой организации. Скопируйте каждый туда собственными средствами сервиса, затем сократите ссылку до имени внутри префикса (${secret:teams/data/db}, хранящийся в orgs/acme/db, становится ${secret:db}). Ссылка, чей секрет не перенесён, завершается ошибкой «не найдено»; она не читается из прежнего места.

Встроенное хранилище Provisa

Это вариант по умолчанию, когда центральный сервис не настроен. Каждая строка в secrets_store содержит зашифрованный конверт. Столбец value двоичный, а ключ расшифровки находится в окружении процесса, вне базы данных. Копия плоскости управления без мастер-ключа развёртывания содержит только шифротекст.

Шифрование никогда не бывает необязательным. Если ключ шифрования для всего процесса не настроен, хранилище использует локальную связку ключей. Если на хосте нет связки ключей для хранения ключа, хранилище отказывается писать, и значение никогда не сохраняется открытым текстом.

Форма хранения:

Колонка Тип Назначение
org_id Text Организация, владеющая этим секретом
owner_id Text "*" для хранилища организации; идентификатор пользователя для личного хранилища
name Text Имя ссылки
value LargeBinary Зашифрованный конверт-блоб
description Text Для чего этот секрет — никогда не выводится из значения
updated_by Text Кто задал его последним

Организация, создавшая собственный ключ шифрования, имеет секреты, обёрнутые этим ключом; иначе они обёрнуты ключом развёртывания. См. Шифрование в покое.

Колонка value не выбирается ни в одном запросе на перечисление.


Эндпоинты 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 нет.

Каждая запись фиксируется в журнале аудита. Запись называет того, кто действовал, и секрет. Ни значение, ни его длина не фиксируются.


Где разрешается ${secret:NAME}

Разрешение происходит внутри привязанной операции. Ничего не разрешается при импорте или при запуске. Хранилище один раз читает и расшифровывает секреты организации в начале операции и держит таблицу, пока операция идёт. Вне привязанной операции ${secret:NAME} вызывает ошибку.

Привязку устанавливают два места вызова:

Удалённые операции Git. URL удалённого репозитория организации может содержать ссылку ${secret:...} или ${user:...}, например токен для push, встроенный в URL. Тогда маршрутизатор окружений привязывает вокруг вызова git и хранилище организации, и личное хранилище действующего пользователя. С формой ${user:GIT_TOKEN} коммит попадает под учётные данные того, кто сделал push. Общая сервисная учётная запись не участвует.

Чтение API-ключей поставщиков ИИ. Когда Provisa читает ключ поставщика LLM организации, а ключ сохранён как ссылка ${secret:NAME}, он привязывает хранилище организации к этому запросу. Ссылка разрешается на выходе, а сам текст ссылки никогда не попадает к поставщику.


Ключи AI-вендоров организации как ссылки на секреты

Ключ AI-вендора организации (Anthropic, OpenAI и другие) можно хранить как ссылку ${secret:NAME} вместо литерального ключа.

Сначала положите ключ в хранилище организации:

PUT /admin/orgs/{org_id}/secrets/OPENAI_KEY
{ "value": "sk-...", "description": "OpenAI production key" }

Затем настройте конфигурацию AI организации так, чтобы она ссылалась на него:

vendor key field → ${secret:OPENAI_KEY}

Ссылка хранится в зашифрованном виде в org_secrets. Во время запроса Provisa разрешает ${secret:OPENAI_KEY} по хранилищу организации и передаёт сам ключ в SDK поставщика. Ротация записи в хранилище действует сразу, без изменения конфигурации в настройках организации.


Собственные учётные данные хранилищ развёртывания

Пароли хранилищ, которые запускает Provisa (PostgreSQL, Redis, MinIO, административная учётная запись Grafana и общий секрет кластера Trino), не являются секретами ни в одном из хранилищ секретов. Они генерируются для каждого развёртывания и хранятся самим развёртыванием: в ~/.provisa/store-credentials.env (режим 600), в состоянии Terraform или в Kubernetes Secret, имя которого задано в credentials.existingSecret Helm-чарта. См. Укрепление развёртывания.


Доступ администратора платформы

Администратор платформы, управляющий плоскостью управления, не может читать значения секретов ни одной организации. Проверка org_settings сразу отклоняет cross_org и обход платформы: управление жизненным циклом организации не даёт чтения учётных данных, которые она хранит. Сервер обеспечивает это сам, что бы ни показывал интерфейс.


Смотрите также