supabase:用于列级安全的解决方案

发布于 2025-02-11 01:55:46 字数 214 浏览 2 评论 0 原文

目前,我正在使用supabase数据库。我面对的大障碍之一是列级安全性,似乎比RLS复杂得多。

假设我有一个称为 is_banned 的列,这是可查看但不可编辑的。但是,其余的列应该既可以编辑又可见。

我真正能想到的唯一解决方案是将其分成两个表并在“敏感信息”表上具有RLS - 但是为每个表创建一个私人表似乎是不必要的。

还有其他解决方案吗?

Currently, I'm using a Supabase database. One of the big roadblocks that I'm facing is column-level security, which seems a lot more complicated than RLS.

Say that I have a column called is_banned, that is viewable but not editable. However, the rest of the columns should be both editable and viewable.

The only solution that I can really think of is splitting it into two tables and having RLS on the "sensitive information" table - but creating a private table for every table seems rather unnecessary.

Are there other solutions?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

违心° 2025-02-18 01:55:46

我必须自己处理这个问题。我目前可以使用视图来解决它,但宁愿将来选择RLS策略,触发器或特权功能(截至目前未经测试)。我在下面分享了我研究的注释。


列级安全性(“ CLS”)是指根据某些条件选择性地禁止列值更新,插入甚至选择。有几种替代解决方案(摘要),每种都有优势和缺点。下面对它们进行了详细讨论。

选项1:RLS策略

(到目前为止我最喜欢的选项,但我尚未在实践中使用它

。 RLS)策略检索旧行,并比较您受保护的列的字段值是否会从旧行变为新行。解决方案的候选者已发布为堆栈溢出答案,但这仍然必须使其成为通用功能。

乍一看,这似乎比触发因素更好:它具有其优势,此外,Supabase无论如何都会促进使用RLS策略来访问控制,并且对RLS的UI支持要比对触发器的支持更好。因此,它将通过降低复杂性来提高数据库的一致性和可维护性。

