Files
mesh-catalog/modules/postgres/cmd/postgres-provider/live_reader_test.go
T
jochen 4f44bf1eb5
mesh/delivery superseded: a newer head of the same pull request
mesh/merge-gate pass: builds postgres, sent nowhere; no bus step; 2 wait(s) for a person; every machine composes with the change as it did without (4 of 4 …
mesh/repo-check pass: its merge-check.sh passed
Give the store's reader only the columns a module declares readable, never a secret, so a query cannot read the controller's signing key (hq issue 337)
mesh_store_reader held pg_read_all_data, so every caller of mesh-store.query
and postgres_query could read every column the store holds: the signing key
nodes trust, sealed secrets, password hashes. The reader now holds no role;
each minute, on every database, its memberships are revoked and its grants
brought to what the consumer's contribution lists under readable, minus any
column or table whose name says it holds a secret. Before a statement runs,
the reader's own session is asked which secret columns it can read and the
statement is refused if any; a statement naming a known secret table is
refused before it is sent.
2026-10-08 21:43:15 +02:00

116 lines
4.0 KiB
Go

package main
// Against a real server, when one is named (MESH_POSTGRES_LIVE, as live_test.go) — skipped otherwise.
//
// novox/hq issue 337: an install made before it gave the store's reader `pg_read_all_data`, and with it
// every secret the store holds. This proves, through nothing but the query the store seat serves, that
// such an install's reader reads no secret any more: the first query takes the role back, and a secret
// the reader can still read (a grant by hand) refuses the query rather than answering it. It uses only
// what the module had before the fix, so it fails on the commit before it.
import (
"net/url"
"os"
"strings"
"testing"
)
const liveStore = "mesh_test_store"
func liveClient(t *testing.T) *Client {
t.Helper()
raw := os.Getenv("MESH_POSTGRES_LIVE")
if raw == "" {
t.Skip("MESH_POSTGRES_LIVE names no server")
}
u, _ := url.Parse(raw)
pw, _ := u.User.Password()
env := map[string]string{"MESH_PROVISION_POSTGRES": raw, "MESH_POSTGRES_PASSWORD": pw, "MESH_POSTGRES_READER_PASSWORD": "reader-pw"}
c, err := ClientFromEnv(func(k string) string { return env[k] })
if err != nil {
t.Fatal(err)
}
return c
}
// freshStore makes a database holding a key beside a plain table, and a reader as an install before
// issue 337 left it: granted pg_read_all_data, and a table granted to it by hand.
func freshStore(t *testing.T, c *Client) {
t.Helper()
for _, db := range []string{liveStore, "identity"} {
if _, err := c.Query(ctx, "", "DROP DATABASE IF EXISTS "+Ident(db)+" WITH (FORCE)"); err != nil {
t.Fatal(err)
}
if _, err := c.Query(ctx, "", "CREATE DATABASE "+Ident(db)); err != nil {
t.Fatal(err)
}
}
setup := []string{
"CREATE TABLE vault (id int, name text, private bytea)",
"INSERT INTO vault VALUES (1, 'the mesh', 'not to be read')",
"CREATE TABLE notes (id int, body text)",
"INSERT INTO notes VALUES (1, 'plain')",
"CREATE SCHEMA audit",
"CREATE TABLE audit.events (at timestamptz default now(), what text)",
"INSERT INTO audit.events (what) VALUES ('started')",
}
for _, sql := range setup {
if _, err := c.Query(ctx, liveStore, sql); err != nil {
t.Fatal(sql, err)
}
}
if _, err := c.Query(ctx, "identity", "CREATE TABLE signing_key (id int, public bytea, private bytea); INSERT INTO signing_key VALUES (1, 'p', 'k')"); err != nil {
t.Fatal(err)
}
has, err := c.exists(ctx, "SELECT 1 FROM pg_roles WHERE rolname = "+Literal(Reader))
if err != nil {
t.Fatal(err)
}
verb := "CREATE"
if has {
verb = "ALTER"
}
for _, sql := range []string{
verb + " ROLE " + Ident(Reader) + " LOGIN PASSWORD 'reader-pw'",
"GRANT pg_read_all_data TO " + Ident(Reader),
} {
if _, err := c.Query(ctx, "", sql); err != nil {
t.Fatal(sql, err)
}
}
if _, err := c.Query(ctx, liveStore, "GRANT SELECT ON vault TO "+Ident(Reader)); err != nil {
t.Fatal(err)
}
}
func dropStore(c *Client) {
for _, db := range []string{liveStore, "identity"} {
_, _ = c.Query(ctx, "", "DROP DATABASE IF EXISTS "+Ident(db)+" WITH (FORCE)")
}
}
func TestLiveAnInstallsReaderReadsNoSecret(t *testing.T) {
c := liveClient(t)
freshStore(t, c)
defer dropStore(c)
// The signing key, by name, in the controller's database: refused.
if r, err := c.ReadOnlyQuery(ctx, "identity", "SELECT private FROM signing_key"); err == nil {
t.Fatalf("the reader read the signing key: %v", r.Maps())
}
// A plain column of a table holding a key, granted to the reader by hand: refused while the reader
// can read the key beside it, and the key named.
r, err := c.ReadOnlyQuery(ctx, liveStore, "SELECT name FROM vault")
if err == nil {
t.Fatalf("answered while the reader can read vault.private: %v", r.Maps())
}
if !strings.Contains(err.Error(), "public.vault.private") {
t.Fatal(err)
}
// The role that read everything is taken back by the first query.
r, err = c.Query(ctx, "", "SELECT pg_has_role("+Literal(Reader)+", 'pg_read_all_data', 'MEMBER') AS reads_all")
if err != nil || cell(r.Rows[0], 0) != "f" {
t.Fatalf("the reader still holds pg_read_all_data: %v %v", r.Rows, err)
}
}