Skip to content

Add docs for Row-level security (RLS) #19527

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

rmloveland
Copy link
Contributor

@rmloveland rmloveland commented Apr 14, 2025

Fixes DOC-10439, DOC-12948

Summary of changes:

  • Add 'Row-level security' overview page
  • Add SQL statement docs for:
    • CREATE POLICY
    • ALTER POLICY
    • DROP POLICY
    • SHOW POLICIES
    • ALTER TABLE {ENABLE,DISABLE} ROW LEVEL SECURITY
    • ALTER TABLE {FORCE,UNFORCE} ROW LEVEL SECURITY
    • CREATE ROLE ... WITH BYPASSRLS
    • ALTER ROLE ... WITH BYPASSRLS

@rmloveland rmloveland marked this pull request as draft April 14, 2025 21:05
Copy link

netlify bot commented Apr 14, 2025

Deploy Preview for cockroachdb-interactivetutorials-docs canceled.

Name Link
🔨 Latest commit faa5864
🔍 Latest deploy log https://app.netlify.com/sites/cockroachdb-interactivetutorials-docs/deploys/6822186133a824000878dca9

Copy link

netlify bot commented Apr 14, 2025

Deploy Preview for cockroachdb-api-docs canceled.

Name Link
🔨 Latest commit faa5864
🔍 Latest deploy log https://app.netlify.com/sites/cockroachdb-api-docs/deploys/68221861e432c3000838c0fa

Copy link

netlify bot commented Apr 14, 2025

Deploy Preview for cockroachdb-docs failed. Why did it fail? →

Name Link
🔨 Latest commit faa5864
🔍 Latest deploy log https://app.netlify.com/sites/cockroachdb-docs/deploys/682218611503cf0008c3faf9

@rmloveland rmloveland force-pushed the 20250414-DOC-10439-row-level-security branch 7 times, most recently from dc16ae3 to 2cb3af9 Compare April 23, 2025 15:50
@rmloveland rmloveland force-pushed the 20250414-DOC-10439-row-level-security branch 9 times, most recently from 56780c5 to f07bb90 Compare May 1, 2025 14:32
@rmloveland rmloveland force-pushed the 20250414-DOC-10439-row-level-security branch 4 times, most recently from 181509f to 0046485 Compare May 5, 2025 19:08
@rmloveland rmloveland marked this pull request as ready for review May 5, 2025 19:30
Fixes DOC-10439, DOC-12948

Summary of changes:

- Add 'Row-level security' overview page
- Add or update SQL statement docs for:
  - `CREATE POLICY`
  - `ALTER POLICY`
  - `DROP POLICY`
  - `SHOW POLICIES`
  - `ALTER TABLE {ENABLE,DISABLE} ROW LEVEL SECURITY`
  - `ALTER TABLE {FORCE,UNFORCE} ROW LEVEL SECURITY`
  - `CREATE ROLE ... WITH BYPASSRLS`
  - `ALTER ROLE ... WITH BYPASSRLS`
@rmloveland rmloveland force-pushed the 20250414-DOC-10439-row-level-security branch from 0046485 to 20ddc91 Compare May 5, 2025 19:32
@rmloveland rmloveland requested review from spilchen and rafiss May 5, 2025 19:32
@rmloveland rmloveland requested a review from dikshant May 5, 2025 19:32
@@ -2,6 +2,7 @@ Privilege | Levels | Description
----------|--------|------------
`ALL` | System, Database, Schema, Table, Sequence, Type | For the object to which `ALL` is applied, grants all privileges at the system, database, schema, table, sequence, or type level.
`BACKUP` | System, Database, Table | Grants the ability to create [backups]({% link {{ page.version.version }}/backup-and-restore-overview.md %}) at the system, database, or table level.
<a name="bypassrls"></a> `BYPASSRLS` | **New in v25.2** Grants the ability for a role to bypass [row-level security (RLS)]({% link {{ page.version.version }}/row-level-security.md %}) policies on a table using the `BYPASSRLS`/`NOBYPASSRLS` role options, granting unrestricted read and write access to all rows.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We talk about BYPASSRLS as a role option. Yet, this is the page for system privileges. I treat them separately from role options.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok thanks for clearing up my confusion there; removed this from the system privs and it's now only in the role options

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I want to clarify that BYPASSRLS can be used as a system privilege. What tripped me up was referring to it as a role option. I think we should include it on the privileges page, but with slightly adjusted wording to reflect this distinction.


### Replace a Policy

This example demonstrates dropping an existing policy before replacing it with a new one that has a different fundamental behavior (e.g., changing from `PERMISSIVE` to `RESTRICTIVE`), which cannot be done using [`ALTER POLICY`]({% link {{ page.version.version }}/alter-policy.md %}).
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like this is an overly complicated example. I wouldn't expect changing the type from PERMISSIVE to RESTRICTIVE (or vice-versa) be a common occurrence that it requires an example. Can we have a simpler example here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated to just be a single, simple DROP POLICY example stmt


{% include_cached copy-clipboard.html %}
~~~ sql
SELECT rolname FROM pg_roles WHERE rolbypassrls = true
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wasn't aware of this column in pg_roles. Currently, it always returns false. Even if the role has the BYPASSRLS option. I'm glad you included this example. I have opened issue cockroachdb/cockroach#146228

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, glad it was useful. Do you think we should remove this for now and add back in after the issue is addressed?


