Форум программистов, компьютерный форум, киберфорум
Codd
Войти
Регистрация
Восстановить пароль

Мульти-тенантные БД с PostgreSQL Row Security

Запись от Codd размещена 23.04.2025 в 20:33
Показов 4789 Комментарии 0

Нажмите на изображение для увеличения
Название: 4b0434a7-fbc4-47cb-9477-fd2c425afa39.jpg
Просмотров: 124
Размер:	177.9 Кб
ID:	10639
Современные облачные сервисы и бизнес-приложения всё чаще обслуживают множество клиентов в рамках единой программной инфраструктуры. Эта архитектурная модель, известная как мульти-тенантность, стала стандартом де-факто для SaaS-решений (Software as a Service). В таких системах критически важным становится вопрос строгой изоляции данных между разными клиентами при одновременном сохранении эффективности и масштабируемости инфраструктуры. Мульти-тенантная архитектура предполагает, что единая инсталляция приложения обслуживает нескольких клиентов (тенантов) при этом данные каждого тенанта логически или физически изолированы. Термин "тенант" в этом контексте означает независимую группу пользователей с общим доступом к выделенному представлению приложения и данных. Такой подход позволяет значительно оптимизировать использование вычислительных ресурсов и упростить процессы обновления и администрирования системы. Среди ключевых преимуществ мульти-тенантного подхода можно выделить:

1. Экономическую эффективность – снижение затрат на инфраструктуру благодаря совместному использованию ресурсов.
2. Упрощенное обслуживание – централизованное управление обновлениями и конфигурацией.
3. Улучшенную масштабируемость – более гибкое распределение ресурсов в зависимости от нагрузки.

Однако внедрение такой архитектуры сопряжено и с серьезными вызовами главным из которых является обеспечение надежной изоляции данных разных тенантов. Утечка информации между тенантами может привести к компрометации конфиденциальных данных и нарушению договорных обязательств.

В практике проектирования мульти-тенантных систем сформировались три основных подхода:

1. "База данных на тенанта" – каждый клиент получает собственную физически изолированную базу данных.
2. "Схема на тенанта" – данные разных клиентов хранятся в разных схемах в рамках одной БД.
3. "Таблицы с дискриминатором" – все данные находятся в общих таблицах, где строки маркируются идентификатором тенанта.

Выбор конкретного подхода определяется балансом между требованиями к изоляции, производительности и простоте управления. PostgreSQL как одна из ведущих реляционных СУБД с открытым кодом, предлагает инструменты для реализации всех этих моделей и развивает специальные механизмы для эффективной работы с мульти-тенантными системами.

С момента своего появления PostgreSQL постоянно совершенствовал возможности разграничения доступа. Первоначально использовались лишь базовые средства – роли и привилегии на объекты БД. В версии 9.2 был представлен механизм Row Security Policies (RLS), который произвел революцию в подходах к изоляции данных, позволив определять правила доступа на уровне отдельных строк таблицы. Этот механизм открыл возможность реализации модели "общих таблиц" с высокой степенью изоляции без негативного влияния на производительность. В сочетании с другими возможностями PostgreSQL, такими как схемы и представления с правилами ограничения доступа, RLS формирует мощную экосистему инструментов для построения безопасных мульти-тенантных систем.

Принципы Row Level Security (RLS)



Row Level Security (RLS) – это механизм контроля доступа к данным на уровне отдельных строк таблицы, который был внедрен в PostgreSQL начиная с версии 9.5. Данная технология позволяет определять политики безопасности, ограничивающие доступ пользователей к определенным строкам в зависимости от контекста выполнения запроса и атрибутов самих строк. Основное преимущество RLS заключается в том, что логика фильтрации интегрирована непосредственно в систему управления базой данных, а не в приложение. Это делает защиту данных более надежной и независимой от ошибок или уязвимостей в коде приложения.

Механизм работы RLS основан на автоматической модификации запросов к таблицам с включенной защитой. Когда пользователь выполняет запрос к таблице с активированной политикой RLS, PostgreSQL автоматически добавляет условия фильтрации к запросу, сужая видимый набор данных только до тех строк, которые пользователь имеет право видеть. Этот процесс полностью прозрачен для клиентского приложения.

SQL
1
2
3
4
5
6
-- Включение RLS для таблицы
ALTER TABLE tenant_data ENABLE ROW LEVEL SECURITY;
 
-- Создание политики, ограничивающей видимость строк
CREATE POLICY tenant_isolation_policy ON tenant_data
    USING (tenant_id = current_setting('app.current_tenant')::INTEGER);
В приведенном примере политика tenant_isolation_policy ограничивает доступ к строкам таблицы tenant_data только теми записями, где значение поля tenant_id совпадает со значением переменной сессии app.current_tenant. Таким образом, пользователи одного тенанта не смогут получить доступ к данным другого тенанта.

Синтаксис определения политик RLS достаточно гибок и позволяет создавать сложные условия доступа. Политика может определяться для различных операций с данными (SELECT, INSERT, UPDATE, DELETE) и может включать различные условия для каждой из них.

SQL
1
2
3
4
CREATE POLICY complex_policy ON tenant_data
    FOR SELECT
    USING (tenant_id = current_setting('app.current_tenant')::INTEGER)
    WITH CHECK (tenant_id = current_setting('app.current_tenant')::INTEGER AND STATUS <> 'deleted');
В этом примере добавлено дополнительное условие WITH CHECK, которое будет применяться при операциях модификации данных, не позволяя изменять строки со статусом 'deleted'.
PostgreSQL также поддерживает параметризованные политики, позволяющие динамически определять правила доступа на основе ролей пользователей или других параметров контекста:

SQL
1
2
3
4
5
6
7
8
CREATE POLICY role_based_policy ON tenant_data
    USING (
        CASE 
            WHEN CURRENT_USER = 'admin' THEN TRUE  -- Администраторы видят все строки
            WHEN pg_has_role(CURRENT_USER, 'reader', 'member') THEN tenant_id = current_setting('app.current_tenant')::INTEGER
            ELSE FALSE  -- Остальные не видят ничего
        END
    );
Несмотря на мощность механизма RLS, у него есть определенные ограничения. Важно понимать, что политики RLS не применяются к пользователям с привилегиями суперпользователя или к владельцам таблиц, если это специально не настроено. Это может создать неожиданные уязвимости, если не учитывается при проектировании системы безопасности. Другое существенное ограничение связано с производительностью. Применение сложных политик RLS может снизить эффективность выполнения запросов, особенно при отсутствии соответствующих индексов для полей, используемых в условиях политик.
По сравнению с альтернативными подходами к изоляции данных тенантов, RLS имеет значительные преимущества:

1. В отличие от подхода "база данных на тенанта", RLS позволяет эффективно использовать системные ресурсы и упрощает администрирование.
2. По сравнению с моделью "схема на тенанта", RLS обеспечивает более гибкую настройку доступа и лучшую масштабируемость при большом количестве тенантов.
3. В отличие от ручной фильтрации на уровне приложения, RLS гарантирует, что ограничения доступа всегда применяются, независимо от точки входа в систему.

