PostgreSQL 9.5 - Безопасность уровня/лучшие методы ROLE

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

В настоящее время приложение имеет несколько разных ROLE, доступных в зависимости от действия, которое оно предпринимает.

Как только приложение создает соединение с помощью своего собственного ROLE, приложение передает параметры аутентификации (предоставляемые пользователем) в разные функции, которые отфильтровывают строки на основе параметров аутентификации, предоставленных пользователем. Система предназначена для работы с тысячами пользователей и, похоже, работает; однако он вызывающе неуклюжий (и медленный).

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

Это правильно? и если да, то стоит ли создавать тысячи ROLE в базе данных?


Обновить из a_horse_with_no_name ссылку в комментариях (спасибо, этот поток включен):

CREATE USER application;

CREATE TABLE t1 (id int primary key, f1 text, app_user text);
INSERT INTO t1 VALUES(1,'a','bob');
INSERT INTO t1 VALUES(2,'b','alice');
ALTER TABLE t1 ENABLE ROW LEVEL SECURITY;
CREATE POLICY P ON t1 USING (app_user = current_setting('app_name.app_user'));
GRANT SELECT ON t1 TO application;

SET SESSION AUTHORIZATION application;

SET app_name.app_user = 'bob';

SELECT * FROM t1;

 id | f1 | app_user
----+----+----------
  1 | a  | bob
(1 row)

SET app_name.app_user = 'alice';
SELECT * FROM t1;

 id | f1 | app_user
----+----+----------
  2 | b  | alice
(1 row)

SET app_name.app_user = 'none';
SELECT * FROM t1;

 id | f1 | app_user
----+----+----------
(0 rows)

Теперь меня смущает current_setting('app_name.app_user'), поскольку я находился под впечатлением, что это было только для параметров конфигурации... где определено app_name?

Ответ 1

Настройка политик безопасности на основе параметра сеанса - это идея BAD BAD BAD (я ненавижу оба CAPS и жирный), поэтому поверьте мне, что я имею в виду). Любой пользователь может SET SESSION 'app_name.app_user' = 'bob', поэтому, как только кто-то выяснит, что "app_name.app_user" - это дверь (поверьте мне, они будут), тогда вся ваша безопасность выходит за дверь.

Единственный способ, который я вижу, - это использовать таблицу, доступную только для вашего webadmin, в которой хранятся токены сеанса (тип uuid, отличная от text для удобства использования). Функция login() SECURITY DEFINER (при условии, что владелец webadmin), установив токен, а также сеанс SET ting, а затем таблица, принадлежащая (или имеющая соответствующие привилегии для) webadmin, ссылается на это таблицы и сеанса в своей политике.

К сожалению, вы не можете использовать временные (сеансовые) таблицы здесь, потому что вы не можете создавать политики во временной таблице, поэтому вам нужно использовать "настоящую" таблицу. Это что-то вроде штрафа за производительность, но весит это против повреждения взлома...

На практике:

CREATE FUNCTION login (uname text, pwd text) RETURNS boolean AS $$
DECLARE 
  t uuid;
BEGIN
  PERFORM * FROM users WHERE user = uname AND password = pwd;
  IF FOUND THEN
    INSERT INTO sessions SET token = uuid_generate_v4()::text, user ....
       RETURNING token INTO t;
    SET SESSION "app_name.token" = t;
    RETURN true;
  ELSE
    SET SESSION "app_name.token" = '';
    RETURN false;
  END IF;
END; $$ LANGUAGE plpgsql STRICT;

И теперь ваша политика будет ссылаться на sessions:

CREATE POLICY p ON t1 FOR SELECT
  USING (SELECT true FROM sessions WHERE token = current_setting('app_name.token'));

(Поскольку uuid можно считать уникальным, нет необходимости в LIMIT 1. ordering или другой магии, если uuid находится в таблице, политика будет проходить, в противном случае сбой.) uuid невозможно угадать (в любом случае, в любом случае) и невозможно получить кого-либо, кроме webadmin.