PostgreSQL 9.5 - 行级安全 / 角色最佳实践

14
我正在努力掌握如何在支持Web应用程序的多租户数据库中使用新的行级安全特性的最佳方法。
目前,该应用程序有几个不同的ROLE可用,具体取决于它尝试执行的操作。
一旦应用程序使用自己的ROLE建立连接,应用程序将身份验证参数(由用户提供)传递到不同的函数中,根据用户提供的身份验证参数过滤出行。该系统旨在与成千上万的用户一起使用,似乎可以正常工作;但是,它确实很笨重(而且慢)。
看来,如果我想使用新的行级安全功能,我需要为每个真实世界的用户(而不仅仅是Web应用程序)创建一个新的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在哪里定义?


4
http://www.postgresql.org/message-id/56840D1A.8030203@gmail.com - user330315
@a_horse_with_no_name - 做得好,谢谢;但是,线程中给出的示例有点晦涩难懂...我已经更新了问题。 - losthorse
这是针对“配置”参数的。将它们用于此基本上是一种“hack”。您不需要事先定义它们 - 这可以动态完成。请注意,如果在之前未定义该参数,则 current_setting('app_name.app_user') 将导致错误。为了防止这种情况发生,您可以在 postgresql.conf 中定义一个虚拟值。 - user330315
为了完整起见,还有一个ACL扩展,用于细粒度权限控制,可以与行级安全性集成。没有必要使用ROLE - mlt
1个回答

10
根据会话设置来设置安全策略是一个非常糟糕的想法(我讨厌使用大写字母和粗体,所以请相信我这句话的严重性)。任何用户都可以使用SET SESSION 'app_name.app_user' = 'bob',一旦有人发现“app_name.app_user”是入口(相信我,他们会发现),那么你的整个安全措施就被破坏了。
我唯一看到的方法是使用一个只有webadmin可以访问的表格来存储会话令牌(我想到了uuid类型,为了方便使用,转换成text)。login()函数是SECURITY DEFINER(假设所有者是webadmin),设置令牌以及会话SET,然后该表由(或具有适当的特权)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、排序或其他魔法,如果表中存在 uuid,则策略将通过,否则将失败。) uuid 不可能被猜测(至少在您的有生之年内是这样的),也不可能被除 webadmin 之外的任何人检索。


这并不完全准确。可以为给定的角色指定策略,因此,在您的示例中,策略只能为“webadmin”角色创建,然后设置“app_name.app_user” GUC 将仅影响该角色发出的查询。这可能足以满足您的安全要求,但它将防止“任何用户”通过更改GUC来访问表中的任何记录。当然,这需要使用“webadmin”角色的应用程序正确验证用户并且没有SQL注入漏洞。 - Stephen Frost
4
2nd Quadrant 提供了一些示例,使用加密签名来管理授权,使得用户无法绕过授权。 - spiffytech

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接