但是,supabase RLS编辑器不能用于复杂的RLS策略(“ noreferrer”>问题报告由于解决方法,应将所有RLS代码包装到一个或嵌套的函数调用中,或者至少不超过一行代码。更好的是,在Supabase之外的版本控制下,将SQL源代码维护,并在要更改RLS策略,表格,功能等时,将其复制到Supabase SQL编辑器中。

选项2:触发器

请参见在这里,我的原始说明 a href =“ https://github.com/orgs/supabase/discussions/656#discussioncomment-5594653” rel =“ noreferrer”>改进的说明 github用户Christophemarois。

优点:

  • 不添加另一个表或视图,因此数据库结构由数据确定,而不是通过许可系统怪癖来确定。

  • 不需要更改默认的supabase权限或表格到示意分配。

  • 结合了RLS策略和列级权限的功能。

缺点:

  • 触发器在supabase UI中尚未得到很好的支持:只能更改触发器状态,但只能在UI中显示或编辑,只能在postgresql控制台中显示或编辑。实际上,这并不是什么问题,因为无论如何,您都必须直接与PostgreSQL数据库一起工作。

  • 它需要了解PGSQL或其他编程语言……对于某些人来说,编程是我们想要使用Supabase避免的。但是,该解决方案使用一个接收到白名单列的抽象函数(“允许更改”),因此无需进行实际编程,只需部署一些可重复使用的代码即可。

选项3:特权功能

“您可以用安全定义器隐藏函数后面的表。该表本身将无法提供更新访问权限,而是用户只能通过函数更新表。” ( source

,在该功能中,您可以按照自己喜欢的任何方式确定列级访问权限。模式中的任何此类功能 public 将通过API自动获得:

“写postgresql sql functions […],并通过 supabase.rpc('function_name',{param1:'value'}); 呼叫。 ( source )。

但是,问题在于,API不再具有“所有内容都在表中可用”的统一结构。

选项4:特定于用户的视图

请参阅指令。更多说明:

“您可以创建一个视图以仅显示所需的列,请确保使用语句忽略语句,因为它忽略了RLS(通常是),然后使用RLS阻止原始表。 “ ( source> source

/Github.com/supabase/supabase/discussions/8663#discussioncomment-3484271“ rel =“ noreferrer”>由supabase维护者推荐。总的来说,RLS政策和触发器似乎是可取的。

要使此解决方案安全,您必须使用选项 security_barrier = on 详细信息),这会严重影响视图性能。解决的唯一方法是不使用 where 子句,而是通过 security_invoker = on 重新使用基础表的RLS策略。这需要将基础表移至API不暴露的自定义数据库方案(见下文)。

优点:

  • 简单。视图就像桌子一样,每个人都知道PostgreSQL表 - 与触发器或(复杂的)RLS策略相比。

  • 您会看到您编辑的内容。用户(或他们的应用程序)可以在表中看到记录,不必担心由于RLS策略是否可以编辑。无论用户可以看到什么,他们都可以编辑。

  • ❓可根据需要扩展。 (仍然不确定。) 仅在视图中提供某个某些用户的编辑列。要找到正确的列,有时需要更多上下文。没问题:在API访问时间时,请再次根据需要加入底层基础表的视图和列。只有替代主键列 id 才需要始终包含在视图中。这不是一个问题:如果用户试图编辑它,则只有在使用新值时才能成功,在这种情况下,有效地创建了新的记录,无论如何都可能允许用户执行此操作。 (待确认,仍然可以使用适当的访问保护更新。)

(可以确认,

  • API将以适当的数据库设计中的形式公开数据。通过暴露其他视图,API变得不必要地复杂。

  • 无法真正重复使用底层表的RLS策略。在创建视图时使用 security_invoker = on 在创建视图时(详细信息)。但是,在执行此操作时,可以通过视图更新记录的相同用户也可以在基础表中更新该记录,从而规避使用该视图的列访问限制。唯一的方法是将基础表移至API未暴露的自定义数据库方案。这是可能的,但增加了结构上的复杂性。

  • 需要更改默认视图权限。由于这些简单视图,因此它们在PostgreSQL中是“可更新”视图。加上supabase schema public中默认的表级 /视图级权限,这意味着所有用户,甚至匿名的用户都可以从这些视图中删除记录,从而导致填充表中的记录删除。< / p>。< / p>

    要解决此问题,必须从视图中删除插入物并删除特权。这是对默认的supabase权限的更改,理想情况下是不需要的。

    有一个替代解决方案,但这不是很实际:您可以使用 security_invoker = on 创建视图来重复使用底层表的RLS策略。然后使用这些RLS策略来防止记录删除。但是,他们必须允许选择和更新;因此,除非将底层表移至API未曝光的模式,否则它将允许用户规避创建视图的列级安全性。

  • 没有限制某些 的好方法。这是因为视图不能具有自己的RLS政策。有几种解决此问题的方法:

    • 可能最佳的工作方式是构建表,以便允许对列的写入访问权限的用户使用该列中的每个值。例如,代替列角色(用户,admin)和状态(应用,批准,不赞成),将有可无效的布尔列 user_application admin_application user_status admin_status

    • 对于复杂情况,另一个选项是将基础表移至不访问API的自定义模式(同时仍授予所有Supabase角色的使用和权限;

    • 对于复杂情况,另一个选项是在视图或底层表上使用触发器。



选项5:列级访问权限

“您只能提供对表的列的一个子集:Grant Update(COL1,COL2)。 >)“( source

据报道,维护所有这些访问权利是一种麻烦。在supabase中,不可能向不同的身份验证的用户提供不同的权限,因为所有这些用户都使用相同的角色 autheratiencatienated 访问数据库。但是,在最高级别上工作时,您可以在这里有不同的选择。

选项6:与视图相比,表分裂

,这将主表分为多个部分。使用RLS策略,定义了谁可以用每个部分表来做什么;而且,与您只能在某个子句中部分模拟RLS策略的视图不同,RLS策略也可以用于限制用户可以用于列的值。要将它们一起使用,必须将它们加入请求。

将桌子分成两个时还可以。但是有时分裂几乎是“每列的一个表”,例如,对于每列角色一个列的许可管理表。这很糟糕,因为它“原子化”了数据,而不是以适当的正常形式保持数据,这意味着数据甚至无法以舒适的方式访问。可以通过将分开表再次组合并提供对这些底层表的写入访问的视图来解决。但是,还有很多表格。这是“丑陋的”。

I had to deal with this issue myself. I currently solve it with views, but would rather choose RLS policies, triggers or privileged functions in the future (untested, as of right now). I share the notes from my research into this issue below.


Column-level security ("CLS") means to selectively prohibit column value UPDATEs, INSERTs or even SELECTs based on certain conditions. There are several alternative solutions for this (summary), each with advantages and disadvantages. They are discussed in detail below.

Option 1: RLS policies

(My favourite option so far, but I have not yet used it in practice.)

Here, for a CLS policy to protect against UPDATEs, you would use a row-level security (RLS) policy that retrieves the old row and compares if your protected column's field value would change from the old to the new row. A solution candidate for this has been posted as a Stack Overflow answer, but this still has to be made into a generic function.

At first glance, this seems better than a trigger: it shares its advantages and in addition, Supabase promotes the use of RLS policies for access control anyway, and has much better UI support for RLS than for triggers. So it would improve consistency and maintainability of the database by reducing complexity.

However, the Supabase RLS editor cannot be used for complex RLS policies (issue report), so as a workaround one should wrap all RLS code into a single or nested function call, or at least something no longer than one line of code. Even better is to maintain the SQL source code under version control outside of Supabase, and to copy-and-paste it into the Supabase SQL Editor whenever you want to change a RLS policy, table, function and so on.

Option 2: Triggers

See here for my original instructions, and then the improved instructions by Github user christophemarois.

Advantages:

  • Does not add another table or view, so that the database structure is determined by the data, as it should be, and not by permission system quirks.

  • Does not require changes to the default Supabase permissions or table-to-schema assignments.

  • Combined the powers of RLS policies and column-level permissions.

Disadvantages:

  • Triggers are not yet supported well in the Supabase UI: only the trigger status can be changed, but it cannot be shown or edited in the UI, only in the PostgreSQL console. In practice, this is not much of an issue, as for any real-life project you will have to work with the PostgreSQL database directly, anyway.

  • It requires knowledge of PGSQL or another programming language … and for some, programming is what we want to avoid with Supabase. However, the solution uses one abstract function that receives columns to whitelist ("changes allowed"), so no real programming is necessary, just deploying some re-usable code.

Option 3: Privileged Functions

"You can hide the table behind a FUNCTION with SECURITY DEFINER. The table itself would not provide UPDATE access, instead users can only update the table through the FUNCTION." (source)

And in that function, you can determine column-level access permissions in any way you like. Any such function in schema public is automatically available through the API:

"write PostgreSQL SQL functions […] and call them via supabase.rpc('function_name', {param1: 'value'});." (source).

The issue is, however, that the API then no longer has a unified structure of "everything is available in tables".

Option 4: User-specific views

See the instructions. More instructions:

"You can create a view to only show the columns you want, make sure you secure with a WHERE statement as it ignores RLS (normally), and then use RLS to block the original table." (source)

This solution has been recommended by a Supabase maintainer. In total, RLS policies and triggers seem preferable, though.

To make this solution secure, you have to use option security_barrier = on (details), which can severely impact view performance. The only way around that is to not use a WHERE clause and instead to re-use RLS policies of the base table via security_invoker = on. That requires moving the base table to a custom database scheme that is not exposed by API (see below).

Advantages:

  • Simple. Views are just like tables, and everyone knows PostgreSQL tables – in contrast to triggers or (complex) RLS policies.

  • You see what you edit. Users (or their applications) who can see records in the table do not have to worry if they are editable due to RLS policies. Whatever a user can see, they can edit.

  • ❓ Extendable as needed. (Still unsure about this.) Only the columns a certain user is allowed to edit can be provided in the view. To find the right column, sometimes more context is needed. Not a problem: join the view and columns from the underlaying base table again as needed, at API access time. Only the surrogate primary key column id needs to be always included into the view; this is not an issue: if a user tries to edit it, it can only succeed when using new values, in which case effectively a new record is created, which the user is probably allowed to do anyway. (To be confirmed that updates with proper access protection are then still possible.)

Disadvantages:

  • Cluttering the table space. Ideally, the API would expose the data in the form they have in a proper database design. By exposing additional views, the API becomes unnecessarily complex.

  • Can not really reuse RLS policies of underlaying table. To be done by using security_invoker = on when creating the view (details). However, when doing this, the same user that can, say, update a record through the view can then also update that record in the base table, circumventing the column access restrictions for which the view is used. The only way around that would be to move the base table to a custom database scheme that is not exposed by API. That is possible, but adds yet more structural complexity.

  • Needs changes to the default view permissions. Since these are simple views, they are "updateable" views in PostgreSQL. Together with the default table-level / view-level permissions in Supabase schema public this means that all users, even anonymous ones, can delete records from these views, leading to the deletion of records in the underlaying tables.

    To fix this, one has to remove the INSERT and DELETE privileges from the view. This is a change to the default Supabase permissions that would ideally not be necessary.

    There is an alternative solution, but it is not very practical: you can create the views with security_invoker = on to reuse the RLS policies of the underlaying table. Then use these RLS policies to prevent record deletion. However, they have to allow SELECT and UPDATE; so unless you move the underlaying table to a schema not exposed by API, it would allow users to circumvent the column-level security for which the views were created.

  • No good way to restrict the use of certain values in a column to certain users. This is because views cannot have their own RLS policies. There are several ways to work around this:

    • Probably the best way to work around that is to structure tables so that a user with write access to a column is allowed to use every value in that column. For example, instead of columns role (user, admin) and status (applied, approved, disapproved), there would be nullable boolean columns user_application, admin_application, user_status, admin_status.

    • Another option, for complex cases, is to move the underlying table to a custom schema that is not API accessible (while still granting USAGE and permissions to all Supabase roles; see), to create RLS policies on that underlying table, and to re-use them in the views via security_invoker = on.

    • Another option, also for complex cases, is to use triggers on the view or the underlaying table.

Option 5: Column-level access rights

"You can provide UPDATE access to only a subset of columns of the table: GRANT UPDATE(col1, col2). (details)" (source)

Reportedly, it is a hassle to maintain all these access rights. And in Supabase, it would not be possible to give different permissions to different authenticated users, as all of them access the database using the same role authenticated. When working on the PostgREST level, you could have different options here, though.

Option 6: Table Splitting

Compared to views, this splits the main table into multiple parts. Using RLS policies, it is defined who can do what with each partial table; and, different from views where you can only partially emulate RLS policies in a WHERE clause, a RLS policy can also be used to limit which values a user can use for a column. To use them together, they have to be joined in requests.

Quite ok when splitting a table in two. But sometimes the splitting is almost "one table per column", for example for permission management tables with one column per role. This is bad because it "atomizes" the data rather than keeping it in a proper normal form, meaning that the data is not even accessible to admins in a comfortable way. That can be solved with views that combine the split-off tables again and provide write access to these underlaying tables. But still, a lot of tables to deal with. It's "ugly".

回首观望 2025-02-18 01:55:46

在PostgreSQL中,您可以通过 Grant 和/或 devoke 语句指定列级权限。

这里棘手的部分是,这些权限是针对PostgreSQL用户/角色而不是针对您的应用程序用户设置的。因此,您需要确保对Supabase使用来执行客户端请求的所有用户设置权限。据我所知,supabase使用 anon 认证 postgresql角色执行请求,但是,我没有官方文档其他任何人。

You can read more about how to utilize this technique

In PostgreSQL, you can specify column-level permissions via GRANT and/or REVOKE statements.

The tricky part here is that these permissions are set against PostgreSQL users/roles, NOT against your app users. So you need to ensure that the permissions are set against all users that Supabase uses to execute client requests. As far as I know, Supabase uses the anon and authenticated PostgreSQL roles to execute requests, however, there is no official documentation on this so I am not 100% sure there aren't any others.

You can read more about how to utilize this technique here (see the section called Column-level permissions).

你げ笑在眉眼 2025-02-18 01:55:46

我花了几天的时间调查supabase并咨询了几个不会导致最佳解决方案的线程,直到我发现一个可以很好地适合RLS(行级别安全)安全策略的线程。

我的表报价我需要限制更新以下字段: id,likes,user_id

我的适配器 so

CREATE OR REPLACE FUNCTION is_restricted_cls(
    _id BIGINT,
    _restricted_field_b BIGINT,
    _restricted_field_c UUID
) RETURNS BOOLEAN AS
$
WITH original_row AS (
    SELECT likes, user_id
    FROM quotes
    WHERE quotes.id = _id
)
SELECT (
    (SELECT likes FROM original_row) IS NOT DISTINCT FROM _restricted_field_b AND
    (SELECT user_id FROM original_row) IS NOT DISTINCT FROM _restricted_field_c
)
$ LANGUAGE SQL SECURITY DEFINER;

进入 oringal_row 将您的字段添加到 select> select()所有比较

将RLS策略分配到表

在本节中更新字段的安全策略的示例在使用和检查中,

((auth.uid() = user_id) AND is_restricted_cls(id, likes, user_id))

您可以根据您的要求修改列名和比较条件来调整IS_RENTERDIC_CLS函数以适合您的特定目的。

I spent a few days investigating supabase and consulting several threads that do not lead to an optimal solution, until I found one that can fit quite well with rls (row level security) security policies.

my table quotes I need restrict update this fields: id, likes, user_id

My adapter for this SO

CREATE OR REPLACE FUNCTION is_restricted_cls(
    _id BIGINT,
    _restricted_field_b BIGINT,
    _restricted_field_c UUID
) RETURNS BOOLEAN AS
$
WITH original_row AS (
    SELECT likes, user_id
    FROM quotes
    WHERE quotes.id = _id
)
SELECT (
    (SELECT likes FROM original_row) IS NOT DISTINCT FROM _restricted_field_b AND
    (SELECT user_id FROM original_row) IS NOT DISTINCT FROM _restricted_field_c
)
$ LANGUAGE SQL SECURITY DEFINER;

into oringal_row adding you fields and into SELECT () all comparations

Assign RLS policy into table

Example of security policy on updating fields, in the section in use and the check

((auth.uid() = user_id) AND is_restricted_cls(id, likes, user_id))

You can adapt the is_restricted_cls function to suit your specific purpose by modifying the column names and the comparison conditions according to your requirements.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文