Skip to content
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
15 changes: 15 additions & 0 deletions doc/developer/guide-adapter.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,21 @@ The current implementation rejects bounded-staleness queries whose timeline
is not `EpochMilliseconds`, in `determine_timestamp_for_inner`. The freshness
math is currently scoped to that timeline.

### System-session replanning does not grant authority

Some DDL paths reconstruct and mutate a stored definition by replanning it with
a system session. The initial authorization check only sees dependencies in the
submitted statement, so it cannot authorize retained dependencies discovered
during replanning. Before reading secrets, performing external I/O, or
persisting the result, authorize the final dependency set against the invoking
session. Check the final set rather than the union of old and new dependencies,
so a caller can remove a dependency they are no longer authorized to use.

This rule applies when reconstructing or mutating a definition. Executing a
fixed connection does not authorize its dependencies separately. For example,
standalone `VALIDATE CONNECTION` is delegated by `USAGE` on the connection and
its containing schema, without requiring `USAGE` on referenced secrets.

### The catalog is the source of truth for state that gets rebuilt from it

If a reconcile or refresh path rebuilds downstream state (for example a
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@
headless: true
---
- Ownership of the connection.
- In addition, to set, reset, or drop connection options:
- `USAGE` privileges on all connections and secrets referenced by the
resulting connection definition.
- `USAGE` privileges on the schemas that contain those connections and
secrets.
- In addition, to change owners:
- Role membership in `new_owner`.
- `CREATE` privileges on the containing schema if the connection is namespaced
Expand Down
16 changes: 16 additions & 0 deletions src/adapter/src/coord/sequencer/inner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3793,6 +3793,22 @@ impl Coordinator {
}
};

// Replanning uses a system session and discovers retained dependencies
// that `check_plan` could not authorize. Check them before secret guards
// or validation can resolve a secret.
let usage_check = {
let catalog = self.catalog().for_session(ctx.session());
rbac::check_usage(
&catalog,
ctx.session(),
&conn.resolved_ids,
&rbac::CREATE_ITEM_USAGE,
)
};
if let Err(err) = usage_check {
return ctx.retire(Err(err.into()));
}

// `conn` is the whole re-planned connection, so this also rejects a
// stored value the statement did not touch.
if let Err(err) = check_connection_details(&conn.details) {
Expand Down
64 changes: 64 additions & 0 deletions test/sqllogictest/privilege_checks.slt
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,70 @@ REVOKE CREATE ON SCHEMA materialize.public FROM joe;
----
COMPLETE 0

# ALTER CONNECTION must authorize secret dependencies retained from the stored
# definition, not only dependencies named in the ALTER statement.

simple conn=mz_system,user=mz_system
CREATE SECRET alter_conn_secret AS 'secret';
----
COMPLETE 0

simple conn=mz_system,user=mz_system
CREATE CONNECTION alter_conn TO CONFLUENT SCHEMA REGISTRY (URL 'https://example.com', USERNAME 'user', PASSWORD SECRET alter_conn_secret) WITH (VALIDATE = false);
----
COMPLETE 0

simple conn=mz_system,user=mz_system
ALTER CONNECTION alter_conn OWNER TO joe;
----
COMPLETE 0

simple conn=mz_system,user=mz_system
GRANT USAGE ON SCHEMA materialize.public TO joe;
----
COMPLETE 0

simple conn=joe,user=joe
ALTER CONNECTION alter_conn SET (URL 'https://example.net') WITH (VALIDATE = false);
----
db error: ERROR: permission denied for SECRET "materialize.public.alter_conn_secret"
DETAIL: The 'joe' role needs USAGE privileges on SECRET "materialize.public.alter_conn_secret"

simple conn=mz_system,user=mz_system
GRANT USAGE ON SECRET alter_conn_secret TO joe;
----
COMPLETE 0

simple conn=joe,user=joe
ALTER CONNECTION alter_conn SET (URL 'https://example.net') WITH (VALIDATE = false);
----
COMPLETE 0

simple conn=mz_system,user=mz_system
REVOKE USAGE ON SECRET alter_conn_secret FROM joe;
----
COMPLETE 0

simple conn=joe,user=joe
ALTER CONNECTION alter_conn RESET (USERNAME), RESET (PASSWORD) WITH (VALIDATE = false);
----
COMPLETE 0

simple conn=mz_system,user=mz_system
DROP CONNECTION alter_conn;
----
COMPLETE 0

simple conn=mz_system,user=mz_system
DROP SECRET alter_conn_secret;
----
COMPLETE 0

simple conn=mz_system,user=mz_system
REVOKE USAGE ON SCHEMA materialize.public FROM joe;
----
COMPLETE 0

# CREATE DATABASE

simple conn=joe,user=joe
Expand Down
22 changes: 21 additions & 1 deletion test/testdrive/connection-validation.td
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
$ skip-consistency-checks reason="workflow uses SSH keys which we currently can't check"

$ postgres-execute connection=postgres://mz_system:materialize@${testdrive.materialize-internal-sql-addr}
ALTER SYSTEM SET enable_connection_validation_syntax = true
ALTER SYSTEM SET enable_connection_validation_syntax = true;
ALTER SYSTEM SET enable_rbac_checks = true;

> CREATE CONNECTION kafka_conn TO KAFKA (BROKER '${testdrive.kafka-addr}', SECURITY PROTOCOL PLAINTEXT)

Expand Down Expand Up @@ -44,3 +45,22 @@ contains:failed to lookup address information

! VALIDATE CONNECTION invalid_kafka_conn
contains:failed to lookup address information

# Altering options authorizes the resulting definition, while validating an
# unchanged connection delegates access to its dependencies.

$ postgres-execute connection=mz_system
CREATE ROLE connection_validator_${testdrive.seed} LOGIN PASSWORD 'validatorpass';
CREATE SECRET alter_connection_password AS 'password';
CREATE CONNECTION secret_csr_conn TO CONFLUENT SCHEMA REGISTRY (URL '${testdrive.schema-registry-url}', USERNAME 'user', PASSWORD SECRET alter_connection_password) WITH (VALIDATE = true);
ALTER CONNECTION secret_csr_conn OWNER TO materialize;
GRANT USAGE ON SCHEMA materialize.public TO materialize, connection_validator_${testdrive.seed};
GRANT USAGE ON CONNECTION secret_csr_conn TO connection_validator_${testdrive.seed};

! ALTER CONNECTION secret_csr_conn SET (URL '${testdrive.schema-registry-url}') WITH (VALIDATE = true)
contains:permission denied for SECRET "materialize.public.alter_connection_password"

$ postgres-connect name=connection_validator url=postgres://connection_validator_${testdrive.seed}:validatorpass@${testdrive.materialize-sql-addr}

$ postgres-execute connection=connection_validator
VALIDATE CONNECTION secret_csr_conn
Loading