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
+59 -53
View File
@@ -8,7 +8,7 @@ import qualified Data.ByteString.Lazy as LBS
import qualified Hasql.Connection as C
import qualified Hasql.Notifications as N
import qualified Hasql.Pool as P
import qualified Hasql.Session as S
import qualified Hasql.Transaction as HT
import qualified Hasql.Transaction.Sessions as HT
import Control.AutoUpdate (defaultUpdateSettings, mkAutoUpdate,
@@ -28,8 +28,7 @@ import Data.Time.Clock (getCurrentTime)
import Network.Wai.Handler.Warp (defaultSettings, runSettings,
setHost, setPort, setServerName)
import System.CPUTime (getCPUTime)
import System.IO (BufferMode (..), hPrint,
hSetBuffering)
import System.IO (BufferMode (..), hSetBuffering)
import Text.Printf (hPrintf)
import PostgREST.App (postgrest)
@@ -37,6 +36,7 @@ import PostgREST.Config
import PostgREST.DbStructure (getDbStructure, getPgVersion)
import PostgREST.Error (PgError (PgError), checkIsFatal,
errorPayload)
import PostgREST.Statements (dbSettingsStatement)
import PostgREST.Types (ConnectionStatus (..), DbStructure,
PgVersion (..), SCacheStatus (..),
minimumPgVersion)
@@ -68,7 +68,7 @@ main = do
opts <- readCLIShowHelp env
-- build the 'AppConfig' from the config file path
conf <- readValidateConfig env $ cliPath opts
conf <- readValidateConfig mempty env $ cliPath opts
-- These are config values that can't be reloaded at runtime. Reloading some of them would imply restarting the web server.
let
@@ -88,19 +88,7 @@ main = do
poolSize = configDbPoolSize conf
poolTimeout = configDbPoolTimeout' conf
logLevel = configLogLevel conf
case cliCommand opts of
CmdDumpConfig ->
do
putStr $ dumpAppConfig conf
exitSuccess
CmdDumpSchema ->
do
dumpedSchema <- dumpSchema conf
putStrLn dumpedSchema
exitSuccess
CmdRun ->
pass
gucConfigEnabled = configDbLoadGucConfig conf
-- create connection pool with the provided settings, returns either a 'Connection' or a 'ConnectionError'. Does not throw.
pool <- P.acquire (poolSize, poolTimeout, dbUri)
@@ -117,6 +105,24 @@ main = do
-- Config that can change at runtime
refConf <- newIORef conf
-- re-read and override the config if db-load-guc-config is true
when gucConfigEnabled $
reReadConfig pool gucConfigEnabled env (cliPath opts) refConf
case cliCommand opts of
CmdDumpConfig ->
do
dumpedConfig <- dumpAppConfig <$> readIORef refConf
putStr dumpedConfig
exitSuccess
CmdDumpSchema ->
do
dumpedSchema <- dumpSchema pool =<< readIORef refConf
putStrLn dumpedSchema
exitSuccess
CmdRun ->
pass
-- This is passed to the connectionWorker method so it can kill the main thread if the PostgreSQL's version is not supported.
mainTid <- myThreadId
@@ -141,11 +147,10 @@ main = do
Catch connWorker
) Nothing
-- Re-read the config on SIGUSR2, but only if we have a config file
when (isJust $ cliPath opts) $
void $ installHandler sigUSR2 (
Catch $ reReadConfig env (cliPath opts) refConf
) Nothing
-- Re-read the config on SIGUSR2
void $ installHandler sigUSR2 (
Catch $ reReadConfig pool gucConfigEnabled env (cliPath opts) refConf >> putStrLn ("Config reloaded" :: Text)
) Nothing
#endif
-- reload schema cache on NOTIFY
@@ -267,6 +272,15 @@ connectionStatus pool =
putStrLn $ "Attempting to reconnect to the database in " <> (show delay::Text) <> " seconds..."
return itShould
loadDbSettings :: P.Pool -> IO [(Text, Text)]
loadDbSettings pool = do
result <- P.use pool $ HT.transaction HT.ReadCommitted HT.Read $ HT.statement mempty dbSettingsStatement
case result of
Left e -> do
hPutStrLn stderr ("An error ocurred when trying to query database settings for the config parameters:\n" <> show e :: Text)
pure []
Right x -> pure x
-- | Load the DbStructure by using a connection from the pool.
loadSchemaCache :: P.Pool -> PgVersion -> IORef AppConfig -> IORef (Maybe DbStructure) -> IO SCacheStatus
loadSchemaCache pool actualPgVersion refConf refDbStructure = do
@@ -332,41 +346,33 @@ listener dbUri dbChannel pool refConf refDbStructure mvarConnectionStatus connWo
errorMessage = "Could not listen for notifications on the " <> dbChannel <> " channel" :: Text
retryMessage = "Retrying listening for notifications on the " <> dbChannel <> " channel.." :: Text
#ifndef mingw32_HOST_OS
-- | Re-reads the config at runtime. Invoked on SIGUSR2.
-- | Re-reads the config at runtime.
-- | If it panics(config path was changed, invalid setting), it'll show an error but won't kill the main thread.
reReadConfig :: Environment -> Maybe FilePath -> IORef AppConfig -> IO ()
reReadConfig env path refConf = do
conf <- readValidateConfig env path
reReadConfig :: P.Pool -> Bool -> Environment -> Maybe FilePath -> IORef AppConfig -> IO ()
reReadConfig pool gucConfigEnabled env path refConf = do
dbSettings <- if gucConfigEnabled then loadDbSettings pool else pure []
conf <- readValidateConfig dbSettings env path
atomicWriteIORef refConf conf
putStrLn ("Config file reloaded" :: Text)
#endif
-- | Dump DbStructure schema to JSON
dumpSchema :: AppConfig -> IO LBS.ByteString
dumpSchema conf =
do
eitherConn <- C.acquire . toS $ configDbUri conf
case eitherConn of
Left e -> hPrint stderr e >> exitFailure
Right conn -> do
result <-
timeToStderr "Loaded schema in %.3f seconds" $
flip S.run conn $ do
pgVersion <- getPgVersion
HT.transaction HT.ReadCommitted HT.Read $
getDbStructure
(toList $ configDbSchemas conf)
(configDbExtraSearchPath conf)
pgVersion
(configDbPreparedStatements conf)
C.release conn
case result of
Left e -> do
hPutStrLn stderr $ "An error ocurred when loading the schema cache:\n" <> show e
exitFailure
Right dbStructure -> return $ Aeson.encode dbStructure
dumpSchema :: P.Pool -> AppConfig -> IO LBS.ByteString
dumpSchema pool conf = do
result <-
timeToStderr "Loaded schema in %.3f seconds" $
P.use pool $ do
pgVersion <- getPgVersion
HT.transaction HT.ReadCommitted HT.Read $
getDbStructure
(toList $ configDbSchemas conf)
(configDbExtraSearchPath conf)
pgVersion
(configDbPreparedStatements conf)
P.release pool
case result of
Left e -> do
hPutStrLn stderr $ "An error ocurred when loading the schema cache:\n" <> show e
exitFailure
Right dbStructure -> return $ Aeson.encode dbStructure
-- | Print the time taken to run an IO action to stderr with the given printf string
timeToStderr :: [Char] -> IO (Either a b) -> IO (Either a b)
+35 -22
View File
@@ -45,6 +45,7 @@ import Control.Monad (fail)
import Crypto.JWT (JWKSet, StringOrURI, stringOrUri)
import Data.Aeson (encode, toJSON)
import Data.Either.Combinators (fromRight', whenLeft)
import Data.List (lookup)
import Data.List.NonEmpty (fromList, toList)
import Data.Maybe (fromJust)
import Data.Scientific (floatingOrInteger)
@@ -101,6 +102,7 @@ data AppConfig = AppConfig {
, configDbPreparedStatements :: Bool
, configDbRootSpec :: Maybe Text
, configDbSchemas :: NonEmpty Text
, configDbLoadGucConfig :: Bool
, configDbTxAllowOverride :: Bool
, configDbTxRollbackAll :: Bool
, configDbUri :: Text
@@ -215,6 +217,9 @@ readCLIShowHelp env = customExecParser parserPrefs opts
|## Enable or disable the notification channel
|db-channel-enabled = false
|
|## Enable loading config parameters from the database by changing the connection role settings
|db-load-guc-config = true
|
|## how to terminate database transactions
|## possible values are:
|## commit (default)
@@ -280,6 +285,7 @@ dumpAppConfig conf =
,("db-prepared-statements", toLower . show . configDbPreparedStatements)
,("db-root-spec", q . fromMaybe mempty . configDbRootSpec)
,("db-schemas", q . intercalate "," . toList . configDbSchemas)
,("db-load-guc-config", q . toLower . show . configDbLoadGucConfig)
,("db-tx-end", q . showTxEnd)
,("db-uri", q . configDbUri)
,("jwt-aud", toS . encode . maybe "" toJSON . configJwtAudience)
@@ -313,7 +319,7 @@ dumpAppConfig conf =
secret = fromMaybe mempty $ configJwtSecret c
showSocketMode c = showOct (fromRight' $ configServerUnixSocketMode c) ""
-- This class is needed for the polymorphism of overrideFromEnvironment
-- This class is needed for the polymorphism of overrideFromDbOrEnvironment
-- because C.required and C.optional have different signatures
class JustIfMaybe a b where
justIfMaybe :: a -> b
@@ -325,8 +331,8 @@ instance JustIfMaybe a (Maybe a) where
justIfMaybe a = Just a
-- | Parse the config file
readAppConfig :: Environment -> Maybe FilePath -> IO AppConfig
readAppConfig env optPath = do
readAppConfig :: [(Text, Text)] -> Environment -> Maybe FilePath -> IO AppConfig
readAppConfig dbSettings env optPath = do
-- Now read the actual config file
conf <- case optPath of
Just cfgPath -> catches (C.load cfgPath)
@@ -356,12 +362,13 @@ readAppConfig env optPath = do
<*> (fromMaybe 10 <$> optInt "db-pool-timeout")
<*> optWithAlias (optString "db-pre-request")
(optString "pre-request")
<*> (fromMaybe True <$> optBool "db-prepared-statements")
<*> (fromMaybe True <$> optBool "db-prepared-statements")
<*> optWithAlias (optString "db-root-spec")
(optString "root-spec")
<*> (fromList . splitOnCommas <$> reqWithAlias (optValue "db-schemas")
(optValue "db-schema")
"missing key: either db-schemas or db-schema must be set")
<*> (fromMaybe True <$> optBool "db-load-guc-config")
<*> parseTxEnd "db-tx-end" snd
<*> parseTxEnd "db-tx-end" fst
<*> reqString "db-uri"
@@ -387,21 +394,27 @@ readAppConfig env optPath = do
fromEnv = M.mapKeys fromJust $ M.filterWithKey (\k _ -> isJust k) $ M.mapKeys normalize env
normalize k = ("app.settings." <>) <$> stripPrefix "PGRST_APP_SETTINGS_" (toS k)
overrideFromEnvironment :: JustIfMaybe a b =>
overrideFromDbOrEnvironment :: JustIfMaybe a b =>
(C.Key -> C.Parser C.Value a -> C.Parser C.Config b) ->
C.Key -> (C.Value -> a) -> C.Parser C.Config b
overrideFromEnvironment necessity key coercion =
case M.lookup name env of
Just envVal -> pure $ justIfMaybe $ coercion $ C.String envVal
Nothing -> necessity key (coercion <$> C.value)
overrideFromDbOrEnvironment necessity key coercion =
case reloadableDbSetting <|> M.lookup name env of
Just dbOrEnvVal -> pure $ justIfMaybe $ coercion $ C.String dbOrEnvVal
Nothing -> necessity key (coercion <$> C.value)
where
name = "PGRST_" <> map capitalize (toS key)
capitalize '-' = '_'
capitalize c = toUpper c
reloadableDbSetting =
if key `notElem` [
"server-host", "server-port", "server-unix-socket", "server-unix-socket-mode", "log-level",
"db-anon-role", "db-uri", "db-channel-enabled", "db-channel", "db-pool", "db-pool-timeout", "db-load-guc-config"]
then lookup key dbSettings
else Nothing
parseSocketFileMode :: C.Key -> C.Parser C.Config (Either Text FileMode)
parseSocketFileMode k =
overrideFromEnvironment C.optional k coerceText >>= \case
overrideFromDbOrEnvironment C.optional k coerceText >>= \case
Nothing -> pure $ Right 432 -- return default 660 mode if no value was provided
Just fileModeText ->
case (readOct . unpack) fileModeText of
@@ -414,7 +427,7 @@ readAppConfig env optPath = do
parseJwtAudience :: C.Key -> C.Parser C.Config (Maybe StringOrURI)
parseJwtAudience k =
overrideFromEnvironment C.optional k coerceText >>= \case
overrideFromDbOrEnvironment C.optional k coerceText >>= \case
Nothing -> pure Nothing -- no audience in config file
Just aud -> case preview stringOrUri (unpack aud) of
Nothing -> fail "Invalid Jwt audience. Check your configuration."
@@ -423,7 +436,7 @@ readAppConfig env optPath = do
parseLogLevel :: C.Key -> C.Parser C.Config LogLevel
parseLogLevel k =
overrideFromEnvironment C.optional k coerceText >>= \case
overrideFromDbOrEnvironment C.optional k coerceText >>= \case
Nothing -> pure LogError
Just "" -> pure LogError
Just "crit" -> pure LogCrit
@@ -434,7 +447,7 @@ readAppConfig env optPath = do
parseTxEnd :: C.Key -> ((Bool, Bool) -> Bool) -> C.Parser C.Config Bool
parseTxEnd k f =
overrideFromEnvironment C.optional k coerceText >>= \case
overrideFromDbOrEnvironment C.optional k coerceText >>= \case
-- RollbackAll AllowOverride
Nothing -> pure $ f (False, False)
Just "" -> pure $ f (False, False)
@@ -460,19 +473,19 @@ readAppConfig env optPath = do
Nothing -> alias
reqString :: C.Key -> C.Parser C.Config Text
reqString k = overrideFromEnvironment C.required k coerceText
reqString k = overrideFromDbOrEnvironment C.required k coerceText
optString :: C.Key -> C.Parser C.Config (Maybe Text)
optString k = mfilter (/= "") <$> overrideFromEnvironment C.optional k coerceText
optString k = mfilter (/= "") <$> overrideFromDbOrEnvironment C.optional k coerceText
optValue :: C.Key -> C.Parser C.Config (Maybe C.Value)
optValue k = overrideFromEnvironment C.optional k identity
optValue k = overrideFromDbOrEnvironment C.optional k identity
optInt :: (Read i, Integral i) => C.Key -> C.Parser C.Config (Maybe i)
optInt k = join <$> overrideFromEnvironment C.optional k coerceInt
optInt k = join <$> overrideFromDbOrEnvironment C.optional k coerceInt
optBool :: C.Key -> C.Parser C.Config (Maybe Bool)
optBool k = join <$> overrideFromEnvironment C.optional k coerceBool
optBool k = join <$> overrideFromDbOrEnvironment C.optional k coerceBool
coerceText :: C.Value -> Text
coerceText (C.String s) = s
@@ -506,10 +519,10 @@ readAppConfig env optPath = do
hPutStrLn stderr err
exitFailure
-- | Parse the AppConfig and validate it. Panic on invalid config options.
readValidateConfig :: Environment -> Maybe FilePath -> IO AppConfig
readValidateConfig env path = do
conf <- loadDbUriFile =<< loadSecretFile =<< readAppConfig env path
-- | Parse the AppConfig and validate it. Overrides the config options from env vars or db settings. Panics on invalid config options.
readValidateConfig :: [(Text, Text)] -> Environment -> Maybe FilePath -> IO AppConfig
readValidateConfig dbSettings env path = do
conf <- loadDbUriFile =<< loadSecretFile =<< readAppConfig dbSettings env path
-- Checks that the provided proxy uri is formated correctly
when (isMalformedProxyUri $ toS <$> configOpenApiServerProxyUri conf) $
panic
+20
View File
@@ -14,6 +14,7 @@ module PostgREST.Statements (
, createReadStatement
, callProcStatement
, createExplainStatement
, dbSettingsStatement
) where
@@ -24,6 +25,7 @@ import qualified Data.ByteString.Char8 as BS
import Data.Maybe
import Data.Text.Read (decimal)
import qualified Hasql.Decoders as HD
import qualified Hasql.Encoders as HE
import qualified Hasql.Statement as H
import Network.HTTP.Types.Status
import PostgREST.Error
@@ -37,6 +39,8 @@ import Protolude.Conv (toS)
import qualified Hasql.DynamicStatements.Snippet as H
import qualified Hasql.DynamicStatements.Statement as H
import Text.InterpolatedString.Perl6 (q)
{-| The generic query result format used by API responses. The location header
is represented as a list of strings containing variable bindings like
@"k1=eq.42"@, or the empty list if there is no location header.
@@ -190,3 +194,19 @@ decodeGucHeaders = first (const GucHeadersError) . JSON.eitherDecode . toS <$> H
decodeGucStatus :: HD.Value (Either SimpleError (Maybe Status))
decodeGucStatus = first (const GucStatusError) . fmap (Just . toEnum . fst) . decimal <$> HD.text
-- | Get db settings from the connection role. Only used for configuration.
dbSettingsStatement :: H.Statement () [(Text, Text)]
dbSettingsStatement = H.Statement sql HE.noParams decodeSettings False
where
sql = [q|
with
role_setting as (
select unnest(setconfig) as setting from pg_catalog.pg_db_role_setting where setrole = 'postgrest_test_authenticator'::regrole::oid
),
kv_settings as (
select split_part(setting, '=', 1) as key, split_part(setting, '=', 2) as value from role_setting
)
select replace(key, 'pgrst.', '') as key, value from kv_settings where key like 'pgrst.%';
|]
decodeSettings = HD.rowList $ (,) <$> column HD.text <*> column HD.text
+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