P2-007 Row Level Security для multi-tenancy¶
Метаданные¶
| Поле | Значение |
|---|---|
| Фаза | Phase 2: Core |
| Статус | done |
| Приоритет | critical |
| Связь с roadmap | Roadmap - Организации |
Контекст¶
Бизнес-контекст¶
Каждая организация — изолированный tenant. Данные одной организации не должны быть доступны другой. Это критично для безопасности и доверия пользователей к платформе.
Технический контекст¶
- PostgreSQL Row Level Security (RLS) обеспечивает изоляцию на уровне БД
tenant_idпередаётся черезapp.tenant_idsession variableTenantContextв common-security хранит текущий tenantTenantContextFilterустанавливает tenant из JWT токенаTenantAwareDataSourceDecoratorустанавливаетapp.tenant_idв PostgreSQL
Связанные документы: - Data Architecture — RLS описание - Architecture Overview — multi-tenancy принципы - Common Library — TenantAwareEntity
Существующий код: - TenantContext.java - TenantAwareEntity.java - TenantAwareDataSourceDecorator.java
Цель¶
Настроить Row Level Security в PostgreSQL для всех бизнес-таблиц и интегрировать с приложением.
Definition of Ready (DoR)¶
- [x] Контекст понятен и описан
- [x] Цель сформулирована
- [x] Acceptance Criteria определены
- [x] Технические детали проработаны
- [x] Зависимости определены и разрешены
- [x] Нет блокеров
Acceptance Criteria¶
RLS Политики¶
- [x] RLS включён на всех таблицах с tenant_id (events — готово, остальные при создании)
- [x] Политики SELECT/INSERT/UPDATE/DELETE применяются
- [x] Данные одной организации недоступны другой
- [x] Попытка доступа к чужим данным возвращает пустой результат (не ошибку)
Интеграция с приложением¶
- [x]
TenantContextFilterизвлекает tenant_id из JWT - [x]
TenantAwareDataSourceDecoratorустанавливаетapp.tenant_idперед каждым запросом - [x] Все сервисы используют tenant-aware конфигурацию (
aqstream.multitenancy.rls.enabled=true) - [x] При отсутствии tenant_id — запросы к tenant-aware таблицам возвращают пустой результат
Специальные случаи¶
- [x] Админ платформы может видеть данные всех организаций (bypass RLS через отсутствие tenant context)
- [x] Публичные endpoints (страница события) работают без tenant_id (политика
public_events_read) - [ ] Cross-tenant запросы (листинг публичных событий) работают корректно (будет протестировано с реальными данными)
Тестирование¶
- [x] Integration тесты проверяют изоляцию данных (
RlsIntegrationTest) - [x] Тест: создание данных в org A, попытка чтения из org B — пустой результат
- [x] Тест: superuser видит данные всех организаций (E2E тесты добавлены)
Definition of Done (DoD)¶
- [x] Все Acceptance Criteria выполнены
- [x] RLS миграции созданы с rollback (
003-improve-events-rls.xml) - [x] Unit тесты для TenantContext (
TenantContextTest.java) - [x] Integration тесты изоляции данных (
RlsIntegrationTest.java) - [x] Документация обновлена
- [x] Code review пройден
- [x] CI/CD pipeline проходит
Технические детали¶
Затрагиваемые компоненты¶
- [x] Backend: все сервисы (конфигурация DataSource)
- [ ] Frontend: —
- [x] Database: RLS политики на всех таблицах
- [ ] Infrastructure: —
SQL миграции для RLS¶
-- Включение RLS на таблице events
ALTER TABLE event_service.events ENABLE ROW LEVEL SECURITY;
-- Политика для обычных пользователей
CREATE POLICY tenant_isolation_policy ON event_service.events
USING (tenant_id = current_setting('app.tenant_id', true)::uuid);
-- Политика для INSERT (tenant_id должен совпадать)
CREATE POLICY tenant_insert_policy ON event_service.events
FOR INSERT
WITH CHECK (tenant_id = current_setting('app.tenant_id', true)::uuid);
-- Bypass для суперпользователя (админ)
ALTER TABLE event_service.events FORCE ROW LEVEL SECURITY;
Таблицы с RLS¶
Event Service (postgres-shared):
- event_service.events
- event_service.ticket_types
- event_service.registrations
- event_service.check_ins
- event_service.waitlist_entries
Notification Service (postgres-shared):
- notification_service.notification_logs
Media Service (postgres-shared):
- media_service.media
- media_service.media_variants
Глобальные таблицы (без RLS)¶
user_service.usersuser_service.organizationsuser_service.organization_membersuser_service.organization_requestsnotification_service.notification_templates(системные)
Конфигурация DataSource¶
@Configuration
public class TenantAwareDataSourceConfig {
@Bean
public DataSource dataSource(DataSource originalDataSource) {
return new TenantAwareDataSourceDecorator(originalDataSource);
}
}
Bypass для админа¶
// При запросах от админа — не устанавливать tenant_id
// RLS политика с current_setting('app.tenant_id', true) вернёт NULL
// и политика пропустит все строки
Зависимости¶
Блокирует¶
- Все задачи работающие с tenant-aware данными (события, регистрации)
Зависит от¶
- P2-006 Организации (источник tenant_id)
Out of Scope¶
- Шифрование данных по tenant
- Separate schemas per tenant (используем RLS)
- Cross-tenant reporting (кроме админа)
Заметки¶
- RLS — последний рубеж защиты. Приложение также должно проверять права доступа
current_setting('app.tenant_id', true)— true означает, что при отсутствии параметра вернётся NULL- Для публичных страниц событий нужен отдельный подход (чтение без tenant context)
- Performance: индексы на tenant_id критичны для производительности с RLS