Namespaces и доступ¶
Статья описывает, как memory-service изолирует базы знаний (namespaces), как аутентифицирует вызывающих (статические ключи и access token IAM), как выводит права на namespaces и как ограничивает видимость внутри namespace. Она для интеграторов и администраторов, которые выдают доступ к памяти.
Namespace — граница базы знаний¶
Namespace — опаковая строка, которая отделяет одну базу знаний от другой. Движок проверяет формат и фильтрует по точному совпадению на каждом шаге: поиск чанков, обход графа (предикат стоит на обоих концах пути), наблюдения, факты, аудит. Сегменты имени движок не интерпретирует — иерархия существует для потребителей: для префиксных грантов и для договорённостей об именах.
| Правило | Значение |
|---|---|
| Формат | ^[a-z0-9][a-z0-9._:-]{0,199}$ — строчные латинские буквы, цифры и ._:-, до 200 символов |
| Запись | Ровно один namespace на запрос |
| Чтение | Один или несколько namespaces (до 50 в одном запросе) |
| Создание | Не нужно: namespace появляется с первой записью |
Без scope |
Запрос работает в namespace по умолчанию (CB_DEFAULT_NAMESPACE) |
Некорректное имя или слишком длинный список → 400.
Всегда передавайте scope явно
Запрос без scope работает в namespace по умолчанию сервиса. Если токену этот
namespace не выдан, будет 403. В интеграциях указывайте базу знаний в каждом
запросе.
Как передать namespace¶
| Маршруты | Способ |
|---|---|
Чтение /api/brain/{query,recall,search}, /api/memory/context, /api/memory/context/typed |
Тело: "scope": {"namespace": "support"} или "scope": {"namespaces": ["support", "shared"]} |
Запись /api/brain/{retain,facts,audit}, /api/memory/observations[:batch] |
Тело: "scope": {"namespace": "support"} |
/api/brain/documents |
"namespace": "support" или "scope": {"namespace": "support"} (если заданы оба — они должны совпадать, иначе 400) |
/api/memory/reconcile |
Поле namespace в теле или ?namespace= (оба — только одинаковые) |
GET/DELETE по ключу, трейсы, наблюдения |
Query-параметр ?namespace=support |
Схема имён в платформе¶
Платформа строит имена namespaces от идентификаторов, а не от отображаемых названий:
| Namespace | Кто пишет | Что лежит |
|---|---|---|
tenant:<tenant-id> |
context-adapter Control Plane |
Доменные события ядра (наблюдения): задачи, прогоны, approvals |
tenant:<tenant-id>:ws:<root-workspace-id> |
Control Plane (/api/v1/knowledge/*) |
Знания дерева воркспейсов: снимки источников, доменные пакеты |
tenant:<tenant-id>:principal:<principal-id> |
— | Приватный namespace principal (используется в модели видимости) |
При сборке контекста Control Plane читает namespace tenant'а и namespace корневого
воркспейса задачи. Префикс tenant: — настройка ядра CP_CONTEXT_NAMESPACE_PREFIX.
Для самостоятельных приложений (бот поддержки, демо) имя выбирает администратор,
например support или sales.
Логическая изоляция
Namespaces одного инстанса делят одну БД. Если контуру нужна физическая изоляция данных, поднимайте отдельный инстанс memory-service с отдельной БД.
Способы аутентификации¶
Заголовок один — Authorization: Bearer <token>. Токен проверяется по порядку:
flowchart TD
A[Запрос] --> B{Настроен хоть один ключ<br/>или CB_IAM_ENABLED?}
B -- нет --> L[Локальный режим:<br/>без авторизации, полный доступ]
B -- да --> C{Совпал с CB_SERVER_API_KEYS_PII?}
C -- да --> P1[Полный доступ ко всем namespaces,<br/>полный допуск к ПДн]
C -- нет --> D{Совпал с CB_SERVER_API_KEY?}
D -- да --> P2[Полный доступ ко всем namespaces;<br/>ПДн маскируются, если CB_PII_PROTECTION]
D -- нет --> E{Совпал с ключом реестра CB_API_KEYS?}
E -- да --> P3[Гранты ключа по префиксам]
E -- нет --> F{CB_IAM_ENABLED и токен похож на JWT?}
F -- да --> G{Подпись, iss, aud, срок}
G -- ок --> P4[Гранты из tenant_id и scopes токена]
G -- дефект --> X401[401]
G -- JWKS недоступен --> X503[503]
F -- нет --> X401
Все сравнения ключей выполняются в постоянном времени. Любой дефект IAM-токена
(истёк, чужой aud/iss, неизвестный ключ подписи) даёт тот же 401, что и
неверный ключ, — сервис не подсказывает, какой способ «почти» подошёл.
Локальный режим
Если не задан ни CB_SERVER_API_KEY, ни CB_SERVER_API_KEYS_PII, ни CB_API_KEYS,
и CB_IAM_ENABLED=false, сервис работает без авторизации. Такой режим
допустим только для разработки на изолированной машине.
Статические ключи без грантов¶
CB_SERVER_API_KEY и ключи из CB_SERVER_API_KEYS_PII (через запятую) дают доступ ко
всем namespaces на чтение и запись. Разница — в допуске к ПДн:
| Ключ | ПДн при CB_PII_PROTECTION=true |
|---|---|
CB_SERVER_API_KEYS_PII |
Полный допуск, каждая выдача ПДн журналируется pii_access |
CB_SERVER_API_KEY |
Маскированная выдача ([ПДн:phone] и т. п.) |
В корневом compose.yml платформы CB_SERVER_API_KEY равен MEMORY_API_KEY,
CB_SERVER_API_KEYS_PII пуст, а CB_PII_PROTECTION=true — то есть статический ключ
платформы получает маскированную выдачу.
Реестр ключей с грантами (CB_API_KEYS)¶
CB_API_KEYS — JSON-массив ключей, у каждого — список грантов на префиксы namespaces.
Так один инстанс обслуживает несколько потребителей с разными правами.
[
{
"name": "support-bot",
"key": "<случайный-ключ-1>",
"grants": [
{"prefix": "support", "read": true, "write": true},
{"prefix": "shared", "read": true, "write": false}
]
},
{
"name": "kb-admin",
"key": "<случайный-ключ-2>",
"pii": true,
"grants": [{"prefix": "", "read": true, "write": true}]
},
{
"name": "orchestrator",
"key": "<случайный-ключ-3>",
"service": true,
"grants": [{"prefix": "tenant:", "read": true, "write": true}]
}
]
| Поле | Обязательно | Смысл |
|---|---|---|
key |
да | Значение Bearer-токена |
name |
нет | Метка вызывающего в аудите и в CB_CORE_IDENTITIES (по умолчанию key-<i>) |
grants |
да, непустой | {prefix, read, write}; read по умолчанию true, write — false |
pii |
нет | Полный допуск к ПДн (иначе при включённой защите — маска) |
service |
нет | Service scope: регистрация доменных пакетов и доступ к маршрутам ядра |
Правила грантов:
- грант покрывает namespace, если имя начинается с
prefix; пустой префикс покрывает все namespaces; - запрос авторизуется, только если все его namespaces покрыты грантом с нужным
флагом; иначе
403с именем первого непокрытого namespace вdetail— данные не читаются и не пишутся даже частично; GET /api/brain/stats(статистика всего инстанса) доступна только ключам с грантом на пустой префикс.
Префикс — это просто начало строки
Грант support покроет и support-archive, и supporters. Если нужно
ограничение ровно поддеревом, заканчивайте префикс разделителем: support:.
Некорректный JSON в CB_API_KEYS не валит старт, но каждый запрос с авторизацией
будет получать 500 с описанием ошибки конфигурации.
Access token IAM¶
При CB_IAM_ENABLED=true Bearer, не совпавший ни с одним статическим ключом и похожий
на JWT (три непустые части через точку), проверяется общим platform-auth-sdk:
подпись по JWKS (CB_IAM_JWKS_URL), точный iss (CB_IAM_ISSUER), точный aud
(CB_IAM_AUDIENCE, по умолчанию memory-service; списки audience отвергаются) и
временные claims с допуском CB_IAM_LEEWAY_SECONDS.
Потребитель получает такой токен обменом своего Platform Access Token на audience
memory-service (см. Токены IAM). Права выводятся из токена:
| Что в токене | Что даёт |
|---|---|
tenant_id |
Namespace tenant:<tenant_id> (ровно он) и поддерево tenant:<tenant_id>:* |
claim memory_namespaces (список) |
Каждый namespace из списка — ровно он и поддерево <ns>:* |
scope memory:read |
Флаг чтения всех грантов токена |
scope memory:write |
Флаг записи всех грантов токена |
scope memory:pii |
Полный допуск к ПДн |
scope memory:tenants |
Всё поддерево tenant:*, независимо от tenant_id токена |
scope memory:service |
Service scope: identity ядра (доменные пакеты, маршруты ядра); namespaces не расширяет |
scope memory:on-behalf |
Чтение от имени другого principal с переданной видимостью (см. ниже) |
Scope действует, только если он есть и в токене, и в потолке scope клиента (проверяет
SDK). Валидный токен без memory:read/memory:write не покрывает ни одного
namespace — любой запрос к данным получит 403. Гранты IAM строгие: tenant:<id>
покрывается точным совпадением, поэтому tenant:<id>0 чужому токену недоступен.
| Ситуация | Ответ |
|---|---|
| Токен с дефектом | 401 |
| JWKS недоступен или не настроен | 503 (fail closed; статические ключи продолжают работать) |
| Токен без прав на namespace запроса | 403 |
GET /api/brain/stats с IAM-токеном |
403 |
JWKS — по внутреннему адресу
Указывайте CB_IAM_JWKS_URL на внутренний адрес IAM в сети контура
(http://iam-service:8010/.well-known/jwks.json в compose.yml), а не на внешний
прокси: проверка подписи не должна зависеть от внешнего TLS. Ключи кэшируются с
учётом ротации.
Scopes памяти в IAM¶
Audience memory-service и его потолок scope заводит deploy/bootstrap.py:
| Scope | Кому выдавать |
|---|---|
memory:read, memory:write |
Приложениям и агентам, которые работают со своей памятью |
memory:pii |
Только тем, кому нужен немаскированный текст с ПДн |
memory:tenants |
Только service account Control Plane |
memory:service |
Только service account Control Plane |
memory:on-behalf |
Только service account Control Plane (режим видимости по principal) |
Service account ядра¶
Control Plane ходит в память service account'ом IAM «Taimen Control Plane»
(создаётся deploy/bootstrap.py, секрет — secrets/control-plane-iam.env).
Его потолок включает memory:read, memory:write, memory:tenants,
memory:on-behalf и memory:service:
memory:tenantsнужен, потому чтоcontext-adapterпишет и читает память всех tenant'ов инсталляции, а tenant IAM у service account один;memory:serviceдаёт право регистрировать доменные пакеты и сверять снимки знаний, которые клиенты публикуют черезPOST /api/v1/knowledge/*ядра;memory:on-behalfиспользуется, когда ядро работает в режиме авторизацииpolicyи читает память от имени конечного principal.
Пока файла secrets/control-plane-iam.env нет (до bootstrap), ядро в режиме
CP_CONTEXT_AUTH=auto использует статический MEMORY_API_KEY; после bootstrap и
перезапуска процессов ядра — IAM. См. Конфигурацию Control Plane.
Маршруты ядра¶
Управление схемой знаний можно закрепить за доверенным оркестратором. Если задан
CB_CORE_ONLY=true или непустой CB_CORE_IDENTITIES, маршруты
POST/GET /api/memory/packages,GET /api/memory/packages/{name},GET/PUT /api/memory/namespaces/{ns}/kinds,POST /api/memory/reconcile
отвечают 403 всем, кроме вызывающих со service scope (IAM memory:service, ключ
реестра с "service": true) и меток из CB_CORE_IDENTITIES, — даже если у токена
есть права на namespace. Метки: name ключа реестра, base (для
CB_SERVER_API_KEY), pii-full (для ключей CB_SERVER_API_KEYS_PII),
iam:<principal_type>:<principal_id> для IAM-токена. Локальный режим без
аутентификации ядро не опознаёт (fail closed), если метка default не перечислена
явно. Доверенному вызывающему права на namespace всё равно нужны.
Без ограничения регистрация пакета (POST /api/memory/packages) всё равно требует
service scope: ключ с "service": true, IAM memory:service, ключ с грантом записи
на пустой префикс или legacy-ключ без грантов.
Видимость внутри namespace¶
Namespace — граница хранения. Внутри него элемент может нести scopes видимости:
workspace:<id>— элемент принадлежит воркспейсу;principal:<id>— приватный элемент principal.
Элемент с такими scopes виден, только если они пересекаются с разрешёнными scopes
вызывающего. Элементы без scopes (или только со scopes релевантности вроде task:…)
видны всем, кто читает namespace.
Разрешённые namespaces и scopes определяет сервер:
| Вызывающий | Видимость |
|---|---|
| Статический ключ, локальный режим | Без ограничений |
IAM-токен при CB_POLICY_ENABLED=false |
Без ограничений (только гранты) |
IAM-токен человека или агента при CB_POLICY_ENABLED=true |
Из policy-service: list_objects(memory.read, memory_namespace) → namespaces, list_objects(memory.read, workspace) → workspace:<id>, плюс собственный principal:<id> и приватный namespace principal |
Service account с memory:on-behalf при CB_POLICY_ENABLED=true |
Из тела запроса: allowedNamespaces и allowedScopes обязательны, иначе 403 |
Ответ policy-service кэшируется на CB_POLICY_CACHE_TTL_SECONDS (по умолчанию 5 с)
по паре tenant/principal. Недоступность policy-service → 503, не «разрешить».
Запрос к namespace вне видимости → 403. Для вызова policy-service память использует
свою service identity (CB_IAM_CLIENT_ID/CB_IAM_CLIENT_SECRET, файл
secrets/memory-service-iam.env создаёт bootstrap).
Экспериментальный режим
Видимость по principal опирается на policy-service, который относится к
экспериментальным модулям. По умолчанию MEMORY_POLICY_ENABLED=false. См.
policy-service.
Сужение видимости в запросе¶
Любой вызывающий может сузить свою видимость на один запрос полями тела
allowedNamespaces и/или allowedScopes (принимаются /api/memory/context,
/api/memory/context/typed, /api/brain/query, /api/brain/recall,
/api/brain/search):
- берётся пересечение с серверной видимостью — сужение не расширяет права;
- поле отсутствует или
null— видимость без изменений; - пустой список — ничего:
"allowedScopes": []скрывает все элементы сworkspace:/principal:,"allowedNamespaces": []запрещает чтение любого namespace (403); - namespace запроса вне суженного
allowedNamespaces→403; - не список строк или некорректное имя →
400;allowedScopes— до 500 элементов.
Пример: читать память воркспейса backend и его предка org, но не соседнего
finance в той же базе знаний:
{
"strategy": "briefing",
"scope": {"namespace": "tenant:<tenant-id>:ws:<org-id>"},
"allowedScopes": ["workspace:<backend-id>", "workspace:<org-id>"]
}
Где фильтр видимости не применяется
Структурный режим /api/brain/search (с filters.type) scope-фильтра видимости
не применяет, GET /api/brain/nodes отсекает узлы по scopes уже после выборки
(ответ может содержать меньше limit записей); GET /api/brain/nodes/{key} и
GET /api/brain/sources/{key} проверяют только гранты на namespace. Не
полагайтесь на scopes видимости как на единственную защиту для точечного чтения
по ключу.
Сводка кодов доступа¶
| Код | Когда |
|---|---|
400 |
Некорректное имя namespace/scope, больше 50 namespaces, противоречивые namespace и scope.namespace |
401 |
Нет заголовка, неверный ключ, дефектный IAM-токен |
403 |
Namespace не покрыт грантами; namespace вне видимости principal; маршрут ядра без service scope; stats без глобального гранта; memory:on-behalf без allowed* |
500 |
Некорректный CB_API_KEYS |
503 |
JWKS или policy-service недоступны |