# Права 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` прямо говорит: если задан ``, `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` поддерживаемым ограничением или зарегистрированным дефектом, не найдено; классификация как дефекта остаётся выводом из расхождения документации и кода.