feat: get configuration parameters from the db

Allows configuring postgrest from the db by setting config parameters
on the connection role. For example:

ALTER ROLE postgrest_test_authenticator
SET pgrst.jwt-secret = "REALLYREALLYREALLYREALLYVERYSAFE"

The above wWill set the `jwt-secret` config option accordingly.

SUPERUSER privileges are required for ALTERing role settings,
so this might not work on some cloud-managed databases.

This feature is enabled by default, for disabling it you can add the
following to the config file:

db-load-guc-config = false
This commit is contained in:
steve-chavez
2021-01-19 13:49:40 -05:00
committed by Steve Chavez
parent 674615041a
commit 9c005fc683
29 changed files with 250 additions and 76 deletions
+1
View File
@@ -78,6 +78,7 @@ _baseCfg = let secret = Just $ encodeUtf8 "reallyreallyreallyreallyverysafe" in
, configDbPreparedStatements = True
, configDbRootSpec = Nothing
, configDbSchemas = fromList ["test"]
, configDbLoadGucConfig = False
, configDbUri = mempty
, configJWKS = parseSecret <$> secret
, configJwtAudience = Nothing
+29
View File
@@ -5,3 +5,32 @@ CREATE ROLE postgrest_test_default_role;
CREATE ROLE postgrest_test_author;
GRANT postgrest_test_anonymous, postgrest_test_default_role, postgrest_test_author TO :USER;
-- reloadable config options for io tests
ALTER ROLE postgrest_test_authenticator SET pgrst."jwt-aud" = 'https://example.org';
ALTER ROLE postgrest_test_authenticator SET pgrst."openapi-server-proxy-uri" = 'https://example.org/api';
ALTER ROLE postgrest_test_authenticator SET pgrst."raw-media-types" = 'application/vnd.pgrst.db-config';
ALTER ROLE postgrest_test_authenticator SET pgrst."jwt-secret" = 'REALLYREALLYREALLYREALLYVERYSAFE';
ALTER ROLE postgrest_test_authenticator SET pgrst."jwt-secret-is-base64" = 'true';
ALTER ROLE postgrest_test_authenticator SET pgrst."jwt-role-claim-key" = '."a"."role"';
ALTER ROLE postgrest_test_authenticator SET pgrst."db-tx-end" = 'commit-allow-override';
ALTER ROLE postgrest_test_authenticator SET pgrst."db-schemas" = 'test, tenant1, tenant2';
ALTER ROLE postgrest_test_authenticator SET pgrst."db-root-spec" = 'root';
ALTER ROLE postgrest_test_authenticator SET pgrst."db-prepared-statements" = 'false';
ALTER ROLE postgrest_test_authenticator SET pgrst."db-pre-request" = 'custom_headers';
ALTER ROLE postgrest_test_authenticator SET pgrst."db-max-rows" = '1000';
ALTER ROLE postgrest_test_authenticator SET pgrst."db-extra-search-path" = 'public, extensions';
-- non-reloadable configs for io tests
ALTER ROLE postgrest_test_authenticator SET pgrst."server-host" = 'ignored';
ALTER ROLE postgrest_test_authenticator SET pgrst."server-port" = 'ignored';
ALTER ROLE postgrest_test_authenticator SET pgrst."server-unix-socket" = 'ignored';
ALTER ROLE postgrest_test_authenticator SET pgrst."server-unix-socket-mode" = 'ignored';
ALTER ROLE postgrest_test_authenticator SET pgrst."log-level" = 'ignored';
ALTER ROLE postgrest_test_authenticator SET pgrst."db-anon-role" = 'ignored';
ALTER ROLE postgrest_test_authenticator SET pgrst."db-uri" = 'postgresql://ignored';
ALTER ROLE postgrest_test_authenticator SET pgrst."db-channel-enabled" = 'ignored';
ALTER ROLE postgrest_test_authenticator SET pgrst."db-channel" = 'ignored';
ALTER ROLE postgrest_test_authenticator SET pgrst."db-pool" = 'ignored';
ALTER ROLE postgrest_test_authenticator SET pgrst."db-pool-timeout" = 'ignored';
ALTER ROLE postgrest_test_authenticator SET pgrst."db-load-guc-config" = 'ignored';
+7
View File
@@ -1924,3 +1924,10 @@ $$ language sql;
-- Only used for manually testing creating prepared statements
create view prepared_statements as
select * from pg_catalog.pg_prepared_statements;
create or replace function change_max_rows_config(val int) returns void as $_$
begin
execute format($$
alter role postgrest_test_authenticator set pgrst."db-max-rows" = %L;
$$, val);
end $_$ volatile security definer language plpgsql ;
+1
View File
@@ -7,3 +7,4 @@ pre-request = "check_alias"
role-claim-key = ".aliased"
root-spec = "open_alias"
secret-is-base64 = true
db-load-guc-config = false
@@ -2,3 +2,4 @@ db-pool = 1
db-pool-timeout = 1
app.settings.external_api_secret = "0123456789abcdef"
db-load-guc-config = false
@@ -3,3 +3,4 @@ db-pool = 1
# Read secret from a file: /dev/stdin (alias for standard input)
jwt-secret = "@/dev/stdin"
jwt-secret-is-base64 = true
db-load-guc-config = false
@@ -5,3 +5,4 @@ db-anon-role = "required"
db-channel-enabled = "1"
db-prepared-statements = "0"
jwt-secret-is-base64 = "2"
db-load-guc-config = false
@@ -5,3 +5,4 @@ db-anon-role = "required"
db-channel-enabled = "true"
db-prepared-statements = "FALSE"
jwt-secret-is-base64 = "\"true\""
db-load-guc-config = false
@@ -1,3 +1,4 @@
db-uri = "@/dev/stdin"
db-pool = 1
jwt-secret = "reallyreallyreallyreallyverysafe"
db-load-guc-config = false
+2
View File
@@ -1,3 +1,5 @@
db-uri = "required"
db-schemas = "required"
db-anon-role = "required"
# Not the default, but only works with proper db-uri
db-load-guc-config = false
@@ -9,6 +9,7 @@ db-pre-request = "check_alias"
db-prepared-statements = true
db-root-spec = "open_alias"
db-schemas = "provided_through_alias"
db-load-guc-config = "false"
db-tx-end = "commit"
db-uri = "required"
jwt-aud = ""
@@ -9,6 +9,7 @@ db-pre-request = ""
db-prepared-statements = false
db-root-spec = ""
db-schemas = "required"
db-load-guc-config = "false"
db-tx-end = "commit"
db-uri = "required"
jwt-aud = ""
@@ -9,6 +9,7 @@ db-pre-request = ""
db-prepared-statements = false
db-root-spec = ""
db-schemas = "required"
db-load-guc-config = "false"
db-tx-end = "commit"
db-uri = "required"
jwt-aud = ""
@@ -9,6 +9,7 @@ db-pre-request = ""
db-prepared-statements = true
db-root-spec = ""
db-schemas = "required"
db-load-guc-config = "false"
db-tx-end = "commit"
db-uri = "required"
jwt-aud = ""
@@ -0,0 +1,27 @@
db-anon-role = "postgrest_test_anonymous"
db-channel = "postgrest"
db-channel-enabled = true
db-extra-search-path = "public,extensions"
db-max-rows = 1000
db-pool = 1
db-pool-timeout = 100
db-pre-request = "custom_headers"
db-prepared-statements = false
db-root-spec = "root"
db-schemas = "test,tenant1,tenant2"
db-load-guc-config = "true"
db-tx-end = "commit-allow-override"
db-uri = "<REPLACED_WITH_DB_URI>"
jwt-aud = "https://example.org"
jwt-role-claim-key = ".\"a\".\"role\""
jwt-secret = "REALLYREALLYREALLYREALLYVERYSAFE"
jwt-secret-is-base64 = true
log-level = "info"
openapi-server-proxy-uri = "https://example.org/api"
raw-media-types = "application/vnd.pgrst.db-config"
server-host = "0.0.0.0"
server-port = 80
server-unix-socket = "/tmp/pgrst_io_test.sock"
server-unix-socket-mode = "777"
app.settings.test = "test"
app.settings.test2 = "test"
@@ -9,6 +9,7 @@ db-pre-request = "please_run_fast"
db-prepared-statements = false
db-root-spec = "openapi_v3"
db-schemas = "multi,tenant,setup"
db-load-guc-config = "false"
db-tx-end = "rollback-allow-override"
db-uri = "tmp_db"
jwt-aud = "https://postgrest.org"
@@ -9,6 +9,7 @@ db-pre-request = ""
db-prepared-statements = true
db-root-spec = ""
db-schemas = "required"
db-load-guc-config = "true"
db-tx-end = "commit"
db-uri = "required"
jwt-aud = ""
@@ -11,6 +11,7 @@ PGRST_DB_PREPARED_STATEMENTS: false
PGRST_DB_PRE_REQUEST: please_run_fast
PGRST_DB_ROOT_SPEC: openapi_v3
PGRST_DB_SCHEMAS: multi, tenant,setup
PGRST_DB_LOAD_GUC_CONFIG: false
PGRST_DB_TX_END: rollback-allow-override
PGRST_DB_URI: tmp_db
PGRST_JWT_AUD: 'https://postgrest.org'
+1
View File
@@ -9,6 +9,7 @@ db-pre-request = "please_run_fast"
db-prepared-statements = false
db-root-spec = "openapi_v3"
db-schemas = "multi, tenant,setup"
db-load-guc-config = "false"
db-tx-end = "rollback-allow-override"
db-uri = "tmp_db"
jwt-aud = "https://postgrest.org"
@@ -1,3 +1,4 @@
db-pool = 1
jwt-role-claim-key = "$(ROLE_CLAIM_KEY)"
jwt-secret = "reallyreallyreallyreallyverysafe"
db-load-guc-config = false
@@ -3,3 +3,4 @@ db-pool = 1
# Read secret from a file: /dev/stdin (alias for standard input)
jwt-secret = "@/dev/stdin"
jwt-secret-is-base64 = false
db-load-guc-config = false
@@ -3,3 +3,4 @@ db-pool = 1
app.settings.name_var = "John"
jwt-secret = "invalidinvalidinvalidinvalidinvalid"
db-load-guc-config = false
+1
View File
@@ -1,2 +1,3 @@
db-pool = 1
jwt-secret = "reallyreallyreallyreallyverysafe"
db-load-guc-config = false
+1
View File
@@ -1,3 +1,4 @@
db-pool = 1
server-unix-socket = "$(POSTGREST_TEST_SOCKET)"
jwt-secret = "reallyreallyreallyreallyverysafe"
db-load-guc-config = false
+50 -1
View File
@@ -87,6 +87,7 @@ def defaultenv():
"PGRST_DB_URI": os.environ["PGRST_DB_URI"],
"PGRST_DB_SCHEMAS": os.environ["PGRST_DB_SCHEMAS"],
"PGRST_DB_ANON_ROLE": os.environ["PGRST_DB_ANON_ROLE"],
"PGRST_DB_LOAD_GUC_CONFIG": "false"
}
@@ -234,7 +235,13 @@ def test_cli(args, env, use_defaultenv, expect, defaultenv):
@pytest.mark.parametrize(
"expectedconfig", (CONFIGSDIR / "expected").iterdir(), ids=attrgetter("name")
"expectedconfig",
[
expectedconfig
for expectedconfig in (CONFIGSDIR / "expected").iterdir()
if (CONFIGSDIR / expectedconfig.name).exists()
],
ids=attrgetter("name"),
)
def test_expected_config(expectedconfig):
"""
@@ -261,6 +268,23 @@ def test_expected_config_from_environment():
assert dumpconfig(env=env) == expected
def test_expected_config_from_db_settings(defaultenv):
"Config should be overriden from database settings"
config = CONFIGSDIR / "no-defaults.config"
env = {
**defaultenv,
"PGRST_DB_LOAD_GUC_CONFIG": "true",
}
expected = (
(CONFIGSDIR / "expected" / "no-defaults-with-db.config")
.read_text()
.replace("<REPLACED_WITH_DB_URI>", env["PGRST_DB_URI"])
)
assert dumpconfig(configpath=config, env=env) == expected
@pytest.mark.parametrize(
"config",
[conf for conf in CONFIGSDIR.iterdir() if conf.suffix == ".config"],
@@ -482,3 +506,28 @@ def test_db_schema_reload(tmp_path, defaultenv):
response = postgrest.session.get("/parents", headers=headers)
assert response.status_code == 200
def test_max_rows_reload(defaultenv):
"max-rows should be reloaded from role settings when PostgREST receives a SIGUSR2."
config = CONFIGSDIR / "sigusr2-settings.config"
env = {
**defaultenv,
"PGRST_DB_LOAD_GUC_CONFIG": "true",
}
with run(config, env=env) as postgrest:
response = postgrest.session.head("/projects")
assert response.headers["Content-Range"] == "0-4/*"
# change max-rows config on the db
postgrest.session.post("/rpc/change_max_rows_config", data={"val": 1})
# reload config
postgrest.process.send_signal(signal.SIGUSR2)
time.sleep(0.1)
response = postgrest.session.head("/projects")
assert response.headers["Content-Range"] == "0-0/*"
+1
View File
@@ -12,6 +12,7 @@ export PGRST_DB_POOL="1"
export PGRST_SERVER_HOST="127.0.0.1"
export PGRST_SERVER_PORT="$pgrPort"
export PGRST_JWT_SECRET="reallyreallyreallyreallyverysafe"
export PGRST_DB_LOAD_GUC_CONFIG="false"
trap "kill 0" int term exit