Внедрение Column Level Security (CLS) в Apache Superset: руководство по защите конфиденциальных данных на уровне столбцов
Эта статья посвящена запросу на тонкое разграничение доступа в отчетных системах.
Очень часто стандартных инструментов вроде Row Level Security (RLS) недостаточно. Клиентам требуется скрыть не строки, а целые столбцы с конфиденциальной информацией — например, зарплаты, персональные данные, финансовые показатели — от глаз неуполномоченных сотрудников. В этом материале мы детально разберем, как реализовать механизм Column Level Security (CLS) в Apache Superset, используя связку Jinja и Handlebars, а также поделимся практическими примерами и предостережем от типичных ошибок, основанных на нашем опыте внедрений.
Почему стандартных средств Superset недостаточно?
Apache Superset — это мощный инструмент для визуализации данных, и его встроенный механизм Row Level Security (RLS) отлично справляется с ограничением доступа к строкам. Например, можно настроить политику так, чтобы менеджер из московского офиса видел данные только по своему региону.
Однако, представьте ситуацию: у вас есть датасет "Сотрудники" с колонками ID, Имя, Должность, Зарплата и Номер паспорта. Вам нужно, чтобы руководители отделов видели всю информацию, кроме зарплат и паспортных данных, а сотрудники кадровой службы — наоборот, видели зарплаты, но не видели паспортные данные. Стандартный RLS здесь бессилен. Он не умеет скрывать колонки.
Именно эту проблему — управление видимостью столбцов на основе роли пользователя — и решает Column Level Security. И сегодня мы покажем, как реализовать этот механизм, используя гибкость шаблонизаторов Jinja и Handlebars.
Column Level Security – что это такое?
Column Level Security (CLS) — это механизм управления доступом в базах данных и аналитических системах, который позволяет ограничивать видимость и доступ к отдельным столбцам (полям) таблицы на основе прав пользователя или его роли.
Если говорить простыми словами, CLS решает вопрос: "Кто может видеть какие поля в таблице?"
Представьте таблицу с данными сотрудников:
- Видят все: Имя, Должность, Отдел
- Видит только HR: Зарплата, Номер телефона
- Видит только бухгалтерия: Банковский счет
CLS — это и есть правила, которые определяют, кто какие колонки может просматривать или изменять.
CLS (защита на уровне столбцов) определяет, какие колонки видит пользователь, а RLS (защита на уровне строк) - какие строки видит пользователь.
Пример в PostgreSQL:
-- Создаем политику доступа к колонке salary
CREATE TABLE employees (
id SERIAL PRIMARY KEY,
name TEXT,
salary DECIMAL,
phone TEXT
);
-- Даем доступ к обычным колонкам всем
GRANT SELECT (id, name, phone) ON employees TO all_users;
-- А к колонке salary - только руководителям
GRANT SELECT (salary) ON employees TO managers_role;
Главные преимущества CLS:
Во –первых, это тонкая настройка доступа — можно гибко управлять видимостью каждого поля.
Во-вторых, это соответствие требованиям регуляторов (GDPR, 152-ФЗ, HIPAA).
В – третьих, это снижение рисков утечки — сотрудники не видят данные, которые им не нужны.
И, наконец, это простота аудита — легко отслеживать, кто имеет доступ к конфиденциальным полям
Однако, у CLS есть и ряд ограничений и рисков:
Во – первых, это сложность управления — много правил для разных ролей и колонок.
Во-вторых, это риски, связанные с производительностью — дополнительные проверки прав могут замедлять запросы.
В – третьих, это обход защиты — через экспорт данных или другие уязвимости системы.
И, наконец, в-четвертых, это ложное чувство безопасности — CLS не защищает от утечки другими путями
Таким образом, CLS — это необходимость в современных системах с конфиденциальными данными. Его лучшая реализация — на уровне СУБД, а не приложения. По своей сути CLS дополняет RLS — используйте оба механизма для комплексной защиты и тщательно тестируйте его — ошибки в настройке CLS могут заблокировать доступ к нужным данным
Column Level Security — это не просто "скрыть колонку", а стратегический инструмент для построения безопасных и соответствующих законодательству систем работы с данными.
Итак, представим задачу в качестве примера - у нас есть таблица salary:
Первое, с чего начинается любое серьезное внедрение — это проверка и настройка окружения. Для работы с Jinja-шаблонами в SQL-запросах необходимо активировать соответствующий функционал.
В файле конфигурации Superset, который в стандартной установке находится по пути superset/docker/pythonpath_dev/superset_config.py, найдите или добавьте словарь FEATURE_FLAGS.
Убедитесь, что в нем присутствует и активирована следующая опция:
FEATURE_FLAGS = {
"ENABLE_TEMPLATE_PROCESSING": True,
# ... другие флаги
}
Важно! Если этот флаг не активирован, Jinja-макросы в запросах не будут обрабатываться, и весь последующий функционал окажется нерабочим. После внесения изменений в конфигурационный файл обязательно перезапустите сервисы Superset. Пропуск этого шага — самая частая ошибка на начальном этапе.
Для реализации CLS нам необходимо знать, кто именно в данный момент просматривает чарт. В этом нам поможет встроенный макрос Jinja — current_username()
Примечание: Макрос current_username() возвращает логин аутентифицированного пользователя. Однако в SQL-контексте это значение будет интерпретировано как идентификатор, а не как строка. Заключив его в одинарные кавычки, мы явно указываем базе данных, что это строковый литерал.
Создадим простой TABLE-чарт, отобразив колонку username:
В зависимости от значения в колонке username скроем/отобразим колонку employee_salary, воспользуясь типом визуализации Handlebars:
Теперь начнется самая важная часть — программирование логики отображения. Перейдите во вкладку "Customize". Перед вами появятся два ключевых поля: "Handlebars Template" и "CSS Styles".
На чарте нажимаем кнопку UPDATE CHART и переключаемся на вкладку CUSTOMIZE:
Пример из нашей практики:
Один из наших клиентов, крупный ритейлер, использовал эту технику не только для CLS, но и для динамического RLS. Они добавляли колонку current_user, а затем в основе датасета с помощью FILTER (в зависимости от этой колонки) подтягивали только те магазины, за которые ответственен данный менеджер. Это мощный паттерн, выходящий за рамки простого скрытия колонок.
Разберем готовый шаблон с комментариями:
<table class="data-table"><!-- thead -названияколонок--><thead><tr><th>IDсотрудника</th><th>Имя сотрудника</th><!-- если пользователь не admin, то ячейки с названием колонки не будет --><!-- data - объект, содержащий информацию из датасета. берем значение колонки username из 0 элемента (первая строка) -->{{#if (eq data.0.username "admin")}}<th>Зарплата</th>{{/if}}</tr></thead><tbody><!-- #each data - итерируемся по объекту data, фактически, построчный вывод таблицы -->{{#each data}}<tr><!-- this - контекстный объект, текущая строка --><td> {{this.employee_id}}</td><td> {{this.employee_name}}</td><!-- если пользователь не admin, то ячейку employee_salary не отображаем-->{{#if (eq this.username "admin")}}<td> {{this.employee_salary}} </td>{{/if}}</tr>{{/each}}</tbody></table>Код CSS Styles:.data-table {border-collapse: collapse;width: 100%;}.data-table th, .data-table td {border: 1px solid black;padding: 8px;text-align: left;}
Проверим работоспособность решения.
Заменим в коде Handlebars Template 'admin' на 'dsa' (или любой другой произвольный набор символов):
Изображение выше подтверждает, что Column-Level Security (CLS) на уровне чарта было реализовано успешно!
Важно - "Слепые зоны" безопасности и сложность поддержки: Прямое сравнение с именем пользователя ("admin") — это просто пример. В реальном проекте так делать не стоит. Логику нужно строить на основе ролей, а не конкретных логинов.
Еще одна самая распространенная ошибка на данном этапе – это жесткое кодирование логинов в шаблоне. При смене сотрудника или добавлении нового администратора придется править код каждого чарта.
Поэтому мы рекомендуем создать в базе данных справочную таблицу user_roles (пользователь -> роль). Затем с помощью Jinja можно выполнить подзапрос, чтобы получить роль текущего пользователя, и использовать в шаблоне уже ее. Это сложнее в настройке, но надежнее и легче в поддержке.
Пример усовершенствованной логики на основе ролей:
Run
{{! Предположим, что в data.0.current_user_role лежит роль пользователя }}
{{#if (or (eq data.0.current_user_role "Admin") (eq data.0.current_user_role "HR_Director"))}}
<th>Зарплата</th>
{{/if}}
После сохранения чарта критически важно провести тщательное тестирование.
- Тест под учетной записью с полным доступом (admin): Убедитесь, что все колонки, включая зарплату, отображаются.
- Тест под учетной записью с ограниченными правами (user, manager): Убедитесь, что колонка с зарплатой скрыта.
- Проверка на дашборде: Разместите чарт на дашборде и протестируйте отображение под разными пользователями.
Важно! Этот метод реализует CLS только на уровне визуализации. Важно понимать его ограничения. Во-первых, данные все равно передаются в браузер. Если пользователь откроет инструменты разработчика (Developer Tools) во вкладке "Сеть" (Network), он может увидеть сырой JSON-ответ от Superset, в котором будут все данные, включая скрытые колонки. Во-вторых, это не защита на уровне данных, это защита на уровне представления.
Этот метод применим для удобства и предотвращения случайного просмотра конфиденциальных данных рядовыми сотрудниками, а также в интранет-средах с доверенными пользователями.
Этот метод НЕ применим для защиты строго конфиденциальной информации (персональные данные, финансы), где риски утечки высоки, а также если пользователи имеют технические навыки для анализа сетевого трафика
Альтернативные и более надежные подходы
Для реализации настоящей, неуязвимой CLS требуется работа на более глубоком уровне.
Во-первых, это виртуальные датасеты: Можно создать несколько SQL-представлений для разных ролей. Например, представление v_employees_for_managers будет выбирать все колонки, кроме salary, а представление v_employees_for_hr — включать ее. Недостаток: дублирование логики и датасетов.
Во-вторых, это динамический SQL на основе Jinja в самом датасете. Можно создать один датасет, где список выбираемых колонок формируется динамически.
SELECT
employee_id,
employee_name
{% if current_username() in ['admin', 'hr_user'] %}
, employee_salary
{% endif %}
FROM salary_table
Это более правильный и безопасный подход! Данные, для которых нет прав, просто не будут выбраны из базы и не попадут в браузер. Однако его поддержка зависит от вашей СУБД и может быть сложнее в отладке.
В целом, реализация Column Level Security в Apache Superset с помощью Jinja и Handlebars — это мощный и гибкий метод, который позволяет решить множество бизнес-задач по разграничению доступа. Однако, как и любой инструмент, он требует грамотного применения и понимания его ограничений.









