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

Merged
merged 7 commits into from
May 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
`ON CONFLICT ... DO NOTHING`: CockroachDB does not run the constraint and row-level policy checks on the `VALUES` clause if the candidate row has a conflict. [#35370](https://github.com/cockroachdb/cockroach/issues/35370).
6 changes: 6 additions & 0 deletions src/current/_includes/v25.2/sidebar-data/reference.json
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,12 @@
"/${VERSION}/column-level-encryption.html"
]
},
{
"title": "Row-level Security",
"urls": [
"/${VERSION}/row-level-security.html"
]
},
{
"title": "PKI and TLS",
"urls": [
Expand Down
24 changes: 24 additions & 0 deletions src/current/_includes/v25.2/sidebar-data/sql.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,12 @@
"/${VERSION}/alter-partition.html"
]
},
{
"title": "<code>ALTER POLICY</code>",
"urls": [
"/${VERSION}/alter-policy.html"
]
},
{
"title": "<code>ALTER PROCEDURE</code>",
"urls": [
Expand Down Expand Up @@ -220,6 +226,12 @@
"/${VERSION}/create-logical-replication-stream.html"
]
},
{
"title": "<code>CREATE POLICY</code>",
"urls": [
"/${VERSION}/create-policy.html"
]
},
{
"title": "<code>CREATE PROCEDURE</code>",
"urls": [
Expand Down Expand Up @@ -340,6 +352,12 @@
"/${VERSION}/drop-owned-by.html"
]
},
{
"title": "<code>DROP POLICY</code>",
"urls": [
"/${VERSION}/drop-policy.html"
]
},
{
"title": "<code>DROP TRIGGER</code>",
"urls": [
Expand Down Expand Up @@ -694,6 +712,12 @@
"/${VERSION}/show-partitions.html"
]
},
{
"title": "<code>SHOW POLICIES</code>",
"urls": [
"/${VERSION}/show-policies.html"
]
},
{
"title": "<code>SHOW RANGES</code>",
"urls": [
Expand Down
1 change: 1 addition & 0 deletions src/current/_includes/v25.2/sql/privileges.md
Original file line number Diff line number Diff line change
Expand Up @@ -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` | 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.
`CANCELQUERY` | System | Grants the ability to cancel queries.
`CHANGEFEED` | Table | Grants the ability to create [changefeeds]({% link {{ page.version.version }}/change-data-capture-overview.md %}) on a table.
<a id="connect"></a>`CONNECT` | Database | Grants the ability to view a database's metadata, which consists of objects in a database's `information_schema` and `pg_catalog` system catalogs. This allows the role to view the database's table, schemas, user-defined types, and list the database when running `SHOW DATABASES`. The `CONNECT` privilege is also required to run backups of the database.
Expand Down
1 change: 1 addition & 0 deletions src/current/_includes/v25.2/sql/role-options.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
Role option | Description
------------|-------------
<a name="bypassrls"></a> `BYPASSRLS`/`NOBYPASSRLS` | **New in v25.2**: Allow or disallow a role to bypass [row-level security (RLS)]({% link {{ page.version.version }}/row-level-security.md %}) policies on a table. 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.
`CANCELQUERY`/`NOCANCELQUERY` | **Deprecated in v22.2: Use the `CANCELQUERY` [system privilege]({% link {{ page.version.version }}/security-reference/authorization.md %}#supported-privileges).** Allow or disallow a role to cancel [queries]({% link {{ page.version.version }}/cancel-query.md %}) and [sessions]({% link {{ page.version.version }}/cancel-session.md %}) of other roles. Without this role option, roles can only cancel their own queries and sessions. Even with the `CANCELQUERY` role option, non-`admin` roles cannot cancel `admin` queries or sessions. This option should usually be combined with `VIEWACTIVITY` so that the role can view other roles' query and session information. <br><br>By default, the role option is set to `NOCANCELQUERY` for all non-`admin` roles.
`CONTROLCHANGEFEED`/`NOCONTROLCHANGEFEED` | **Deprecated in v23.1: Use the `CHANGEFEED` [privilege]({% link {{ page.version.version }}/security-reference/authorization.md %}#supported-privileges).** Allow or disallow a role to run [`CREATE CHANGEFEED`]({% link {{ page.version.version }}/create-changefeed.md %}) on tables they have `SELECT` privileges on. <br><br>By default, the role option is set to `NOCONTROLCHANGEFEED` for all non-`admin` roles.
`CONTROLJOB`/`NOCONTROLJOB` | Allow or disallow a role to [pause]({% link {{ page.version.version }}/pause-job.md %}), [resume]({% link {{ page.version.version }}/resume-job.md %}), and [cancel]({% link {{ page.version.version }}/cancel-job.md %}) jobs. Non-`admin` roles cannot control jobs created by `admin` roles. <br><br>By default, the role option is set to `NOCONTROLJOB` for all non-`admin` roles.
Expand Down
3 changes: 3 additions & 0 deletions src/current/_includes/v25.2/sql/row-level-security-enabled.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{{site.data.alerts.callout_info}}
RLS applies to a table **only when explicitly enabled** using `ALTER TABLE ... ENABLE ROW LEVEL SECURITY`. Roles exempt from RLS policies include [admins]({% link {{ page.version.version }}/security-reference/authorization.md %}#roles), [table owners]({% link {{ page.version.version }}/security-reference/authorization.md %}#object-ownership) (unless the table is set to [`FORCE ROW LEVEL SECURITY`](#force-row-level-security)), and [roles with `BYPASSRLS`]({% link {{ page.version.version }}/alter-role.md %}#allow-a-role-to-bypass-row-level-security-rls).
{{site.data.alerts.end}}
124 changes: 124 additions & 0 deletions src/current/v25.2/alter-policy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
---
title: ALTER POLICY
summary: The ALTER POLICY statement changes an existing row-level security (RLS) policy on a table.
toc: true
keywords: security, row level security, RLS
docs_area: reference.sql
---

The `ALTER POLICY` statement changes an existing [row-level security (RLS)]({% link {{ page.version.version }}/row-level-security.md %}) policy on a table.

Allowed changes to a policy using `ALTER POLICY` include:

- Rename the policy.
- Change the applicable [roles]({% link {{ page.version.version }}/security-reference/authorization.md %}#roles).
- Modify the [`USING` expression](#parameters).
- Modify the [`WITH CHECK` expression](#parameters).

{{site.data.alerts.callout_info}}
You cannot use `ALTER POLICY` to change the `PERMISSIVE`, `RESTRICTIVE`, or `FOR` clauses of a policy, as defined in `CREATE POLICY ... ON ... ( PERMISSIVE | RESTRICTIVE ) ... FOR ( ALL | SELECT | ... )`. To make these changes, drop the policy with [`DROP POLICY`]({% link {{ page.version.version }}/drop-policy.md %}) and issue a new [`CREATE POLICY`]({% link {{ page.version.version }}/create-policy.md %}) statement.
{{site.data.alerts.end}}

## Syntax

<!--

NB. This was waiting on a fix to DOC-12125 when this doc was being
written. Now there is additional followup work (tracked in DOC-13653)
to update the parameters and potentially the diagram.

<div>
{% remote_include https://raw.githubusercontent.com/cockroachdb/generated-diagrams/{{ page.release_info.crdb_branch_name }}/grammar_svg/alter_policy.html %}
</div>

-->

{% include_cached copy-clipboard.html %}
~~~
ALTER POLICY policy_name ON table_name RENAME TO new_policy_name;

ALTER POLICY policy_name ON table_name
[ TO ( role_name | PUBLIC | CURRENT_USER | SESSION_USER ) [, ...] ]
[ USING ( using_expression ) ]
[ WITH CHECK ( check_expression ) ];
~~~

## Parameters

Parameter | Description
----------|------------
`policy_name` | The identifier of the existing policy to be modified. Must be unique for the specified `table_name`.
`ON table_name` | The name of the table on which the policy `policy_name` is defined.
`new_policy_name` | The new identifier for the policy. The `new_policy_name` must be a unique name on `table_name`.
`TO (role_name | PUBLIC | CURRENT_USER | SESSION_USER) [, ...]` | Specifies the database [role(s)]({% link {{ page.version.version }}/security-reference/authorization.md %}#roles) to which the altered policy applies. These role(s) replace the existing set of roles for the policy. `PUBLIC` refers to all roles. `CURRENT_USER` and `SESSION_USER` refer to the current execution context's user (also available via [functions]({% link {{ page.version.version }}/functions-and-operators.md %}) `current_user()` and `session_user()`).
`USING ( using_expression )` | Replaces the previous value of this expression. For details about this expression, refer to [`CREATE POLICY`]({% link {{ page.version.version }}/create-policy.md %}#parameters).
`WITH CHECK ( check_expression )` | Replaces the previous value of this expression. For details about this expression, refer to [`CREATE POLICY`]({% link {{ page.version.version }}/create-policy.md %}#parameters).

## Example

In this example, you will start by only allowing users to see or modify their own rows in an `orders` table. Then, as the schema is updated due to business requirements, you will refine the policy to take into account the new requirements.

{% include_cached copy-clipboard.html %}
~~~ sql
CREATE TABLE orders (user_id TEXT PRIMARY KEY, order_details TEXT);
~~~

The original policy on the table was as follows:

{% include_cached copy-clipboard.html %}
~~~ sql
CREATE POLICY user_orders_policy ON orders
FOR ALL
TO PUBLIC
USING ( user_id = CURRENT_USER )
WITH CHECK ( user_id = CURRENT_USER );
~~~

However, the `orders` table schema will be updated to include an `is_archived` flag, and the initial policy will need refinement.

{% include_cached copy-clipboard.html %}
~~~ sql
-- Assume this change was made after the initial policy was created
ALTER TABLE orders ADD COLUMN is_archived BOOLEAN DEFAULT FALSE NOT NULL;
CREATE INDEX idx_orders_user_id_is_archived ON orders(user_id, is_archived); -- For performance
~~~

The policy requirements have changed as follows:

1. The policy should now only apply to users belonging to the `customer_service` role, not `PUBLIC`.
1. Users in `customer_service` should only be able to view and modify orders that are **not** archived (`is_archived = FALSE`). Archived orders should be invisible/immutable via this policy.

This assumes the `customer_service` role has been created:

{% include_cached copy-clipboard.html %}
~~~ sql
CREATE ROLE customer_service;
~~~

This leads to the following `ALTER POLICY` statement:

{% include_cached copy-clipboard.html %}
~~~ sql
ALTER POLICY user_orders_policy ON orders
TO customer_service
USING ( user_id = CURRENT_USER AND is_archived = FALSE )
WITH CHECK ( user_id = CURRENT_USER AND is_archived = FALSE );
~~~

The changes to the `ALTER POLICY` statement can be explained as follows:

- `TO customer_service`: Restricts the policy's application from all users (`PUBLIC`) to only those who are members of the `customer_service` role. Other users will no longer be affected by this specific policy (they would need other applicable policies or RLS would deny access by default).
- `USING ( user_id = CURRENT_USER AND is_archived = FALSE )`: Modifies the visibility rule. Now, `customer_service` users can only see rows that match their `user_id` *and* are not archived.
- `WITH CHECK ( user_id = CURRENT_USER AND is_archived = FALSE )`: Modifies the constraint for `INSERT`/`UPDATE`. Users attempting modifications must match the `user_id`, and the resulting row must not be archived. This prevents the user from inserting archived orders or updating an order to set `is_archived = TRUE` via operations governed by this policy.

The preceding `ALTER POLICY` statement represents a typical use case: it refines role targeting and adapts the policy logic to accommodate schema changes and evolving access control requirements.

## See also

- [Row-level security (RLS) overview]({% link {{ page.version.version }}/row-level-security.md %})
- [`CREATE POLICY`]({% link {{ page.version.version }}/create-policy.md %})
- [`DROP POLICY`]({% link {{ page.version.version }}/drop-policy.md %})
- [`SHOW POLICIES`]({% link {{ page.version.version }}/show-policies.md %})
- [`ALTER TABLE ... ENABLE ROW LEVEL SECURITY`]({% link {{ page.version.version }}/alter-table.md %}#enable-row-level-security)
- [`ALTER ROLE ... WITH BYPASSRLS`]({% link {{ page.version.version }}/alter-role.md %}#allow-a-role-to-bypass-row-level-security-rls)
- [`CREATE ROLE ... WITH BYPASSRLS`]({% link {{ page.version.version }}/create-role.md %}#create-a-role-that-can-bypass-row-level-security-rls)
Loading
Loading