feat(dds): добавлен публичный контракт события
Зачем: - Показать слой DDS как договор данных, которому не нужна физическая копия. - Сохранить простой учебный стенд без отдельного маршрута ради узких прав. Что: - Добавлено кластерное представление dds.event_v с явными 44 колонками. - Роли bi_reader разрешено чтение ODS, DDS и DM при договоре чтения через _v. - Согласованы мастер-спека, ADR, справочники архитектуры и живые тикеты этапа. - Зафиксировано исследование поведения DEFINER поверх Distributed. Проверка: - make config-test - make smoke - make check-clickhouse - Приёмочные запросы: равенство ODS/DDS, состав колонок, коды устройств и права bi.
This commit is contained in:
1 parent
4ea9591a16
commit
9ea361d199
11 files changed
+288
-76
No files matched your search
@@ -0,0 +1,132 @@
|
||||
# Права DEFINER у представления над Distributed
|
||||
|
||||
Дата исследования: 2026-08-25. Тикет #150. ClickHouse 26.3.17.56.
|
||||
|
||||
## Вопрос
|
||||
|
||||
Почему `dds.event_v` с `SQL SECURITY DEFINER`, созданное пользователем
|
||||
`lifecycle`, всё равно читает удалённый `ods.event_rep` как `bi`? Можно ли
|
||||
оставить `bi` только с правом на публичное представление DDS, не заводя отдельный
|
||||
маршрут или пользователя и не копируя данные?
|
||||
|
||||
## Короткий вывод
|
||||
|
||||
Простого поддерживаемого способа в ClickHouse 26.3 нет. `DEFINER` меняет
|
||||
пользователя, чьи права проверяет локальный запрос представления, но сохраняет
|
||||
в запросе имя его первоначального инициатора — `initial_user`. Движок
|
||||
`Distributed` передаёт это имя на шарды, а межсерверный секрет делает его
|
||||
пользователем удалённой сессии. Поэтому на координаторе источники читает
|
||||
`lifecycle`, а на соседнем шарде физическую таблицу читает `bi`.
|
||||
|
||||
Это не заявленная семантика `SQL SECURITY DEFINER`: документация обещает, что
|
||||
право на источник требуется определившему представление пользователю, а
|
||||
вызывающему достаточно `SELECT` на представление. Поведение следует из
|
||||
сочетания двух реализаций и выглядит как недокументированное ограничение или
|
||||
дефект распределённого исполнения.
|
||||
|
||||
## Где меняется пользователь
|
||||
|
||||
`dds.event_v` разворачивается в запрос к `ods.event_v`, затем к
|
||||
`ods.event_dist FINAL`. Для внутреннего запроса представления ClickHouse
|
||||
создаёт новый контекст в
|
||||
[`getSQLSecurityOverriddenContext`](https://github.com/ClickHouse/ClickHouse/blob/v26.3.17.56-lts/src/Storages/StorageInMemoryMetadata.cpp#L135-L175):
|
||||
|
||||
1. копирует исходный `ClientInfo` целиком, включая `initial_user = bi`;
|
||||
2. затем отдельно вызывает `setUser` для определившего представление
|
||||
пользователя `lifecycle`.
|
||||
|
||||
В контексте остаются два имени с разными задачами: `lifecycle` отвечает за
|
||||
локальную проверку прав, а `bi` остаётся первоначальным пользователем запроса.
|
||||
Второй `SQL SECURITY DEFINER` на `ods.event_v` повторяет те же два шага и
|
||||
поэтому ничего не меняет.
|
||||
|
||||
## Что происходит на границе шарда
|
||||
|
||||
`Distributed` копирует `ClientInfo` из контекста и отправляет его на удалённую
|
||||
ноду без замены `initial_user`
|
||||
([`RemoteQueryExecutor.cpp`](https://github.com/ClickHouse/ClickHouse/blob/v26.3.17.56-lts/src/QueryPipeline/RemoteQueryExecutor.cpp#L415-L444)).
|
||||
При межсерверном секрете имя входит в подписываемые данные запроса
|
||||
([`Connection.cpp`](https://github.com/ClickHouse/ClickHouse/blob/v26.3.17.56-lts/src/Client/Connection.cpp#L968-L994)).
|
||||
Принимающая нода проверяет подпись и открывает сессию с
|
||||
`AlwaysAllowCredentials{client_info.initial_user}`
|
||||
([`TCPHandler.cpp`](https://github.com/ClickHouse/ClickHouse/blob/v26.3.17.56-lts/src/Server/TCPHandler.cpp#L2303-L2356)).
|
||||
|
||||
Секрет здесь выполняет штатную функцию. Это не случайный побочный эффект.
|
||||
Документация `Distributed` прямо говорит: если задан `<secret>`, `initial_user`
|
||||
становится текущим пользователем удалённого запроса
|
||||
([описание кластера](https://github.com/ClickHouse/ClickHouse/blob/v26.3.17.56-lts/docs/en/engines/table-engines/special/distributed.md#L105-L118)).
|
||||
Так права, роли и ограничения исходного пользователя одинаково действуют на
|
||||
всех шардах: координатор подтверждает личность пользователя, но не превращает
|
||||
его запрос в запрос своей служебной учётки. Протокол не передаёт отдельно
|
||||
эффективного пользователя `DEFINER`, поэтому на стенде эта защита и
|
||||
останавливает `bi` у `ods.event_rep`.
|
||||
|
||||
## Рассмотренные варианты
|
||||
|
||||
### Настройка или другая форма представления
|
||||
|
||||
В 26.3 нет настройки, которая подменяет `initial_user` на определившего
|
||||
представление пользователя только для чтения `Distributed`.
|
||||
`push_external_roles_in_interserver_queries` пересылает роли именно
|
||||
`initial_user`, не роли `DEFINER`
|
||||
([исходник отправки](https://github.com/ClickHouse/ClickHouse/blob/v26.3.17.56-lts/src/QueryPipeline/RemoteQueryExecutor.cpp#L425-L444)).
|
||||
`distributed_ddl_use_initial_user_and_roles` относится только к заданиям
|
||||
`ON CLUSTER`. На `SELECT` настройка не действует
|
||||
([объявление настройки](https://github.com/ClickHouse/ClickHouse/blob/v26.3.17.56-lts/src/Core/ServerSettings.cpp#L1002-L1006)).
|
||||
|
||||
### Пользователь в существующем описании кластера
|
||||
|
||||
С межсерверным секретом ClickHouse выбирает специальную межсерверную
|
||||
аутентификацию и не использует пароль из описания реплики
|
||||
([`Connection::sendHello`](https://github.com/ClickHouse/ClickHouse/blob/v26.3.17.56-lts/src/Client/Connection.cpp#L452-L476)).
|
||||
Чтобы удалённый запрос всегда шёл как `lifecycle`, пришлось бы убрать секрет и
|
||||
передавать его пароль. Тогда между нодами перестанет передаваться исходный
|
||||
пользователь. Одна модель доступа сменится другой, менее прозрачной.
|
||||
|
||||
### `remote()` с явными реквизитами
|
||||
|
||||
Для `remote()` ClickHouse умеет заменить `initial_user` пользователем из
|
||||
адреса
|
||||
([`executeQuery.cpp`](https://github.com/ClickHouse/ClickHouse/blob/v26.3.17.56-lts/src/Interpreters/ClusterProxy/executeQuery.cpp#L96-L120)).
|
||||
Но представлению понадобятся адреса и реквизиты удалённого пользователя — в
|
||||
тексте DDL либо в отдельной настройке. Это и есть отдельный маршрут доступа;
|
||||
он сложнее нынешнего кластера и требует хранить ещё один секрет.
|
||||
|
||||
### Промежуточное локальное представление на шарде
|
||||
|
||||
Можно направить `Distributed` на локальное представление с `DEFINER`, но
|
||||
удалённому пользователю всё равно нужен `SELECT` на это представление. Значит
|
||||
появятся дополнительный объект и дополнительный грант, а `bi` увидит
|
||||
поверхность одного шарда. Физическая таблица останется закрытой, но простота и
|
||||
контракт «только публичная поверхность DDS» исчезнут.
|
||||
|
||||
### Ослабление проверки
|
||||
|
||||
- `SQL SECURITY NONE` снимает проверку источника, но устарело и позволяет
|
||||
создателю такого представления исполнять произвольный запрос; ClickHouse
|
||||
требует для этого отдельное право
|
||||
([документация](https://github.com/ClickHouse/ClickHouse/blob/v26.3.17.56-lts/docs/en/sql-reference/statements/create/view.md#L115-L136)).
|
||||
- Право `bi` на физическую ODS-таблицу решает ошибку без новой механики, но
|
||||
ослабляет исходную границу.
|
||||
- Физическая или материализованная копия в DDS сохраняет границу, но добавляет
|
||||
хранение и жизненный цикл данных ради переименования колонок.
|
||||
|
||||
## Решение для стенда
|
||||
|
||||
25 августа 2026 года выбрана простота: роль `bi_reader` читает ODS, DDS и DM
|
||||
целиком. Потребительские запросы продолжают обращаться к `_v`. Этот договор
|
||||
держат документация и ревью; границы прав задают гранты.
|
||||
|
||||
Отдельный межсерверный маршрут, служебная учётка и физическая поверхность DDS
|
||||
отклонены: каждый вариант добавляет механику только ради защиты, которой стенд
|
||||
не учит. Готовой настройки, которая сохранила бы и узкую границу, и простое
|
||||
устройство, в закреплённой версии нет.
|
||||
|
||||
## Граница уверенности
|
||||
|
||||
Механизм подтверждён исходным кодом точной версии 26.3.17.56. Простую настройку
|
||||
искали в настройках сервера и в путях формирования удалённого запроса этой же
|
||||
версии. Уверенность в обоих выводах высокая. Официального
|
||||
текста, который называет сочетание `DEFINER + Distributed + secret`
|
||||
поддерживаемым ограничением или зарегистрированным дефектом, не найдено;
|
||||
классификация как дефекта остаётся выводом из расхождения документации и кода.
|
||||
Reference in new issue
Block a user