ClickHouse / ClickHouse/ClickHouse
HTTP session does not pick up newly granted nested role privileges until reconnect
- Dominant language
- C++
- Stars
- 49.9k
- Forks
- 9k
- Avg merge
- 21h 32m
- Merged PRs (30d)
- 515
Description
### Company or project name
Local reproducible test case
### Describe the bug
An existing HTTP session does not pick up privileges from a role granted to the user after the session was opened.
If the user opens an HTTP session first, then an administrator grants a role to that user, the same HTTP session still uses the old active role set and fails access checks. A new HTTP session immediately sees the newly granted role and the same query succeeds.
This is especially visible with `clusterAllReplicas(..., system.tables)` and a subquery reading an ordinary table. The old session fails with `ACCESS_DENIED` for the table used in the subquery, even though the role granted after session creation provides that privilege through a nested role.
Setting `push_external_roles_in_interserver_queries=0` does not help this scenario because the failure happens before remote execution: the current session does not refresh its active roles/privileges.
### How to reproduce
Tested on ClickHouse `26.7.3.19` official build.
Minimal topology: multi-replica cluster, HTTP interface enabled, access management enabled.
Create test objects and nested roles:
```sql
CREATE DATABASE role_cache_repro ON CLUSTER 'test_cluster';
CREATE TABLE role_cache_repro.test_table ON CLUSTER 'test_cluster'
(
name String
)
ENGINE = ReplicatedMergeTree('/clickhouse/tables/{uuid}/{shard}', '{replica}')
ORDER BY name;
INSERT INTO role_cache_repro.test_table VALUES ('role_cache_repro');
CREATE ROLE role_cache_main;
CREATE ROLE role_cache_subrole;
GRANT REMOTE ON *.* TO role_cache_subrole;
GRANT SELECT ON system.tables TO role_cache_subrole;
GRANT SELECT ON role_cache_repro.* TO role_cache_subrole;
GRANT role_cache_subrole TO role_cache_main;
CREATE USER role_cache_user IDENTIFIED BY 'password';
```
Open an HTTP session before granting the role:
```bash
curl -u role_cache_user:password \
'http://localhost:8123/?session_id=role-cache-session' \
--data-binary "SELECT currentUser(), currentRoles()"
```
Grant the role after the session already exists:
```sql
GRANT role_cache_main TO role_cache_user;
```
In the same HTTP session, roles are still stale:
```bash
curl -u role_cache_user:password \
'http://localhost:8123/?session_id=role-cache-session' \
--data-binary "SELECT currentUser(), currentRoles()"
```
Run the query in the same old HTTP session:
```bash
curl -u role_cache_user:password \
'http://localhost:8123/?session_id=role-cache-session' \
--data-binary "
SELECT
hostName(),
t.database,
t.name
FROM clusterAllReplicas('test_cluster', system.tables) AS t
WHERE t.database IN
(
SELECT name
FROM role_cache_repro.test_table
)
FORMAT TSVRaw"
```
Actual result in the old session:
```text
Code: 497. DB::Exception: role_cache_user: Not enough privileges. To execute this query, it's necessary to have the grant SELECT(name) ON role_cache_repro.test_table. (ACCESS_DENIED)
```
Open a new HTTP session and run the same query:
```bash
curl -u role_cache_user:password \
'http://localhost:8123/?session_id=role-cache-session-new' \
--data-binary "
SELECT
hostName(),
t.database,
t.name
FROM clusterAllReplicas('test_cluster', system.tables) AS t
WHERE t.database IN
(
SELECT name
FROM role_cache_repro.test_table
)
FORMAT TSVRaw"
```
The new session succeeds and returns rows from all replicas.
### Expected behavior
After a role is granted to a user, existing HTTP sessions should either:
1. refresh the active role/privilege set before subsequent queries, or
2. clearly document that HTTP sessions cache role grants until reconnect/session expiration.
The behavior is surprising because reconnecting immediately fixes the access check without any other access-control changes.
### Error message and/or stacktrace
From `system.query_log` for the old session:
```text
type: ExceptionBeforeStart
exception_code: 497
exception: Code: 497. DB::Exception: role_cache_user: Not enough privileges. To execute this query, it's necessary to have the grant SELECT(name) ON role_cache_repro.test_table. (ACCESS_DENIED)
used_privileges: ['SELECT ON system.tables','SHOW COLUMNS ON system.tables','READ ON REMOTE']
missing_privileges: ['SELECT(name) ON role_cache_repro.test_table']
```
For the new session, the same query finishes successfully and `used_privileges` includes:
```text
SELECT(name) ON role_cache_repro.test_table
SELECT(database, name) ON system.tables
READ ON REMOTE
```
### Additional context
The same result was observed with nested roles:
```sql
GRANT role_cache_subrole TO role_cache_main;
GRANT role_cache_main TO role_cache_user;
```
The issue is not fixed by adding:
```sql
SETTINGS push_external_roles_in_interserver_queries = 0
```
because the old-session failure is `ExceptionBeforeStart` on the initiator, before remote execution.
Contributor guide
Research direction
Start with the HTTP interface reproduction using the documented session_id requests, then inspect system.query_log for the old session's ExceptionBeforeStart and missing privileges. The payload names no source files or tests; done means an existing session reflects the newly granted nested role, or the caching behavior is clearly documented.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp, sql
- Domain
- authorization, backend, databases
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100