{% include_cached copy-clipboard.html %}
~~~ sql
SELECT rolname FROM pg_roles WHERE rolbypassrls = true
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar comment here about bug cockroachdb/cockroach#146228.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same q re: should we remove for now?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, let's remove it. When we fix it, I'll make sure there is a release note included with that change so we can remember to add this back.

@rmloveland rmloveland requested a review from spilchen May 9, 2025 17:32
@rmloveland
Copy link
Contributor Author

thanks for the review @spilchen, i've tried to address your feedback, this is RFAL - changes are in latest commit Update with spilchen feedback (1)

Copy link

@spilchen spilchen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is looking good.


{% include_cached copy-clipboard.html %}
~~~ sql
SELECT rolname FROM pg_roles WHERE rolbypassrls = true
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, let's remove it. When we fix it, I'll make sure there is a release note included with that change so we can remember to add this back.

For examples, see [Force and unforce row-level security](#force-and-unforce-row-level-security).

{{site.data.alerts.callout_danger}}
Users with the `BYPASSRLS` [role option]({% link {{ page.version.version }}/security-reference/authorization.md %}#role-options) can still bypass RLS even when `ALTER TABLE ... FORCE ROW LEVEL SECURITY` is enabled. To see the roles with this option, run: `SELECT rolname FROM pg_roles WHERE rolbypassrls = true;`.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably leave the last bit about looking up in pg_roles until we fix that issue.


When [views]({% link {{ page.version.version }}/views.md %}) are accessed, RLS policies on any underlying [tables]({% link {{ page.version.version }}/schema-design-table.md %}) are applied. [Policies]({% link {{ page.version.version }}/create-policy.md %}) can also be defined directly on views.

Views use the [role]({% link {{ page.version.version }}/security-reference/authorization.md %}#roles) of the view owner to determine row-level security filters, **not** the role of the user executing the view. This can cause issues because the view owner may have entirely different policies than the user executing the view.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, the design document is wrong sorry. This found out late in the cycle. We tried to capture the current behaviour here: cockroachdb/cockroach#138918 (comment)

TO public
USING (tenant_id = split_part(current_setting('application_name', true), '.', 2)::UUID);

CREATE POLICY tenant_isolation ON invoices
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’m not quite sure what the second policy is intended to show. At first glance, the expressions in both policies appear to be identical. While tenant_isolation_permissive doesn’t include an explicit WITH CHECK, it will still enforce the USING expression for INSERT and UPDATE, and that expression is the same as the WITH CHECK in the other policy.

The only notable difference is the role: one policy is for public, and the other is for app_dev. However, in the example, only the app_dev role is used—so we never really see a scenario where only the first policy applies.

If the goal is to demonstrate how multiple policies with different roles interact, a more illustrative example might involve a role that can read all tenants' data but cannot modify it. In contrast, the app_dev role would enforce tenant-level isolation for both reads and writes. Then, if a user had both roles—say, two users from app_dev and one of them also has this broader-access role—we could observe the combined effect of overlapping policies in action. This may still be a contrived example.

Another idea is to use the role for public to only show results for a default tenant. That way if the application_name is omitted, we still see results.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

went with the latter suggestion b/c it seemed simpler. here is what i updated to, PTAL

CREATE POLICY tenant_isolation_permissive ON invoices
    AS PERMISSIVE
    FOR ALL
    TO public
    USING (tenant_id = COALESCE(
        -- Attempt to extract UUID after the last '.' in application_name
        -- Handle cases where setting is missing, empty, or has no '.'
        NULLIF(split_part(current_setting('application_name', true), '.', 2), '')::UUID,
        -- Default Tenant ID
        'ad7b4529-f5d6-4d0e-8b0f-91af455a70e5'
        )
    );

with the following extra explanation

- `AS PERMISSIVE`: Necessary because you need at least one permissive policy. The permissive policy above has logic to show results for a default tenant ID if the `application_name` is omitted or improperly formatted.

@@ -1,5 +1,6 @@
Role option | Description
------------|-------------
<a name="bypassrls"></a> `BYPASSRLS`/`NOBYPASSRLS` | **New in v25.2**: Grants the ability to bypass [row-level security (RLS)]({% link {{ page.version.version }}/row-level-security.md %}) policies on a table, granting unrestricted read and write access to all rows. This option controls the access from an RLS perspective only; the user also needs sufficient [`GRANT`]({% link {{ page.version.version }}/grant.md %}) privileges to read or write to the table.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should mention BYPASSRLS in system privileges. You had it in earlier that I commented on. But I think there was a misunderstanding of my comment, and the entire mention was removed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah my bad

went back and looked at your previous comment which was about us not talking about role options in the system privilege description. here's the updated version

BYPASSRLS | Table | New in v25.2 Grants the ability to bypass [row-level security (RLS)]({% link {{ page.version.version }}/row-level-security.md %}) policies on a table. This privilege controls the access from an RLS perspective only; the user also needs sufficient [GRANT]({% link {{ page.version.version }}/grant.md %}) privileges to read or write to the table.

@rmloveland rmloveland requested a review from taroface May 12, 2025 14:05
@rmloveland rmloveland requested a review from spilchen May 12, 2025 15:50
@rmloveland
Copy link
Contributor Author

thanks for the additional comments @spilchen , this is RFAL again!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants