docs(adr): решена модель доступа к ClickHouse
- Зачем:
- до появления ETL и витрин надо выбрать границу боевого реализма в
доступе: один беспарольный default учит нулю, а полноценная защита
стоит эксплуатации, которой стенду не потянуть (#66).
- Что:
- принято четыре пользователя и три роли, объявленные файлами настройки,
без состояния в томах и без SQL-хранилища доступа.
- межнодовое доверие переведено на общий секрет кластера: учётные данные
по репликам подменяют права спросившего правами общей учётки.
- записаны отвергнутые варианты, отложенные меры защиты и четыре
проверки, которые закрывает тикет реализации.
- устройство модели отдано разделу README «Состав и доступ»: отдельный
справочник по доступу своего содержания сверх ADR сегодня не имеет.
- оговорено, что роль шире прав на слои — образ требует отдельных
разрешений на ON CLUSTER и на чтение системных таблиц (холодное
ревью #78).
- Проверка:
- реализации в этом коммите нет, менять нечему: make config-test.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,143 @@
|
||||
# ADR 0007. Доступ к ClickHouse: пользователи файлом, доверие нод секретом
|
||||
|
||||
Дата: 9 августа 2026 года. Статус: принято. Реализация — отдельным тикетом.
|
||||
|
||||
## Решение
|
||||
|
||||
У кластера четыре пользователя и три роли.
|
||||
|
||||
- `default` — с паролем, только служебный: проверки здоровья нод и работа
|
||||
изнутри контейнеров. Приложения им не ходят.
|
||||
- `etl` с ролью `etl_writer` — чтение и запись во всех слоях. Им ходит
|
||||
Airflow и применяется DDL при подъёме стенда.
|
||||
- `bi` с ролью `bi_reader` — чтение витрин DM и слоя DDS. Им ходит Superset.
|
||||
- `analyst` с ролью `analyst_reader` — чтение всех слоёв. Им человек
|
||||
подключается снаружи, из своего клиента.
|
||||
|
||||
Роль здесь шире прав на слои: образ ClickHouse требует отдельного разрешения
|
||||
на запросы `ON CLUSTER` и на чтение системных таблиц, а без второго клиент
|
||||
не открывает соединение вовсе. Состав прав каждой роли — работа тикета
|
||||
реализации.
|
||||
|
||||
Пользователи, роли и права объявлены файлами настройки сервера, а не
|
||||
запросами SQL. Файл монтируется на обе ноды и лежит в git. Пароли живут
|
||||
в `.env` и попадают в настройку подстановкой по имени переменной; значения
|
||||
собраны по конвенции «слово-число-слово», потому что пароль `analyst`
|
||||
человек набирает руками.
|
||||
|
||||
Ноды доверяют друг другу по общему секрету, объявленному в описании
|
||||
кластера. Учётные данные по репликам не расписываются.
|
||||
|
||||
Сетевых ограничений на `default` нет: порты стенда и так открыты только на
|
||||
`127.0.0.1`, а внутри сети Compose дверь заперта паролем.
|
||||
|
||||
Устройство модели — кто каким пользователем ходит и где живут пароли —
|
||||
опишет раздел README «Состав и доступ»; здесь только решение и доводы.
|
||||
Отдельного справочника по доступу в `docs/architecture/` не заводим: своего
|
||||
содержания сверх этих двух документов у него сегодня нет, а дом ему
|
||||
понадобится, когда появятся слои DDS и DM и гранты размножатся. Абзац README
|
||||
«У локального учебного кластера нет пароля» этим решением отменяется.
|
||||
|
||||
## Почему
|
||||
|
||||
Стенд учит не администрированию доступа, а чтению отказа в правах: человек
|
||||
должен узнать в бою картину, которую видел здесь, и понимать, чего просить.
|
||||
Отсюда критерий отбора — модель должна читаться из текста ошибки, а не из
|
||||
описания. Поэтому пользователей мало, имена у них говорящие, а границы
|
||||
проходят там, где их обычно проводят в компаниях: `bi` видит витрины и слой
|
||||
DDS под ними, потому что так чаще всего и бывает; заодно расхождение витрины
|
||||
со слоем ниже остаётся проверяемым одним и тем же пользователем.
|
||||
|
||||
**Секрет, а не учётные данные по репликам.** У каждой реплики в описании
|
||||
кластера своя учётка — вписанная явно или подразумеваемая, и тогда это
|
||||
беспарольный `default`. Подключение к соседней ноде идёт от неё, а не от
|
||||
того, кто задал вопрос. Значит, стоит `default` получить пароль — и без
|
||||
правки описания кластера межнодовые запросы встают. Починить это можно двумя
|
||||
способами, и они различаются не удобством. С учётными данными по репликам вся
|
||||
межшардовая работа идёт от одной общей учётки: на своей ноде права
|
||||
применяются, на соседней — нет. Ролевая модель тогда наполовину декорация, а
|
||||
это хуже её отсутствия: она утверждает разграничение, которого не существует.
|
||||
Общий секрет передаёт на соседа личность инициатора, стоит одной строки в
|
||||
описании кластера и не размножает пароль по репликам. Шифрование тут ни при
|
||||
чём; в соседнем `clickhouse-learning-cluster` выбран как раз путь с учётными
|
||||
данными по репликам, и там пароль вписан в каждую реплику каждого из трёх
|
||||
кластеров.
|
||||
|
||||
Довод опирается на измеренное лишь наполовину: колонку `user` видно на живом
|
||||
стенде, а вот что права `bi` на соседнем шарде подменяются правами общей
|
||||
учётки — вывод из неё, и он ждёт измерения (см. конец документа). Окажись
|
||||
вывод неверным, выбор секрета устоит по остальным основаниям, но перестанет
|
||||
быть единственно возможным.
|
||||
|
||||
**Файлы, а не SQL.** Объявленное файлом не оставляет состояния в томах:
|
||||
повторный `make up` на живом стенде ничего не сдвигает, правка пароля
|
||||
применяется перезапуском, источник истины — файл в git. Оба варианта с SQL —
|
||||
локальное хранилище доступа с `ON CLUSTER` и реплицированное в keeper —
|
||||
делают пользователей состоянием тома: `.env` на них больше не влияет, и
|
||||
появляется расхождение того же сорта, которое README уже описывает для
|
||||
Postgres и Grafana. Цена выбора известна: объявленных файлом пользователей
|
||||
нельзя менять запросами. Она принята как упрощение — сломать модель случайно
|
||||
труднее.
|
||||
|
||||
**Что ещё отвергнуто.** Оставить всё на беспарольном `default`
|
||||
— цена нулевая, но и рассказать по итогам нечего. Раздать всем пароль без
|
||||
ролей — дёшево и показывает ровно тот антипаттерн, за который ругают в бою:
|
||||
все под одним админом. Вынести доступ в лекцию, как репликационную
|
||||
эксплуатацию, — довод «отдельный операционный домен» здесь не работает:
|
||||
у репликации это живые процессы, учения и runbook, а тут несколько десятков
|
||||
строк настройки и ноль эксплуатации. Запереть `default` ещё и по сети —
|
||||
второй замок на ту же дверь.
|
||||
|
||||
**Отложено явно**: TLS и защищённые межсерверные соединения, настоящее
|
||||
хранилище секретов, внешние поставщики учётных записей, квоты и профили под
|
||||
пользователя, политики строк, аудит запросов.
|
||||
|
||||
## Следствия
|
||||
|
||||
До появления слоёв DDS и DM читать `bi` нечего, и это намеренно. Дашборды
|
||||
рисуются поверх модели данных, а не поверх типизированных событий, поэтому
|
||||
доступ Superset к ODS не планировался и правами не выдаётся. Пока витрин нет,
|
||||
подключение Superset проверяется связью, а не запросом к таблице.
|
||||
|
||||
Владелец матвью приёма определяется тем, кто применил DDL. Весь DDL стенда
|
||||
идёт через `IF NOT EXISTS`, поэтому на существующих томах три матвью
|
||||
сохранят нынешнего владельца, а на собранном заново стенде им станет `etl`.
|
||||
Работают оба — права есть у обоих, — но два стенда в этом расходятся.
|
||||
|
||||
Лаба «сломать права» вариантом с файлами не закрывается: создать через SQL
|
||||
новую роль и нового пользователя ничто не мешает, и новую роль разрешено
|
||||
выдавать даже пользователю, объявленному файлом. Недоступно обратное —
|
||||
править объявленных файлом. Всё, что лаба создаст, живёт в томе и убирается
|
||||
`make clean`.
|
||||
|
||||
## Что проверено
|
||||
|
||||
Сверено 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/`).
|
||||
|
||||
Осталось проверить при исполнении, и это работа тикета реализации: точная
|
||||
форма объявления секрета в описании кластера (готового примера в
|
||||
документации не нашлось); работает ли подстановка из окружения именно для
|
||||
пароля пользователя, и если нет — пароли лягут в файл настройки прямым
|
||||
текстом, а README назовёт это вслух; применяются ли на соседнем шарде права
|
||||
`bi`, а не общей учётки (вывод из колонки `user`, живьём не измерен —
|
||||
проверка требует создать пользователя); принимает ли ClickHouse права,
|
||||
выданные на ещё не созданную базу — на момент решения `dds` и `dm` не
|
||||
существуют.
|
||||
Reference in New Issue
Block a user