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:
committed by
Steve Chavez
parent
674615041a
commit
9c005fc683
@@ -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
|
||||
|
||||
Vendored
+29
@@ -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';
|
||||
|
||||
Vendored
+7
@@ -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 ;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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,2 +1,3 @@
|
||||
db-pool = 1
|
||||
jwt-secret = "reallyreallyreallyreallyverysafe"
|
||||
db-load-guc-config = false
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
db-pool = 1
|
||||
server-unix-socket = "$(POSTGREST_TEST_SOCKET)"
|
||||
jwt-secret = "reallyreallyreallyreallyverysafe"
|
||||
db-load-guc-config = false
|
||||
|
||||
@@ -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/*"
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user