Зачем: - Показать слой 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.
11 KiB
Права 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:
- копирует исходный
ClientInfoцеликом, включаяinitial_user = bi; - затем отдельно вызывает
setUserдля определившего представление пользователяlifecycle.
В контексте остаются два имени с разными задачами: lifecycle отвечает за
локальную проверку прав, а bi остаётся первоначальным пользователем запроса.
Второй SQL SECURITY DEFINER на ods.event_v повторяет те же два шага и
поэтому ничего не меняет.
Что происходит на границе шарда
Distributed копирует ClientInfo из контекста и отправляет его на удалённую
ноду без замены initial_user
(RemoteQueryExecutor.cpp).
При межсерверном секрете имя входит в подписываемые данные запроса
(Connection.cpp).
Принимающая нода проверяет подпись и открывает сессию с
AlwaysAllowCredentials{client_info.initial_user}
(TCPHandler.cpp).
Секрет здесь выполняет штатную функцию. Это не случайный побочный эффект.
Документация Distributed прямо говорит: если задан <secret>, initial_user
становится текущим пользователем удалённого запроса
(описание кластера).
Так права, роли и ограничения исходного пользователя одинаково действуют на
всех шардах: координатор подтверждает личность пользователя, но не превращает
его запрос в запрос своей служебной учётки. Протокол не передаёт отдельно
эффективного пользователя DEFINER, поэтому на стенде эта защита и
останавливает bi у ods.event_rep.
Рассмотренные варианты
Настройка или другая форма представления
В 26.3 нет настройки, которая подменяет initial_user на определившего
представление пользователя только для чтения Distributed.
push_external_roles_in_interserver_queries пересылает роли именно
initial_user, не роли DEFINER
(исходник отправки).
distributed_ddl_use_initial_user_and_roles относится только к заданиям
ON CLUSTER. На SELECT настройка не действует
(объявление настройки).
Пользователь в существующем описании кластера
С межсерверным секретом ClickHouse выбирает специальную межсерверную
аутентификацию и не использует пароль из описания реплики
(Connection::sendHello).
Чтобы удалённый запрос всегда шёл как lifecycle, пришлось бы убрать секрет и
передавать его пароль. Тогда между нодами перестанет передаваться исходный
пользователь. Одна модель доступа сменится другой, менее прозрачной.
remote() с явными реквизитами
Для remote() ClickHouse умеет заменить initial_user пользователем из
адреса
(executeQuery.cpp).
Но представлению понадобятся адреса и реквизиты удалённого пользователя — в
тексте DDL либо в отдельной настройке. Это и есть отдельный маршрут доступа;
он сложнее нынешнего кластера и требует хранить ещё один секрет.
Промежуточное локальное представление на шарде
Можно направить Distributed на локальное представление с DEFINER, но
удалённому пользователю всё равно нужен SELECT на это представление. Значит
появятся дополнительный объект и дополнительный грант, а bi увидит
поверхность одного шарда. Физическая таблица останется закрытой, но простота и
контракт «только публичная поверхность DDS» исчезнут.
Ослабление проверки
SQL SECURITY NONEснимает проверку источника, но устарело и позволяет создателю такого представления исполнять произвольный запрос; ClickHouse требует для этого отдельное право (документация).- Право
biна физическую ODS-таблицу решает ошибку без новой механики, но ослабляет исходную границу. - Физическая или материализованная копия в DDS сохраняет границу, но добавляет хранение и жизненный цикл данных ради переименования колонок.
Решение для стенда
25 августа 2026 года выбрана простота: роль bi_reader читает ODS, DDS и DM
целиком. Потребительские запросы продолжают обращаться к _v. Этот договор
держат документация и ревью; границы прав задают гранты.
Отдельный межсерверный маршрут, служебная учётка и физическая поверхность DDS отклонены: каждый вариант добавляет механику только ради защиты, которой стенд не учит. Готовой настройки, которая сохранила бы и узкую границу, и простое устройство, в закреплённой версии нет.
Граница уверенности
Механизм подтверждён исходным кодом точной версии 26.3.17.56. Простую настройку
искали в настройках сервера и в путях формирования удалённого запроса этой же
версии. Уверенность в обоих выводах высокая. Официального
текста, который называет сочетание DEFINER + Distributed + secret
поддерживаемым ограничением или зарегистрированным дефектом, не найдено;
классификация как дефекта остаётся выводом из расхождения документации и кода.