Зачем: студенту нужен понятный путь от сохраненного состояния к SQL-сравнению без доступа технической учетной записи к изменению хранилища. Что: добавлен student с чтением источников и записью только в default; лабораторная сохраняет заказы и итоги в две локальные таблицы. Обновлены настройка доступа и программа, уточнено округление productPrice. Проверка: DDL и INSERT SELECT под student, пустое наблюдение, восемь переходов сохраненной истории и запрет вставки в пять прикладных баз на обеих нодах. config-test, lint стенда и генератора, 54 ссылки, независимое ревью.
19 KiB
ADR 0007. Доступ к ClickHouse: пользователи файлом, доверие нод секретом
Дата: 9 августа 2026 года. Статус: принято; роль monitoring_reader
и пользователь grafana добавлены 21 августа 2026 года (#114), права
bi_reader расширены до ODS, DDS и DM 25 августа 2026 года (#150).
Пользователь student и роль student_writer добавлены 22 сентября 2026 года.
Решение
У кластера восемь пользователей и семь ролей.
default— с паролем, только служебный: проверки здоровья нод и работа изнутри контейнеров. Приложения им не ходят.etlс рольюetl_writer— чтение и запись во всех слоях. Им ходят рабочие даги Airflow.lifecycleс рольюlifecycle_owner— создание и удаление только прикладных баз и объектов. Им даги жизненного цикла применяют DDL и пересоздают мир (ADR 0013).biс рольюbi_reader— чтение слоёв ODS, DDS и DM, а также справочниковdic. Им ходит Superset; его запросы обращаются к публичным_v, хотя права шире.analystс рольюanalyst_reader— чтение всех слоёв и справочников. Им человек подключается снаружи, из своего клиента.studentс ролямиanalyst_readerиstudent_writer— то же чтение и работа со своими таблицами вdefault: создание, вставка и удаление. Сохранение учебных наблюдений не требует прав технической учётной записиetlна изменение слоёв. Таблицы локальные; работа ведётся на одной ноде.grafanaс рольюmonitoring_reader— чтение слоёв, таблиц базыdicи системных таблиц,CLUSTERиREMOTE. Им ходит Grafana, и больше никто.dictс рольюdict_reader— чтение одной базыdic, пароля нет. Им словарь читает свою подложку, и больше он никем не используется (ADR 0012).
Роль здесь шире прав на слои: образ ClickHouse требует отдельного разрешения
на запросы ON CLUSTER, на чтение системных таблиц и на каждый движок
внешнего источника. Без права на системные таблицы клиент вовсе не открывает
соединение.
Пользователи, роли и права объявлены файлами настройки сервера, а не
запросами SQL. Файл монтируется на обе ноды и лежит в git. Пароли живут
в .env и попадают в настройку подстановкой по имени переменной; значения
собраны по конвенции «слово-число-слово», потому что пароль analyst
человек набирает руками.
Ноды доверяют друг другу по общему секрету, объявленному в описании кластера. Учётные данные по репликам не расписываются.
Сетевых ограничений на default нет: порты стенда и так открыты только на
127.0.0.1, а внутри сети Compose дверь заперта паролем.
Раздел README «Адреса и пароли» описывает, кто каким пользователем ходит и где
живут пароли; здесь остаются решение и доводы. Отдельного справочника по
доступу в docs/architecture/ нет: точные права видны в access.xml, конвенция
чтения через _v описана в доке хранилища.
Почему
Стенд учит не администрированию доступа, а чтению отказа в правах: человек
должен узнать в бою картину, которую видел здесь, и понимать, чего просить.
Отсюда критерий отбора — модель должна читаться из текста ошибки, а не из
описания. Поэтому пользователей мало, имена у них говорящие. В бою BI обычно
читает витрины и модельный слой. ODS ему могут открыть для расследования или
сверки. Стенд оставляет этот доступ постоянным, но сохраняет две крупные
границы: bi отделён от STG и от записи. Один пользователь может сверить всю
цепочку DM → DDS → ODS, не получая доступа к сырью и изменению данных.
Справочники видны всем читателям и никому на запись: зона входит в хранилище
сбоку, и её содержимым владеет файл репозитория, а не слой.
Ограничить bi одними _v поверх Distributed можно только отдельным
маршрутом, учётной записью или копией данных. Эта механика не даёт нового урока.
Публичное представление остаётся договором чтения; границу прав оно не задаёт
(исследование).
Беспарольный dict. Пароль пришлось бы вписать в текст DDL словаря, то
есть положить секрет в git. Ходить этой учёткой некуда: право у неё одно —
SELECT на dic, и хост источника локальный.
Своя роль дашбордам. Панели мониторинга живут не на витринах, а на
system.query_log, частях MergeTree и очереди распределённых DDL — и читают
их через clusterAllReplicas, потому что системные таблицы у каждой ноды свои
(ADR 0002). Для этого нужны SELECT на system.*
и REMOTE; контракт роли также включает CLUSTER для запросов ON CLUSTER.
Такого набора не было ни у одного читателя: у обеих читающих ролей из
системных таблиц только system.settings. Догрузить
недостающее в analyst_reader отвергнуто: аналитик получил бы системные
таблицы и REMOTE заодно, а в system.query_log запросы Grafana стали бы
неотличимы от человеческих — ровно там, где дашборд «Запросы» и должен их
различать. Пускать Grafana под etl отвергнуто сразу: читатель не получает
права писать. Осталась своя роль по здешней общей схеме — у каждого
потребителя она своя.
Секрет, а не учётные данные по репликам. У каждой реплики в описании
кластера своя учётка — вписанная явно или подразумеваемая, и тогда это
беспарольный default. Подключение к соседней ноде идёт от неё, а не от
того, кто задал вопрос. Значит, стоит default получить пароль — и без
правки описания кластера межнодовые запросы встают. Починить это можно двумя
способами, и они различаются не удобством. С учётными данными по репликам вся
межшардовая работа идёт от одной общей учётки: на своей ноде права
применяются, на соседней — нет. Ролевая модель тогда наполовину декорация, а
это хуже её отсутствия: она утверждает разграничение, которого не существует.
Общий секрет передаёт на соседа личность инициатора, стоит одной строки в
описании кластера и не размножает пароль по репликам. Шифрование тут ни при
чём; в соседнем clickhouse-learning-cluster выбран как раз путь с учётными
данными по репликам, и там пароль вписан в каждую реплику каждого из трёх
кластеров.
Довод подтверждён замером 13 августа 2026 года: на соседнем шарде сохранился пользователь-инициатор, поэтому там применились его права. Результат замера — в разделе «Что проверено».
Файлы, а не SQL. Объявленное файлом не оставляет состояния в томах:
источник истины — файл в git. Стенд считает access.xml и переданные через
.env значения настройкой при создании контейнера; отдельного способа
применять их на живом стенде нет, потому что уроки не меняют базовую модель
доступа. Порядок применения изменений — в разделе README «Адреса и пароли».
Оба варианта с SQL — локальное хранилище доступа с ON CLUSTER и
реплицированное в keeper — делают пользователей состоянием тома: .env на
них больше не влияет, и появляется расхождение того же сорта, которое README
уже описывает для Postgres и Grafana. Цена выбора известна: объявленных файлом
пользователей нельзя менять запросами. Она принята как упрощение — сломать
модель случайно труднее.
Что ещё отвергнуто. Оставить всё на беспарольном default
— цена нулевая, но и рассказать по итогам нечего. Раздать всем пароль без
ролей — дёшево и показывает ровно тот антипаттерн, за который ругают в бою:
все под одним админом. Вынести доступ в лекцию, как репликационную
эксплуатацию, — довод «отдельный операционный домен» здесь не работает:
у репликации это живые процессы, учения и runbook, а тут несколько десятков
строк настройки и ноль эксплуатации. Запереть default ещё и по сети —
второй замок на ту же дверь.
Отложено явно: TLS и защищённые межсерверные соединения, настоящее хранилище секретов, внешние поставщики учётных записей, квоты и профили под пользователя, политики строк, аудит запросов.
Следствия
Новые таблицы ODS, DDS и DM не требуют отдельного гранта для bi_reader.
Явный договор _v защищает потребителя от устройства хранения и случайных
новых колонок.
DDL для матвью приёма выполняется от имени lifecycle. Это видно в журнале
запросов, а при создании ClickHouse проверяет у этой учётки чтение источника и
вставку в цель. Так отдельная учётка показывает границу между редким
управлением структурой и повседневной записью etl, не выдавая рабочим дагам
право удалять базы.
Лаба «сломать права» вариантом с файлами не закрывается: создать через SQL
новую роль и нового пользователя ничто не мешает, и новую роль разрешено
выдавать даже пользователю, объявленному файлом. Недоступно обратное —
править объявленных файлом. Всё, что лаба создаст, живёт в томе и убирается
make clean.
Что проверено
Права student сверены 22 сентября 2026 года через MCP Context7 и на стенде
26.3.17.56: создание локальных MergeTree, INSERT SELECT из DDS и DM,
чтение и удаление своих таблиц работают. На обеих нодах вставка в STG, ODS,
DDS, DM и dic завершается ACCESS_DENIED.
Сверено 9 августа 2026 года по документации ClickHouse через MCP Context7 и на живом стенде 26.3.17.56.
- В
system.clustersу каждой реплики своя колонкаuser; на стенде тамdefault. Отсюда весь довод про общий секрет. - Секрет кластера аутентифицирует серверы хешем от соли, нонса, секрета и запроса и позволяет назначить пользователя, от которого запрос выполняется на соседней ноде.
- Роли и права объявляются в файле настройки пользователей наравне с самими пользователями; SQL-хранилище доступа для этого не требуется.
- Хранилище пользователей из файла — только для чтения: попытка изменить
такого пользователя запросом даёт
ACCESS_STORAGE_READONLY. - Роль, созданную через SQL, разрешено выдавать пользователю из файла — отдельное улучшение ClickHouse 2025 года, в 26.3 оно есть.
- Подстановка значений из переменных окружения — общий механизм файлов настройки ClickHouse.
- Весь DDL репозитория идёт через
IF NOT EXISTS(файлыsql/ddl/).
Права роли monitoring_reader сверены 21 августа 2026 года по документации
ClickHouse через MCP Context7 (#114).
- Системные таблицы выдаются обычным
SELECT: в документации этоGRANT SELECT ON system.query_log, значитSELECT ON system.*и есть «все системные таблицы». Отдельного права на них нет. GRANT CLUSTER ON *.*— документированная форма права на запросыON CLUSTER; текущий запросclusterAllReplicasего не использует.clusterAllReplicasдокументация держит в одном ряду сremote,remoteSecureиcluster: это табличная функция, которая берёт имя кластера изremote_serversи идёт на каждую его реплику. ОтсюдаREMOTE— без права на удалённое обращение запрос дальше своей ноды не пойдёт, а панели ADR 0002 читаютsystem.*именно так.
Набор проверен на живом стенде 21 августа 2026 года (#114): пользователь
grafana из Grafana выполнил
SELECT count() AS replicas FROM clusterAllReplicas('clickstream_cluster', system.one)
и получил 2 — прав хватает, чтобы прочесть системную таблицу с обеих нод.
Проверено на живом стенде 13 августа 2026 года, ClickHouse 26.3.17.56.
- Секрет кластера принимается внутри
remote_serversв виде<secret from_env="CLICKHOUSE_CLUSTER_SECRET"/>. Запрос отetlчерез обе ноды выполнился на каждой под пользователемetl. from_envработает у элементаpassword:etl,lifecycle,biиanalystвошли с паролями из окружения и получили свои роли.- При запросе от
biчерез Distributed на второй нодеsystem.query_logпоказалuser = biиinitial_user = bi. Значит, на соседнем шарде применяются права инициатора, а не общей учётной записи. - Права на отсутствующие базы
ddsиdmприняты. До создания баз они видны вSHOW GRANTS FOR etl_writer; временная таблица вdmподтвердила правоbiна чтение после появления базы.
Расширение bi_reader проверено на живом стенде 25 августа 2026 года (#150):
SHOW GRANTS FOR bi_readerсодержитSELECTна ODS, DDS и DM;biпрочитал одинаковое число строк изods.event_vиdds.event_v;- чтение
stg.hits_raw_repзавершилось ошибкойACCESS_DENIED; - представление над
DistributedсSQL SECURITY DEFINERна удалённой ноде сохранило исходного пользователяbi. Причина и отклонённые варианты разобраны в исследовании.