From 8a3686d86bf32d153ed13b25087e35167c211f65 Mon Sep 17 00:00:00 2001 From: steve-chavez Date: Mon, 22 May 2023 18:51:27 -0300 Subject: [PATCH] refactor: whitelist in-db config settings --- src/PostgREST/Config.hs | 13 ++---- src/PostgREST/Config/Database.hs | 79 ++++++++++++++++++++++++-------- test/io/db_config.sql | 3 ++ 3 files changed, 66 insertions(+), 29 deletions(-) diff --git a/src/PostgREST/Config.hs b/src/PostgREST/Config.hs index e3e102050..3bd48c97a 100644 --- a/src/PostgREST/Config.hs +++ b/src/PostgREST/Config.hs @@ -358,21 +358,14 @@ parser optPath env dbSettings roleSettings = (C.Key -> C.Parser C.Value a -> C.Parser C.Config b) -> C.Key -> (C.Value -> a) -> C.Parser C.Config b overrideFromDbOrEnvironment necessity key coercion = - case reloadableDbSetting <|> M.lookup envVarName env of + case dbConf <|> M.lookup envVarName env of Just dbOrEnvVal -> pure $ justIfMaybe $ coercion $ C.String dbOrEnvVal - Nothing -> necessity key (coercion <$> C.value) + Nothing -> necessity key (coercion <$> C.value) where dashToUnderscore '-' = '_' dashToUnderscore c = c envVarName = "PGRST_" <> (toUpper . dashToUnderscore <$> toS key) - reloadableDbSetting = - let dbSettingName = T.pack $ dashToUnderscore <$> toS key in - if dbSettingName `notElem` [ - "server_host", "server_port", "server_unix_socket", "server_unix_socket_mode", "admin_server_port", "log_level", - "db_uri", "db_channel_enabled", "db_channel", "db_pool", "db_pool_acquisition_timeout", - "db_pool_max_lifetime", "db_pool_max_idletime", "db_config"] - then lookup dbSettingName dbSettings - else Nothing + dbConf = lookup (T.pack $ dashToUnderscore <$> toS key) dbSettings coerceText :: C.Value -> Text coerceText (C.String s) = s diff --git a/src/PostgREST/Config/Database.hs b/src/PostgREST/Config/Database.hs index 9f64f1d70..880b1930e 100644 --- a/src/PostgREST/Config/Database.hs +++ b/src/PostgREST/Config/Database.hs @@ -21,12 +21,40 @@ import qualified Hasql.Statement as SQL import qualified Hasql.Transaction as SQL import qualified Hasql.Transaction.Sessions as SQL -import Text.InterpolatedString.Perl6 (q) +import Text.InterpolatedString.Perl6 (q, qc) import Protolude type RoleSettings = (HM.HashMap ByteString (HM.HashMap ByteString ByteString)) +prefix :: Text +prefix = "pgrst." + +-- | In-db settings names +dbSettingsNames :: [Text] +dbSettingsNames = + (prefix <>) <$> + ["db_anon_role" + ,"db_extra_search_path" + ,"db_max_rows" + ,"db_plan_enabled" + ,"db_pre_request" + ,"db_prepared_statements" + ,"db_root_spec" + ,"db_schemas" + ,"db_tx_end" + ,"db_use_legacy_gucs" + ,"jwt_aud" + ,"jwt_role_claim_key" + ,"jwt_secret" + ,"jwt_secret_is_base64" + ,"openapi_mode" + ,"openapi_security_active" + ,"openapi_server_proxy_uri" + ,"raw_media_types" + ,"server_trace_header" + ] + queryPgVersion :: Bool -> Session PgVersion queryPgVersion prepared = statement mempty $ pgVersionStatement prepared @@ -39,33 +67,40 @@ pgVersionStatement = SQL.Statement sql HE.noParams versionRow queryDbSettings :: Bool -> Session [(Text, Text)] queryDbSettings prepared = let transaction = if prepared then SQL.transaction else SQL.unpreparedTransaction in - transaction SQL.ReadCommitted SQL.Read $ SQL.statement mempty $ dbSettingsStatement prepared + transaction SQL.ReadCommitted SQL.Read $ SQL.statement dbSettingsNames $ dbSettingsStatement prepared -- | Get db settings from the connection role. Global settings will be overridden by database specific settings. -dbSettingsStatement :: Bool -> SQL.Statement () [(Text, Text)] -dbSettingsStatement = SQL.Statement sql HE.noParams decodeSettings +-- i.e. Doing: +-- ALTER ROLE authenticator IN DATABASE postgres SET jwt_aud = 'val'; +-- ALTER ROLE authenticator SET jwt_aud = 'overridden'; +-- Will result in jwt_aud = 'overridden' +-- +-- A setting on the database only will have no effect +-- ALTER DATABASE postgres SET jwt_aud = 'xx' +dbSettingsStatement :: Bool -> SQL.Statement [Text] [(Text, Text)] +dbSettingsStatement = SQL.Statement sql (arrayParam HE.text) decodeSettings where - sql = [q| + sql = [qc| WITH - role_setting (database, setting) AS ( - SELECT setdatabase, - unnest(setconfig) - FROM pg_catalog.pg_db_role_setting - WHERE setrole = CURRENT_USER::regrole::oid - AND setdatabase IN (0, (SELECT oid FROM pg_catalog.pg_database WHERE datname = CURRENT_CATALOG)) + role_setting AS ( + SELECT setdatabase as database, + unnest(setconfig) as setting + FROM pg_catalog.pg_db_role_setting + WHERE setrole = CURRENT_USER::regrole::oid + AND setdatabase IN (0, (SELECT oid FROM pg_catalog.pg_database WHERE datname = CURRENT_CATALOG)) ), - kv_settings (database, k, v) AS ( + kv_settings AS ( SELECT database, - substr(setting, 1, strpos(setting, '=') - 1), - substr(setting, strpos(setting, '=') + 1) - FROM role_setting - WHERE setting LIKE 'pgrst.%' + substr(setting, 1, strpos(setting, '=') - 1) as k, + substr(setting, strpos(setting, '=') + 1) as v + FROM role_setting ) SELECT DISTINCT ON (key) - replace(k, 'pgrst.', '') AS key, + replace(k, '{prefix}', '') AS key, v AS value - FROM kv_settings - ORDER BY key, database DESC; + FROM kv_settings + WHERE k = ANY($1) + ORDER BY key, database DESC; |] decodeSettings = HD.rowList $ (,) <$> column HD.text <*> column HD.text @@ -111,3 +146,9 @@ compositeArrayColumn = arrayColumn . HD.composite arrayColumn :: HD.Value a -> HD.Row [a] arrayColumn = column . HD.listArray . HD.nonNullable + +param :: HE.Value a -> HE.Params a +param = HE.param . HE.nonNullable + +arrayParam :: HE.Value a -> HE.Params [a] +arrayParam = param . HE.foldableArray . HE.nonNullable diff --git a/test/io/db_config.sql b/test/io/db_config.sql index 0cb75f0f0..10a552597 100644 --- a/test/io/db_config.sql +++ b/test/io/db_config.sql @@ -40,6 +40,9 @@ ALTER ROLE db_config_authenticator SET pgrst.db_channel_enabled = 'ignored'; ALTER ROLE db_config_authenticator SET pgrst.db_channel = 'ignored'; ALTER ROLE db_config_authenticator SET pgrst.db_pool = 'ignored'; ALTER ROLE db_config_authenticator SET pgrst.db_pool_timeout = 'ignored'; +ALTER ROLE db_config_authenticator SET pgrst.db_pool_acquisition_timeout = 'ignored'; +ALTER ROLE db_config_authenticator SET pgrst.db_pool_max_lifetime = 'ignored'; +ALTER ROLE db_config_authenticator SET pgrst.db_pool_max_idletime = 'ignored'; ALTER ROLE db_config_authenticator SET pgrst.db_config = 'ignored'; -- other authenticator reloadable config options