Revoke the reader's grants as their own grantors and read them back, alter its role only on drift, and report definer functions and views (review of hq issue 337)
A superuser's REVOKE acts as the table owner, so a grant to the reader made by a role holding GRANT OPTION survived it while the report said revoked. Each grant is now revoked under SET ROLE to its grantor, the grants are read back, and anything still held is reported as a failure. The role was altered every minute with its password in the statement, which a server logging DDL would record and which could collide with a first query; it is now altered only when an attribute, a setting or the password has drifted, one ensure at a time. SECURITY DEFINER functions and views the reader may use reach data without a grant on it, so every pass lists them. The README says the name filter is a backstop and that the read-only session default can be turned off by a statement.
This commit is contained in:
@@ -47,8 +47,10 @@ for, so one removed by hand is installed again.
|
||||
- **`postgres_retire_database` renames, it does not drop**: the database becomes
|
||||
`<name>_deleted_<yyyymmdd>` and its owner is locked. Removing the data is a person's act, by hand.
|
||||
- **A caller's statement never runs as the superuser.** `postgres_query` and the seat's `query` verb
|
||||
run as `mesh_store_reader` — every session read-only by the server's own setting, a 60 s statement
|
||||
timeout — with the statement sent as given (issue 193). Without the reader's password
|
||||
run as `mesh_store_reader` — every session read-only by default, a 60 s statement timeout — with the
|
||||
statement sent as given (issue 193). The default is only a default: a statement may
|
||||
`SET transaction_read_only = off`. What stops the reader writing is that it holds no grant to write
|
||||
(and, beyond that, only the `TEMP` privilege PostgreSQL gives every login, for temporary tables). Without the reader's password
|
||||
(`own-secrets.reader`) the call is refused.
|
||||
- **The reader reads only what a module declares readable, and never a secret** (issue 337). It holds
|
||||
no role — until issue 337 it held `pg_read_all_data`, which let any caller of the seat's `query` read
|
||||
@@ -60,13 +62,25 @@ for, so one removed by hand is installed again.
|
||||
An entry is `[schema.]table` (every column it has, at each pass) or `[schema.]table(column, …)`. A
|
||||
database no contribution names — the controller's own among them — gives the reader nothing. A column
|
||||
or table whose name says it holds a secret (private, secret, sealed, token, password, credential, salt,
|
||||
a key) is never granted, declared or not, and the refusal is reported. Every minute, on every
|
||||
database, the reader's role memberships are revoked and its grants brought to exactly that, so an
|
||||
existing install is narrowed without anybody acting and a narrowed declaration takes effect at the
|
||||
next pass. Before a caller's statement runs, the reader's own session is asked which secret-named
|
||||
a key) is never granted, declared or not, and the refusal is reported. **That name check is a
|
||||
backstop, not the guard:** a module that declares a table whose secret sits under a plain column name
|
||||
(`value`, `data`, `blob`) has declared that secret readable, and the review of its declaration is
|
||||
what stands in the way. Declare columns by name where a table mixes the two.
|
||||
Every minute, on every database, the reader's role memberships are revoked and its grants brought to
|
||||
exactly that — each grant revoked as its own grantor, since a superuser's revoke acts as the table's
|
||||
owner and leaves a grant another role made — and then read back: anything the reader still holds that
|
||||
is not declared is reported as a failure, never as revoked. An existing install is narrowed without
|
||||
anybody acting, and a narrowed declaration takes effect at the next pass. The role itself is altered
|
||||
only when an attribute, a setting or its password has drifted, so the password is not sent every
|
||||
minute. Before a caller's statement runs, the reader's own session is asked which secret-named
|
||||
columns it can read (a grant to `PUBLIC` or by hand would show there); if any, the statement is
|
||||
refused and the columns named. A statement that names a known secret table of the controller's, or a
|
||||
secret-sounding name, is refused before it is sent — a second layer, not the guard.
|
||||
- **What no grant shows.** A `SECURITY DEFINER` function the reader may execute (PUBLIC may execute a new
|
||||
function unless revoked) runs with its owner's rights, and a view the reader may read reads with its
|
||||
owner's. Neither needs a grant on the table behind it, so neither the reconcile nor the probe can see
|
||||
what it returns. Each pass lists both for every database, in `postgres_reader_grants` and once in the
|
||||
log; they are reported, not refused.
|
||||
|
||||
## Tools
|
||||
|
||||
@@ -76,7 +90,7 @@ for, so one removed by hand is installed again.
|
||||
| `postgres_query` `{database, sql}` | one read-only statement, as the reader; rows keyed by column, `NULL` as null |
|
||||
| `postgres_retire_database` `{database, confirm}` | renames a database aside and locks its owner; `confirm` repeats the name |
|
||||
| `mesh-store.databases`, `mesh-store.query` | the store seat's verbs: the same listing and read-only query |
|
||||
| `postgres_reader_grants` | what the reader may read in each database as the last pass left it: readable columns, refused secrets, declared but missing, revoked, and any secret it can still read; names only |
|
||||
| `postgres_reader_grants` | what the reader may read in each database as the last pass left it: readable columns, refused secrets, declared but missing, revoked, any secret it can still read, and the definer functions and views it may use; names only |
|
||||
| `provisioner_retirement` | what is held, what waits for a person, what was rejected, every retired consumer and set-aside database with when, why and size |
|
||||
| `provisioner_retire_approve`, `provisioner_retire_reject` `{consumers, why, by}` | a person's answer to a set waiting; through the controller's `retire approve|reject` |
|
||||
| `provisioner_delete` `{consumer, confirm, why, by}` | drops one retired consumer's database and role, or one set-aside database; through the controller's `cleanup delete` |
|
||||
|
||||
Reference in New Issue
Block a user