Files
clickstream-data-platform/docs/research/2026-08-25-clickhouse-distributed-view-security.md
ddadmin 9ea361d199 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.
2026-08-25 17:13:35 -04:00

11 KiB
Raw Permalink Blame History

Права 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:

  1. копирует исходный ClientInfo целиком, включая initial_user = bi;
  2. затем отдельно вызывает 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 поддерживаемым ограничением или зарегистрированным дефектом, не найдено; классификация как дефекта остаётся выводом из расхождения документации и кода.