RLS можно эффективно комбинировать с другими механизмами безопасности PostgreSQL. Например, система ролей позволяет определять группы пользователей с различными правами, а грантовая система обеспечивает контроль доступа на уровне объектов базы данных. Объединение этих механизмов позволяет создавать многоуровневые системы защиты.

Особый интерес представляет интеграция RLS с механизмом представлений (views). Представления могут использоваться для дополнительной абстракции и упрощения доступа к данным, при этом политики RLS будут применяться к базовым таблицам, обеспечивая безопасность на низком уровне.

Динамическое управление политиками RLS в зависимости от бизнес-логики открывает широкие возможности для адаптации системы безопасности к меняющимся требованиям. Политики могут создаваться, изменяться и удаляться "на лету", без необходимости перезапуска сервера или переподключения клиентов. Это позволяет реализовывать сложные сценарии безопасности, такие как временный доступ к данным других тенантов для выполнения специфических операций или изменение уровня доступа в зависимости от состояния системы.

ERROR: more than one row returned by a subquery
Доброго времени суток!! Может кто нибудь сталкивался с подобным родом ошибок? Как быть и что...

ERROR: more than one row returned by a subquery used as an expression
Добрового времени суток господа, прошу пожалуйста помочь с такой проблемой при запросе : update...

RLS политики и функции Security Definer
Есть таблица на которую настроены RLS политики (пользователю доступны не все строки), так же есть...

PostgreSQL
Здраствуйте. Работаю на одной из фирм , програмистом. Тока начинают. Програмирую на Линуксе. Так...


Практическая реализация



Перейдем от теории к практике и рассмотрим, как реализовать мульти-тенантную систему с использованием Row Level Security в PostgreSQL. Рассмотрим полный цикл создания архитектуры, от проектирования схемы базы данных до настройки политик безопасности и проверки их работоспособности. Начнем с создания базовой структуры базы данных, которая будет поддерживать мульти-тенантность:

SQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
-- Создаем таблицу тенантов
CREATE TABLE tenants (
    tenant_id SERIAL PRIMARY KEY,
    tenant_name VARCHAR(100) NOT NULL,
    active BOOLEAN DEFAULT TRUE,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
 
-- Создаем таблицу пользователей с привязкой к тенантам
CREATE TABLE users (
    user_id SERIAL PRIMARY KEY,
    tenant_id INTEGER NOT NULL REFERENCES tenants(tenant_id),
    username VARCHAR(50) NOT NULL,
    email VARCHAR(100) NOT NULL,
    password_hash VARCHAR(255) NOT NULL,
    ROLE VARCHAR(20) NOT NULL DEFAULT 'user',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    UNIQUE(tenant_id, username),
    UNIQUE(email)
);
 
-- Создаем таблицу с данными, которые будем разграничивать
CREATE TABLE customer_data (
    data_id SERIAL PRIMARY KEY,
    tenant_id INTEGER NOT NULL REFERENCES tenants(tenant_id),
    customer_name VARCHAR(100) NOT NULL,
    contact_info JSONB,
    STATUS VARCHAR(20) DEFAULT 'active',
    created_by INTEGER REFERENCES users(user_id),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
После создания основных таблиц необходимо включить Row Level Security и создать соответствующие политики доступа:

SQL
1
2
3
4
5
6
-- Включаем RLS для таблицы с данными клиентов
ALTER TABLE customer_data ENABLE ROW LEVEL SECURITY;
 
-- Создаем базовую политику изоляции данных по тенантам
CREATE POLICY tenant_isolation ON customer_data
    USING (tenant_id = current_setting('app.current_tenant_id', TRUE)::INTEGER);
Обратите внимание на использование current_setting('app.current_tenant_id') – это параметр конфигурации сессии, который мы будем устанавливать для каждого подключения для идентификации текущего тенанта.
Для настройки контекста безопасности мы можем создать вспомогательные функции, которые будут использоваться при установлении соединения с базой данных:

SQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
-- Создаем функцию для установки контекста тенанта
CREATE OR REPLACE FUNCTION set_tenant_context(p_tenant_id INTEGER)
RETURNS VOID AS $$
BEGIN
    -- Проверяем существование тенанта
    IF NOT EXISTS (SELECT 1 FROM tenants WHERE tenant_id = p_tenant_id) THEN
        RAISE EXCEPTION 'Tenant with ID % does not exist', p_tenant_id;
    END IF;
    
    -- Устанавливаем параметр сессии
    PERFORM set_config('app.current_tenant_id', p_tenant_id::TEXT, FALSE);
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
 
-- Создаем функцию для получения текущего тенанта
CREATE OR REPLACE FUNCTION get_current_tenant_id()
RETURNS INTEGER AS $$
BEGIN
    RETURN current_setting('app.current_tenant_id', TRUE)::INTEGER;
EXCEPTION
    WHEN OTHERS THEN
        RETURN NULL;
END;
$$ LANGUAGE plpgsql STABLE;
Теперь рассмотрим, как эта система будет работать на практике. Сначала создадим некоторые тестовые данные:

SQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
-- Создаем тенантов
INSERT INTO tenants (tenant_name) VALUES ('Acme Corp'), ('Globex Inc');
 
-- Получаем IDs тенантов
DO $$
DECLARE
    acme_id INTEGER;
    globex_id INTEGER;
BEGIN
    SELECT tenant_id INTO acme_id FROM tenants WHERE tenant_name = 'Acme Corp';
    SELECT tenant_id INTO globex_id FROM tenants WHERE tenant_name = 'Globex Inc';
    
    -- Создаем пользователей для каждого тенанта
    INSERT INTO users (tenant_id, username, email, password_hash, ROLE)
    VALUES 
        (acme_id, 'john', 'john@acme.com', 'hash123', 'admin'),
        (acme_id, 'sarah', 'sarah@acme.com', 'hash456', 'user'),
        (globex_id, 'mike', 'mike@globex.com', 'hash789', 'admin'),
        (globex_id, 'lisa', 'lisa@globex.com', 'hash012', 'user');
    
    -- Создаем данные для каждого тенанта
    INSERT INTO customer_data (tenant_id, customer_name, contact_info)
    VALUES 
        (acme_id, 'Customer A', '{"phone": "123-456-7890", "address": "123 Main St"}'),
        (acme_id, 'Customer B', '{"phone": "123-456-7891", "address": "124 Main St"}'),
        (globex_id, 'Customer X', '{"phone": "987-654-3210", "address": "456 Oak Ave"}'),
        (globex_id, 'Customer Y', '{"phone": "987-654-3211", "address": "457 Oak Ave"}');
END $$;
Теперь проверим, как работает изоляция данных с использованием RLS:

SQL
1
2
3
4
5
6
7
8
9
10
11
12
13
-- Устанавливаем контекст первого тенанта (Acme Corp)
SELECT set_tenant_context(1);
 
-- Запрашиваем данные клиентов
SELECT * FROM customer_data;
-- Должны видеть только клиентов Acme Corp
 
-- Переключаемся на контекст второго тенанта
SELECT set_tenant_context(2);
 
-- Запрашиваем данные клиентов
SELECT * FROM customer_data;
-- Должны видеть только клиентов Globex Inc
Для построения полноценной мульти-тенантной системы необходимо интегрировать механизм RLS с системой аутентификации. Рассмотрим, как это реализовать.

в большинстве приложений аутентификация происходит на уровне приложения, а не базы данных. Это создает определенный разрыв между системой идентификации пользователей и механизмом контроля доступа в базе данных. Для преодоления этого разрыва можно использовать подход с промежуточным слоем – специальными функциями, которые будут устанавливать контекст безопасности. Расширим нашу схему, добавив более детальную систему ролей

SQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
-- Создаем перечисление для возможных ролей
CREATE TYPE user_role AS ENUM ('tenant_admin', 'manager', 'regular_user', 'readonly');
 
-- Модифицируем таблицу пользователей
ALTER TABLE users 
DROP COLUMN ROLE,
ADD COLUMN ROLE user_role NOT NULL DEFAULT 'regular_user';
 
-- Создаем функцию авторизации
CREATE OR REPLACE FUNCTION authorize_user(p_username TEXT, p_tenant_id INTEGER)
RETURNS JSONB AS $$
DECLARE
    v_user_data JSONB;
BEGIN
    SELECT jsonb_build_object(
        'user_id', user_id,
        'tenant_id', tenant_id,
        'username', username,
        'role', ROLE
    ) INTO v_user_data
    FROM users
    WHERE username = p_username AND tenant_id = p_tenant_id;
    
    IF v_user_data IS NULL THEN
        RAISE EXCEPTION 'User not found or not authorized for this tenant';
    END IF;
    
    -- Устанавливаем контекст пользователя и тенанта
    PERFORM set_config('app.current_tenant_id', p_tenant_id::TEXT, FALSE);
    PERFORM set_config('app.current_user_id', (v_user_data->>'user_id'), FALSE);
    PERFORM set_config('app.current_user_role', (v_user_data->>'role'), FALSE);
    
    RETURN v_user_data;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
Теперь модифицируем нашу политику RLS, чтобы учитывать роли пользователей:

SQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
-- Удаляем предыдущую политику
DROP POLICY IF EXISTS tenant_isolation ON customer_data;
 
-- Создаем более сложную политику с учетом ролей
CREATE POLICY tenant_isolation ON customer_data
USING (
    tenant_id = current_setting('app.current_tenant_id')::INTEGER
    AND (
        current_setting('app.current_user_role') IN ('tenant_admin', 'manager') 
        OR STATUS <> 'archived'
    )
);
 
-- Добавляем политику для операций обновления
CREATE POLICY tenant_data_update ON customer_data
FOR UPDATE
USING (tenant_id = current_setting('app.current_tenant_id')::INTEGER)
WITH CHECK (
    tenant_id = current_setting('app.current_tenant_id')::INTEGER
    AND current_setting('app.current_user_role') IN ('tenant_admin', 'manager')
);
Важно понимать, что при нарушении политик RLS PostgreSQL не генерирует исключения напрямую – вместо этого запрос просто не возвращает или не модифицирует строки, к которым нет доступа. Это может привести к неочевидным ошибкам в приложении. Для более явной обработки таких ситуаций можно использовать триггеры:

SQL
1
2
3
4
5
6
7
8
9
10
11
12
13
CREATE OR REPLACE FUNCTION check_tenant_access()
RETURNS TRIGGER AS $$
BEGIN
    IF NEW.tenant_id <> current_setting('app.current_tenant_id')::INTEGER THEN
        RAISE EXCEPTION 'Cannot modify data belonging to another tenant';
    END IF;
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;
 
CREATE TRIGGER enforce_tenant_access
BEFORE INSERT OR UPDATE ON customer_data
FOR EACH ROW EXECUTE FUNCTION check_tenant_access();
Особый случай представляют суперпользователи PostgreSQL, которые по умолчанию могут обходить ограничения RLS. Это может создать неожиданные уязвимости, когда администратор базы данных получает доступ к данным всех тенантов. Для предотвращения таких ситуаций можно использовать дополнительные меры:

SQL
1
2
3
4
5
6
7
8
9
10
11
-- Принудительно применяем RLS даже для владельца таблицы
ALTER TABLE customer_data FORCE ROW LEVEL SECURITY;
 
-- Создаем специальную роль для администрирования таблицы
CREATE ROLE app_admin;
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO app_admin;
 
-- Создаем политику специально для администраторов
CREATE POLICY admin_access ON customer_data
TO app_admin
USING (TRUE);
Для эффективного тестирования политик безопасности полезно создать специальные функции, которые позволят имитировать действия различных пользователей:

SQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
CREATE OR REPLACE FUNCTION test_access_as_user(
    p_tenant_id INTEGER,
    p_username TEXT,
    p_query TEXT
) RETURNS TABLE (RESULT JSON) AS $$
DECLARE
    v_orig_tenant TEXT;
    v_orig_user TEXT;
    v_orig_role TEXT;
BEGIN
    -- Сохраняем текущий контекст
    v_orig_tenant := current_setting('app.current_tenant_id', TRUE);
    v_orig_user := current_setting('app.current_user_id', TRUE);
    v_orig_role := current_setting('app.current_user_role', TRUE);
    
    -- Устанавливаем новый контекст
    PERFORM authorize_user(p_username, p_tenant_id);
    
    -- Выполняем запрос
    RETURN QUERY EXECUTE p_query;
    
    -- Восстанавливаем контекст
    IF v_orig_tenant IS NOT NULL THEN
        PERFORM set_config('app.current_tenant_id', v_orig_tenant, FALSE);
    END IF;
    IF v_orig_user IS NOT NULL THEN
        PERFORM set_config('app.current_user_id', v_orig_user, FALSE);
    END IF;
    IF v_orig_role IS NOT NULL THEN
        PERFORM set_config('app.current_user_role', v_orig_role, FALSE);
    END IF;
EXCEPTION
    WHEN OTHERS THEN
        RAISE;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
Примеры использования этой функции:

SQL
1
2
3
4
5
6
-- Тестируем доступ пользователя из Acme Corp
SELECT * FROM test_access_as_user(1, 'john', 'SELECT * FROM customer_data');
 
-- Проверяем, что пользователь не может видеть данные другого тенанта
SELECT * FROM test_access_as_user(1, 'john', 
    'SELECT * FROM customer_data WHERE tenant_id = 2');
В реальных системах часто возникает необходимость организации сложных иерархических структур доступа, когда некоторые пользователи должны иметь доступ к подмножеству тенантов или когда существуют иерархические отношения между самими тенантами. Например, корпоративное приложение может обслуживать холдинговую компанию с несколькими дочерними организациями, каждая из которых является отдельным тенантом.
Для реализации таких сценариев потребуется расширить нашу модель данных:

SQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
-- Создаем таблицу для связей между тенантами
CREATE TABLE tenant_relationships (
    parent_tenant_id INTEGER REFERENCES tenants(tenant_id),
    child_tenant_id INTEGER REFERENCES tenants(tenant_id),
    relationship_type VARCHAR(50) NOT NULL,
    PRIMARY KEY (parent_tenant_id, child_tenant_id),
    CHECK (parent_tenant_id <> child_tenant_id)
);
 
-- Создаем таблицу для доступа пользователей к множеству тенантов
CREATE TABLE user_tenant_access (
    user_id INTEGER REFERENCES users(user_id),
    tenant_id INTEGER REFERENCES tenants(tenant_id),
    access_level VARCHAR(50) NOT NULL,
    granted_by INTEGER REFERENCES users(user_id),
    granted_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (user_id, tenant_id)
);
Теперь можно модифицировать наши политики безопасности, чтобы учитывать эти новые структуры:

SQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
-- Функция для проверки доступа пользователя к тенанту
CREATE OR REPLACE FUNCTION user_has_tenant_access(p_user_id INTEGER, p_tenant_id INTEGER)
RETURNS BOOLEAN AS $$
BEGIN
    -- Прямой доступ через основную привязку
    IF EXISTS (SELECT 1 FROM users WHERE user_id = p_user_id AND tenant_id = p_tenant_id) THEN
        RETURN TRUE;
    END IF;
    
    -- Доступ через явные права на дополнительных тенантов
    IF EXISTS (SELECT 1 FROM user_tenant_access 
               WHERE user_id = p_user_id AND tenant_id = p_tenant_id) THEN
        RETURN TRUE;
    END IF;
    
    -- Доступ через иерархию тенантов (рекурсивный)
    RETURN EXISTS (
        WITH RECURSIVE tenant_hierarchy AS (
            -- Базовый случай: тенанты, к которым у пользователя есть прямой доступ
            SELECT t.tenant_id
            FROM users u
            JOIN tenants t ON u.tenant_id = t.tenant_id
            WHERE u.user_id = p_user_id
            
            UNION
            
            SELECT uta.tenant_id
            FROM user_tenant_access uta
            WHERE uta.user_id = p_user_id
            
            UNION ALL
            
            -- Рекурсивный случай: дочерние тенанты
            SELECT tr.child_tenant_id
            FROM tenant_relationships tr
            JOIN tenant_hierarchy th ON tr.parent_tenant_id = th.tenant_id
        )
        SELECT 1 FROM tenant_hierarchy WHERE tenant_id = p_tenant_id
    );
END;
$$ LANGUAGE plpgsql STABLE;
С этой функцией обновим нашу политику RLS:

SQL
1
2
3
4
5
6
7
8
9
10
-- Обновляем политику с учетом иерархии доступа
DROP POLICY IF EXISTS tenant_isolation ON customer_data;
 
CREATE POLICY tenant_hierarchy_isolation ON customer_data
USING (
    user_has_tenant_access(
        current_setting('app.current_user_id')::INTEGER,
        tenant_id
    )
);
Такой подход обеспечивает гибкость при организации доступа в сложных мульти-тенантных системах. Однако есть важный нюанс: рекурсивные запросы в условиях политик RLS могут негативно влиять на производительность, особенно при глубоких иерархиях. Для смягчения этого эффекта можно использовать кэширование результатов проверки доступа:

SQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
-- Создаем таблицу для кэширования результатов проверки доступа
CREATE TABLE access_cache (
    user_id INTEGER NOT NULL,
    tenant_id INTEGER NOT NULL,
    has_access BOOLEAN NOT NULL,
    calculated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (user_id, tenant_id)
);
 
-- Функция для получения кэшированного результата доступа
CREATE OR REPLACE FUNCTION get_cached_tenant_access(p_user_id INTEGER, p_tenant_id INTEGER)
RETURNS BOOLEAN AS $$
DECLARE
    v_result BOOLEAN;
    v_cache_ttl INTERVAL = '15 minutes';
BEGIN
    -- Проверяем кэш
    SELECT has_access INTO v_result
    FROM access_cache
    WHERE user_id = p_user_id 
      AND tenant_id = p_tenant_id
      AND calculated_at > (CURRENT_TIMESTAMP - v_cache_ttl);
      
    -- Если кэш действителен, возвращаем результат
    IF FOUND THEN
        RETURN v_result;
    END IF;
    
    -- Иначе вычисляем заново
    v_result := user_has_tenant_access(p_user_id, p_tenant_id);
    
    -- Обновляем кэш
    INSERT INTO access_cache (user_id, tenant_id, has_access)
    VALUES (p_user_id, p_tenant_id, v_result)
    ON CONFLICT (user_id, tenant_id) 
    DO UPDATE SET has_access = v_result, calculated_at = CURRENT_TIMESTAMP;
    
    RETURN v_result;
END;
$$ LANGUAGE plpgsql STABLE;

Производительность и оптимизация



Применение Row Level Security в мульти-тенантных системах, при всех его преимуществах, может оказывать заметное влияние на производительность. Политики RLS фактически добавляют условия WHERE к каждому запросу, что увеличивает вычислительную нагрузку при выполнении операций с данными. Понимание этих механизмов и правильная оптимизация – ключевые факторы для построения эффективных систем.

Когда пользователь выполняет запрос к таблице с активированной политикой RLS, PostgreSQL автоматически модифицирует запрос, добавляя в него условия из политик безопасности. Если эти условия сложные или затрагивают большие объемы данных, производительность может заметно снижаться. Проиллюстрируем проблему производительности на примере:

SQL
1
2
3
4
5
6
7
-- Запрос без RLS (быстрое выполнение)
SELECT * FROM customer_data WHERE STATUS = 'active';
 
-- С активированным RLS запрос фактически преобразуется в:
SELECT * FROM customer_data 
WHERE STATUS = 'active' 
AND tenant_id = current_setting('app.current_tenant_id')::INTEGER;
Если таблица customer_data содержит миллионы записей, а индекс по полю tenant_id отсутствует, то каждый запрос будет требовать полного сканирования таблицы, что приведет к значительному падению производительности.

Для минимизации накладных расходов RLS можно применить несколько стратегий:

Первое и самое важное – правильное индексирование полей, используемых в условиях политик безопасности. В мульти-тенантной архитектуре это прежде всего относится к полю tenant_id:

SQL
1
2
3
4
5
-- Создание индекса для поля tenant_id
CREATE INDEX idx_customer_data_tenant_id ON customer_data(tenant_id);
 
-- Для более сложных политик могут потребоваться составные индексы
CREATE INDEX idx_customer_data_tenant_status ON customer_data(tenant_id, STATUS);
Индексы позволяют PostgreSQL быстро находить строки, соответствующие условиям политики, без необходимости сканировать всю таблицу. При этом важно помнить о компромиссе между производительностью на чтение и запись – каждый дополнительный индекс увеличивает время операций вставки и обновления данных. Вторая стратегия – оптимизация самих политик безопасности. Сложные политики с множеством условий и подзапросов могут существенно замедлить выполнение запросов. Рекомендуется:

1. Избегать использования сложных функций в условиях политик.
2. Минимизировать количество подзапросов.
3. По возможности использовать простые условия сравнения.

Пример оптимизации политики:

SQL
1
2
3
4
5
6
7
8
9
10
11
12
13
-- Менее оптимальная политика с подзапросом
CREATE POLICY complex_policy ON customer_data
USING (
  tenant_id = current_setting('app.current_tenant_id')::INTEGER
  AND data_id IN (SELECT data_id FROM allowed_data)
);
 
-- Более оптимальная политика с джойном
CREATE POLICY optimized_policy ON customer_data
USING (
  tenant_id = current_setting('app.current_tenant_id')::INTEGER
  AND EXISTS (SELECT 1 FROM allowed_data ad WHERE ad.data_id = customer_data.data_id)
);
Существуют также случаи, когда имеет смысл обойти механизм RLS для определенных операций, особенно для массовой обработки данных. Это можно сделать с помощью хранимых процедур с атрибутом SECURITY DEFINER:

SQL
1
2
3
4
5
6
7
8
9
10
11
12
13
CREATE OR REPLACE FUNCTION bulk_process_tenant_data(p_tenant_id INTEGER)
RETURNS VOID AS $$
BEGIN
  -- Проверка авторизации
  IF current_setting('app.current_tenant_id')::INTEGER <> p_tenant_id THEN
    RAISE EXCEPTION 'Unauthorized access';
  END IF;
  
  -- Выполнение операции без накладных расходов RLS
  UPDATE customer_data SET processed = TRUE 
  WHERE tenant_id = p_tenant_id AND processed = FALSE;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
Для эффективного мониторинга и выявления потенциальных проблем с производительностью RLS можно использовать встроенные инструменты PostgreSQL:

SQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-- Включение мониторинга запросов
ALTER SYSTEM SET pg_stat_statements.track = 'all';
SELECT pg_reload_conf();
 
-- Анализ самых медленных запросов с наибольшим временем выполнения
SELECT query, calls, total_time, mean_time
FROM pg_stat_statements
ORDER BY mean_time DESC
LIMIT 10;
 
-- Мониторинг использования индексов
SELECT relname, idx_scan, seq_scan
FROM pg_stat_user_tables
WHERE idx_scan < seq_scan
ORDER BY seq_scan DESC;
Особое внимание следует уделять изучению планов выполнения запросов с помощью команды EXPLAIN ANALYZE. Это позволяет увидеть, как PostgreSQL обрабатывает запросы с учетом политик RLS:

SQL
1
2
3
-- Анализ плана выполнения запроса
EXPLAIN ANALYZE
SELECT * FROM customer_data WHERE STATUS = 'active';
Изучение плана выполнения позволяет выявить узкие места, такие как последовательное сканирование таблиц вместо использования индексов, и принять меры по оптимизации.

При работе с RLS и кэшированием возникают определенные сложности. Стандартные механизмы кэширования на уровне приложения могут не учитывать правила доступа, определенные политиками RLS. Это может привести к утечке данных, когда пользователь получает из кэша информацию, к которой у него не должно быть доступа. Для решения этой проблемы можно использовать контекстно-зависимое кэширование, где ключ кэша включает идентификатор тенанта и роль пользователя:

SQL
1
cache_key = f"{query_hash}_{tenant_id}_{user_role}"
Альтернативный подход – использование материализованных представлений с учетом контекста тенанта:

SQL
1
2
3
4
5
6
7
-- Создание материализованного представления для тенанта
CREATE MATERIALIZED VIEW tenant_1_active_customers AS
SELECT * FROM customer_data
WHERE tenant_id = 1 AND STATUS = 'active';
 
-- Обновление представления по расписанию
REFRESH MATERIALIZED VIEW tenant_1_active_customers;
Еще одна техника оптимизации – секционирование (партиционирование) таблиц по идентификатору тенанта. Это особенно эффективно для крупных мульти-тенантных систем:

SQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-- Создание секционированной таблицы
CREATE TABLE customer_data_partitioned (
  data_id SERIAL,
  tenant_id INTEGER NOT NULL,
  customer_name VARCHAR(100) NOT NULL,
  STATUS VARCHAR(20) DEFAULT 'active',
  PRIMARY KEY (tenant_id, data_id)
) PARTITION BY LIST (tenant_id);
 
-- Создание секций для каждого тенанта
CREATE TABLE customer_data_tenant_1 PARTITION OF customer_data_partitioned
FOR VALUES IN (1);
 
CREATE TABLE customer_data_tenant_2 PARTITION OF customer_data_partitioned
FOR VALUES IN (2);
Такой подход позволяет PostgreSQL обрабатывать запросы только к нужной секции, минуя проверку условий RLS, что значительно повышает производительность.

Примеры из практики



Теоретические знания о Row Level Security в PostgreSQL приобретают особую ценность, когда мы видим их применение в реальных проектах. Рассмотрим несколько практических кейсов, иллюстрирующих различные аспекты внедрения мульти-тенантной архитектуры на базе PostgreSQL.

SaaS-платформа для управления клиентскими коммуникациями



Компания, разрабатывающая CRM-систему для малого и среднего бизнеса, столкнулась с необходимостью перехода от модели "база данных на клиента" к более консолидированной архитектуре. Причиной послужил быстрый рост количества клиентов, что привело к проблемам с администрированием множества отдельных баз данных и неэффективным использованием серверных ресурсов. Решение включало миграцию на единую базу данных с использованием Row Level Security:

SQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
-- Определение основной таблицы контактов
CREATE TABLE contacts (
  contact_id BIGSERIAL PRIMARY KEY,
  tenant_id INTEGER NOT NULL,
  name VARCHAR(100) NOT NULL,
  email VARCHAR(255),
  phone VARCHAR(50),
  STATUS VARCHAR(20) DEFAULT 'active',
  custom_fields JSONB,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
 
-- Включение RLS и создание базовой политики
ALTER TABLE contacts ENABLE ROW LEVEL SECURITY;
 
CREATE POLICY tenant_isolation ON contacts
USING (tenant_id = current_setting('app.tenant_id')::INTEGER);
Для оптимизации производительности разработчики внедрили партиционирование данных по диапазонам значений tenant_id, что позволило эффективно масштабировать систему при росте количества клиентов:

SQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
CREATE TABLE contacts_partition (
  contact_id BIGSERIAL,
  tenant_id INTEGER NOT NULL,
  name VARCHAR(100) NOT NULL,
  email VARCHAR(255),
  phone VARCHAR(50),
  STATUS VARCHAR(20) DEFAULT 'active',
  custom_fields JSONB,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (tenant_id, contact_id)
) PARTITION BY RANGE (tenant_id);
 
CREATE TABLE contacts_1_100 PARTITION OF contacts_partition
FOR VALUES FROM (1) TO (101);
 
CREATE TABLE contacts_101_200 PARTITION OF contacts_partition
FOR VALUES FROM (101) TO (201);
Особую сложность представляла обработка поисковых запросов с использованием полнотекстового поиска. Для сохранения изоляции данных между тенантами без ущерба для производительности была реализована специальная функция поиска:

SQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
CREATE OR REPLACE FUNCTION search_tenant_contacts(
  p_tenant_id INTEGER,
  p_query TEXT
) RETURNS SETOF contacts AS $$
BEGIN
  -- Проверка контекста безопасности
  IF current_setting('app.tenant_id')::INTEGER <> p_tenant_id THEN
    RAISE EXCEPTION 'Unauthorized access attempt';
  END IF;
  
  RETURN QUERY
  SELECT * FROM contacts
  WHERE tenant_id = p_tenant_id
  AND to_tsvector('english', name || ' ' || COALESCE(email, '') || ' ' || COALESCE(phone, ''))
      @@ to_tsquery('english', p_query);
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;

Аналитическая платформа для образовательных учреждений



Другой показательный пример – платформа для анализа успеваемости студентов, используемая несколькими образовательными учреждениями. Каждое учреждение выступает отдельным тенантом, и вопрос безопасности и конфиденциальности данных об учащихся особенно критичен в этой сфере.
Архитектура системы реализована с использованием схем для логической группировки таблиц и Row Level Security для обеспечения изоляции данных:

SQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
-- Создание схемы для общих данных
CREATE SCHEMA common;
 
-- Создание схемы для данных тенантов
CREATE SCHEMA tenant_data;
 
-- Таблица образовательных учреждений
CREATE TABLE common.institutions (
  institution_id SERIAL PRIMARY KEY,
  name VARCHAR(255) NOT NULL,
  address TEXT,
  contact_info JSONB,
  subscription_plan VARCHAR(50),
  active BOOLEAN DEFAULT TRUE
);
 
-- Таблица студентов с RLS
CREATE TABLE tenant_data.students (
  student_id SERIAL PRIMARY KEY,
  institution_id INTEGER REFERENCES common.institutions(institution_id),
  full_name VARCHAR(255) NOT NULL,
  date_of_birth DATE,
  grade_level INTEGER,
  enrollment_date DATE,
  academic_data JSONB
);
 
ALTER TABLE tenant_data.students ENABLE ROW LEVEL SECURITY;
Интересной особенностью данной системы стало внедрение динамического управления политиками для обеспечения временного доступа к данным других учреждений в целях проведения сравнительного анализа:

SQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
-- Функция для временного предоставления доступа к данным другого учреждения
CREATE OR REPLACE FUNCTION grant_temporary_access(
  source_institution_id INTEGER,
  target_institution_id INTEGER,
  purpose TEXT,
  duration INTERVAL
) RETURNS UUID AS $$
DECLARE
  access_id UUID;
BEGIN
  -- Генерируем уникальный идентификатор сессии доступа
  access_id := gen_random_uuid();
  
  -- Записываем информацию о предоставленном доступе
  INSERT INTO common.access_grants (
    access_id,
    source_institution_id,
    target_institution_id,
    purpose,
    granted_at,
    expires_at,
    granted_by
  ) VALUES (
    access_id,
    source_institution_id,
    target_institution_id,
    purpose,
    CURRENT_TIMESTAMP,
    CURRENT_TIMESTAMP + duration,
    current_setting('app.user_id')::INTEGER
  );
  
  RETURN access_id;
END;
$$ LANGUAGE plpgsql;
 
-- Политика RLS, учитывающая временные разрешения
CREATE POLICY institution_data_access ON tenant_data.students
USING (
  institution_id = current_setting('app.institution_id')::INTEGER
  OR EXISTS (
    SELECT 1 FROM common.access_grants
    WHERE source_institution_id = current_setting('app.institution_id')::INTEGER
    AND target_institution_id = institution_id
    AND expires_at > CURRENT_TIMESTAMP
  )
);
Для обеспечения защиты персональных данных при совместном анализе была реализована система автоматической анонимизации:

SQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
-- Создание представления с анонимизированными данными
CREATE OR REPLACE VIEW tenant_data.anonymized_students AS
SELECT
  student_id,
  institution_id,
  CONCAT(LEFT(full_name, 1), '***') AS full_name,
  EXTRACT(YEAR FROM date_of_birth) AS birth_year,
  grade_level,
  EXTRACT(YEAR FROM enrollment_date) AS enrollment_year,
  jsonb_build_object(
    'avg_score', academic_data->'avg_score',
    'attendance_rate', academic_data->'attendance_rate'
  ) AS academic_metrics
FROM
  tenant_data.students;
 
-- Политика RLS для анонимизированного представления
ALTER VIEW tenant_data.anonymized_students ENABLE ROW LEVEL SECURITY;
 
CREATE POLICY anonymized_data_access ON tenant_data.anonymized_students
FOR SELECT
USING (TRUE);  -- Доступно для всех учреждений
Эти практические примеры демонстрируют гибкость PostgreSQL RLS в построении различных моделей мульти-тенантных систем, от простых решений с базовой изоляцией данных до сложных архитектур с динамическим управлением доступом и интеграцией со специализированными функциями безопасности.

Система электронного документооборота с гибкой моделью доступа



Еще один интересный пример – корпоративная система электронного документооборота с поддержкой множества организаций. В этой системе требовалось не только изолировать данные разных тенантов, но и обеспечить избирательное совместное использование документов между организациями-партнерами. Ключевым компонентом архитектуры стала сложная система ролей и разрешений, реализованная с использованием комбинации RLS и расширенного управления доступом:

SQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
-- Таблица документов с метаданными о совместном доступе
CREATE TABLE documents (
  document_id BIGSERIAL PRIMARY KEY,
  tenant_id INTEGER NOT NULL,
  title VARCHAR(200) NOT NULL,
  content TEXT,
  STATUS VARCHAR(50) DEFAULT 'draft',
  is_shared BOOLEAN DEFAULT FALSE,
  shared_with INTEGER[],  -- Массив ID тенантов для совместного доступа
  security_level INTEGER DEFAULT 1,
  created_by INTEGER NOT NULL,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
 
-- Создание политики RLS с учетом совместного доступа
ALTER TABLE documents ENABLE ROW LEVEL SECURITY;
 
CREATE POLICY documents_access_policy ON documents
USING (
  tenant_id = current_setting('app.current_tenant_id')::INTEGER 
  OR (
    is_shared = TRUE AND 
    current_setting('app.current_tenant_id')::INTEGER = ANY(shared_with)
  )
);
Для защиты от потенциальных утечек данных при использовании совместного доступа были реализованы дополнительные меры безопасности:

SQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
-- Триггер для проверки прав при изменении статуса совместного доступа
CREATE OR REPLACE FUNCTION check_sharing_permissions()
RETURNS TRIGGER AS $$
BEGIN
  -- Проверка, что пользователь принадлежит к тенанту-владельцу документа
  IF NEW.tenant_id <> current_setting('app.current_tenant_id')::INTEGER THEN
    IF NEW.is_shared <> OLD.is_shared OR NEW.shared_with <> OLD.shared_with THEN
      RAISE EXCEPTION 'Unauthorized attempt to modify sharing settings';
    END IF;
  END IF;
  
  -- Логирование операций совместного доступа
  IF NEW.is_shared AND (OLD.is_shared IS NULL OR NOT OLD.is_shared) THEN
    INSERT INTO audit.sharing_events (
      document_id, tenant_id, shared_with, user_id, event_type
    ) VALUES (
      NEW.document_id, NEW.tenant_id, NEW.shared_with, 
      current_setting('app.current_user_id')::INTEGER, 'SHARE'
    );
  END IF;
  
  RETURN NEW;
END;
$$ LANGUAGE plpgsql;
 
CREATE TRIGGER documents_sharing_trigger
BEFORE UPDATE ON documents
FOR EACH ROW
WHEN (NEW.is_shared IS DISTINCT FROM OLD.is_shared OR NEW.shared_with IS DISTINCT FROM OLD.shared_with)
EXECUTE FUNCTION check_sharing_permissions();

Микросервисная архитектура с централизованной авторизацией



При разработке микросервисной инфраструктуры для крупного финтех-проекта потребовалось интегрировать PostgreSQL с RLS в распределенную архитектуру. Каждый микросервис работал с отдельной схемой в базе данных, но требовалось обеспечить единые правила изоляции данных тенантов.
Решение включало создание специализированного сервиса авторизации, который генерировал JWT токены с информацией о тенанте:

SQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
-- Функция для проверки JWT и установки контекста безопасности
CREATE OR REPLACE FUNCTION set_context_from_jwt(p_token TEXT)
RETURNS JSONB AS $$
DECLARE
  payload JSONB;
  valid BOOLEAN;
BEGIN
  -- Декодирование и проверка JWT (упрощенно)
  SELECT 
    jwt_payload INTO payload
  FROM 
    verify_jwt(p_token) AS jwt_payload;
  
  IF payload IS NULL THEN
    RAISE EXCEPTION 'Invalid JWT token';
  END IF;
  
  -- Установка параметров сессии
  PERFORM set_config('app.current_tenant_id', payload->>'tenant_id', FALSE);
  PERFORM set_config('app.current_user_id', payload->>'sub', FALSE);
  PERFORM set_config('app.current_roles', payload->>'roles', FALSE);
  
  RETURN payload;
EXCEPTION
  WHEN OTHERS THEN
    RAISE;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
Этот подход позволил стандартизировать процесс авторизации и установки контекста безопасности для всех микросервисов, взаимодействующих с базой данных.

Мониторинг и аудит доступа в многопользовательских медицинских системах



Особый интерес представляет опыт внедрения мульти-тенантной архитектуры в системах здравоохранения, где требования к конфиденциальности данных особенно строги. Крупная телемедицинская платформа, обслуживающая несколько медицинских организаций, столкнулась с необходимостью организации комплексного аудита доступа к данным пациентов с использованием RLS. Решение включало создание выделенной схемы для хранения журналов аудита:

SQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
CREATE SCHEMA audit;
 
CREATE TABLE audit.data_access_events (
  event_id BIGSERIAL PRIMARY KEY,
  tenant_id INTEGER NOT NULL,
  user_id INTEGER NOT NULL,
  patient_id INTEGER NOT NULL,
  record_type VARCHAR(50),
  operation VARCHAR(10) NOT NULL,
  accessed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  client_ip INET,
  access_reason TEXT
);
 
-- Триггер для автоматической записи событий доступа
CREATE OR REPLACE FUNCTION log_patient_data_access()
RETURNS TRIGGER AS $$
BEGIN
  INSERT INTO audit.data_access_events (
    tenant_id, user_id, patient_id, record_type, operation, client_ip, access_reason
  ) VALUES (
    current_setting('app.tenant_id')::INTEGER,
    current_setting('app.user_id')::INTEGER,
    NEW.patient_id,
    TG_TABLE_NAME,
    TG_OP,
    inet_client_addr(),
    current_setting('app.access_reason', TRUE)
  );
  RETURN NEW;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
Для защиты целостности журналов аудита были приняты дополнительные меры:

SQL
1
2
3
4
5
6
7
8
9
10
11
12
13
-- Запрещаем прямое изменение таблицы аудита
REVOKE INSERT, UPDATE, DELETE ON audit.data_access_events FROM PUBLIC;
 
-- Установка RLS для контроля доступа к журналам
ALTER TABLE audit.data_access_events ENABLE ROW LEVEL SECURITY;
 
CREATE POLICY audit_isolation ON audit.data_access_events
FOR SELECT
USING (
  tenant_id = current_setting('app.tenant_id')::INTEGER
  AND (current_setting('app.user_role') = 'compliance_officer' 
       OR current_setting('app.user_role') = 'admin')
);

Геоинформационная система с территориальным разграничением данных



Еще один интересный пример – геоинформационная платформа для городских служб, где требовалось обеспечить не только изоляцию данных по тенантам, но и по географическим зонам ответственности:

SQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
-- Таблица объектов инфраструктуры с географическими данными
CREATE TABLE infrastructure_objects (
  object_id SERIAL PRIMARY KEY,
  tenant_id INTEGER NOT NULL,
  name VARCHAR(100) NOT NULL,
  TYPE VARCHAR(50) NOT NULL,
  STATUS VARCHAR(20) DEFAULT 'active',
  location GEOMETRY(POINT, 4326),
  responsibility_zone INTEGER REFERENCES zones(zone_id)
);
 
-- Политика доступа с учётом зоны ответственности
ALTER TABLE infrastructure_objects ENABLE ROW LEVEL SECURITY;
 
CREATE POLICY geo_tenant_policy ON infrastructure_objects
USING (
  tenant_id = current_setting('app.tenant_id')::INTEGER
  AND (
    responsibility_zone IN (
      SELECT zone_id FROM user_responsibility_zones 
      WHERE user_id = current_setting('app.user_id')::INTEGER
    )
    OR current_setting('app.user_role') = 'supervisor'
  )
);
Интересным решением стала интеграция с пространственными функциями PostgreSQL:

SQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
-- Функция для проверки прав доступа на основе географического положения
CREATE OR REPLACE FUNCTION check_location_access(
  p_location GEOMETRY, 
  p_user_id INTEGER
) RETURNS BOOLEAN AS $$
BEGIN
  RETURN EXISTS (
    SELECT 1 FROM user_responsibility_zones u
    JOIN zones z ON u.zone_id = z.zone_id
    WHERE u.user_id = p_user_id
    AND ST_Contains(z.boundary, p_location)
  );
END;
$$ LANGUAGE plpgsql STABLE;
Эти примеры наглядно демонстрируют, как PostgreSQL RLS позволяет реализовать сложные модели разграничения доступа, адаптированные к специфическим требованиям различных предметных областей. Технология обеспечивает гибкий баланс между безопасностью и производительностью, особенно в сочетании с другими возможностями PostgreSQL.

Заключение и рекомендации



Мульти-тенантные архитектуры с использованием PostgreSQL Row Level Security представляют собой мощное решение для современных информационных систем, обслуживающих множество клиентов. Подводя итоги, можно выделить несколько ключевых аспектов, определяющих успех таких проектов. Прежде всего, выбор модели мульти-тенантности должен соответствовать конкретным требованиям приложения. Для систем с небольшим количеством тенантов и строгими требованиями к изоляции подойдет подход "база данных на тенанта". При большом количестве тенантов и необходимости эффективного использования ресурсов оптимальным становится подход с общими таблицами и Row Level Security.
При проектировании мульти-тенантных систем рекомендуется:

1. Заранее продумать стратегию масштабирования — секционирование таблиц по tenant_id обеспечит хорошую производительность даже при значительном росте данных.
2. Использовать контекстную аутентификацию, когда идентификатор тенанта устанавливается в параметрах сессии и автоматически применяется политиками RLS.
3. Сочетать Row Level Security с другими механизмами безопасности — схемами, представлениями и управлением ролями для создания многоуровневой системы защиты.
4. Внедрить комплексный аудит доступа к данным, особенно для систем, работающих с конфиденциальной информацией.
5. Тщательно тестировать производительность — политики RLS могут существенно влиять на скорость выполнения запросов при отсутствии правильных индексов.

При разработке политик безопасности следует стремиться к их максимальной простоте и понятности. Сложные условия могут не только снижать производительность, но и создавать потенциальные уязвимости из-за неочевидной логики.

Row Level Security — технология, которая продолжает развиваться. Начиная с версии PostgreSQL 9.5, она получила множество улучшений, включая оптимизацию производительности и расширенную функциональность. Современные версии СУБД предлагают дополнительные возможности для тонкой настройки политик и их сочетания с другими инструментами безопасности.

Проблемы в PostgreSQL
Здраствуйте. Создаю проект в KDevelop , работаю с базой даных . Использую PostgreSQL , и...

XML и Postgresql
Всем привет! Кто-нибудь работал с типом XML в Postgresql? Как туда записать и оттуда считать...

Вышел релиз-кандидат СУБД PostgreSQL 9.0
Разработчики открытой системы управления базами данных PostgreSQL выпустили первый релиз-кандидат...

Состоялся выход стабильной версии СУБД PostgreSQL 9.0
Разработчики открытой системы управления базами данных Postgresql сообщили о выходе девятой версии...

Изменение каталога БД в Postgresql
Доброго времени суток! Кто знает, как в PostgreSQL прописать путь, где нужно создать БД......

PostgreSQL + OOo Basic
Добрый день! Ситуация такая. Имеется база Postgres. К ней подключен ООо через ODBC. Пытаюсь...

Из excel'я в postgresql
Доброго дня. Есть данные в экселе, нужно импортировать в базу postgresql. // получаю таблицу в...

как подружить c++ с postgresql?
Расскажите, что нужно сделать, что бы программа написанная в visual c++ могла бы обрабатывать...

PHP и PostgreSQL. ООП интерфейс
Всем доброго времени суток! Подскажите, существует ли ООП интерфейс (класс) для СУБД PostgreSQL...

Объект в postgresql
Только начинаю разбираться с PostgreSQL) Это, на сколько я понимаю - объектно-реляционная СУБД....

Установка Apache+PHP+PostgreSQL под Windows
Народ подскажите кто знает пошаговую установку в этом отношении. Нашла ...

Ищу литературу по PostgreSQL
Ребята у кого есть книги по PostgreSQL для работы системного администратора нужно срочьно...

Размещено в Без категории
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
Всего комментариев 0
Комментарии
 
Новые блоги и статьи
Генераторы Python для эффективной обработки данных
AI_Generated 21.05.2025
В Python существует инструмент настолько мощный и в то же время недооценённый, что я часто сравниваю его с тайным оружием в арсенале программиста. Речь идёт о генераторах — одной из самых элегантных. . .
Чем заменить Swagger в .NET WebAPI
stackOverflow 21.05.2025
Если вы создавали Web API на . NET в последние несколько лет, то наверняка сталкивались с зелёным интерфейсом Swagger UI. Этот инструмент стал практически стандартом для документирования и. . .
Использование Linq2Db в проектах C# .NET
UnmanagedCoder 21.05.2025
Среди множества претендентов на корону "идеального ORM" особое место занимает Linq2Db — микро-ORM, балансирующий между мощью полноценных инструментов и легковесностью ручного написания SQL. Что. . .
Реализация Domain-Driven Design с Java
Javaican 20.05.2025
DDD — это настоящий спасательный круг для проектов со сложной бизнес-логикой. Подход, предложенный Эриком Эвансом, позволяет создавать элегантные решения, которые точно отражают реальную предметную. . .
Возможности и нововведения C# 14
stackOverflow 20.05.2025
Выход версии C# 14, который ожидается вместе с . NET 10, приносит ряд интересных нововведений, действительно упрощающих жизнь разработчиков. Вы уже хотите опробовать эти новшества? Не проблема! Просто. . .
Собеседование по Node.js - вопросы и ответы
Reangularity 20.05.2025
Каждому разработчику рано или поздно приходится сталкиватся с техническими собеседованиями - этим стрессовым испытанием, где решается судьба карьерного роста и зарплатных ожиданий. В этой статье я. . .
Cython и C (СИ) расширения Python для максимальной производительности
py-thonny 20.05.2025
Python невероятно дружелюбен к начинающим и одновременно мощный для профи. Но стоит лишь заикнуться о высокопроизводительных вычислениях — и энтузиазм быстро улетучивается. Да, Питон медлительнее. . .
Безопасное программирование в Java и предотвращение уязвимостей (SQL-инъекции, XSS и др.)
Javaican 19.05.2025
Самые распространёные векторы атак на Java-приложения за последний год выглядят как классический "топ-3 хакерских фаворитов": SQL-инъекции (31%), межсайтовый скриптинг или XSS (28%) и CSRF-атаки. . .
Введение в Q# - язык квантовых вычислений от Microsoft
EggHead 19.05.2025
Microsoft вошла в гонку технологических гигантов с собственным языком программирования Q#, специально созданным для разработки квантовых алгоритмов. Но прежде чем погружаться в синтаксические дебри. . .
Безопасность Kubernetes с Falco и обнаружение вторжений
Mr. Docker 18.05.2025
Переход организаций к микросервисной архитектуре и контейнерным технологиям сопровождается лавинообразным ростом векторов атак — от тривиальных попыток взлома до многоступенчатых кибератак, способных. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2025, CyberForum.ru