Compare commits

...
4 Commits
Author SHA1 Message Date
steve-chavez cd3cf9ed97 bump version to 12.2.12 2025-05-01 20:24:30 -05:00
steve-chavez 1f28efa9bd fix: don't enable admin server /config by default
This now requires setting `admin-server-config-enabled`.
2025-05-01 20:24:08 -05:00
steve-chavez 36eb72c2a0 bump version to 12.2.11 2025-04-21 17:08:00 -05:00
Taimoor ZaeemandSteve Chavez 38c596800a fix: regression with parameter charset=utf-8 in mediatype 2025-04-21 17:06:26 -05:00
21 changed files with 99 additions and 8 deletions
+13
View File
@@ -5,6 +5,19 @@ This project adheres to [Semantic Versioning](http://semver.org/).
## Unreleased ## Unreleased
## [12.2.12] - 2025-05-01
### Fixed
- #3956, Fix exposing admin server `/config` by default - @steve-chavez
+ The above endpoint is now disabled unless the `admin-server-config-enabled` config is set to `true`
## [12.2.11] - 2025-04-21
### Fixed
- #4030, Fix regression with parameter `charset=utf-8` in mediatype - @taimoorzaeem
## [12.2.10] - 2025-04-18 ## [12.2.10] - 2025-04-18
### Fixed ### Fixed
+5 -1
View File
@@ -55,10 +55,12 @@ Metrics
Provides :ref:`metrics`. Provides :ref:`metrics`.
.. _runtime_config:
Runtime Configuration Runtime Configuration
===================== =====================
Provides a ``config`` endpoint that returns the runtime :ref:`configuration`. Provides a ``config`` endpoint that returns the runtime :ref:`configuration`. This requires setting :ref:`admin-server-config-enabled`.
.. code-block:: bash .. code-block:: bash
@@ -72,6 +74,8 @@ Provides a ``config`` endpoint that returns the runtime :ref:`configuration`.
db-channel-enabled = false db-channel-enabled = false
... ...
.. _runtime_schema_cache:
Runtime Schema Cache Runtime Schema Cache
==================== ====================
+24
View File
@@ -161,6 +161,30 @@ admin-server-port
Specifies the port for the :ref:`admin_server`. Specifies the port for the :ref:`admin_server`.
.. _admin-server-config-enabled:
admin-server-config-enabled
---------------------------
.. danger::
The ``/config`` endpoint contains sensitive information, don't enable this if you're exposing the Admin Server publicly.
To safely enable this you can use a proxy like :ref:`nginx` to:
- Ensure ``/config`` are only available to local networks.
- Only expose ``/live`` and ``/ready`` to public networks.
=============== =================================
**Type** Boolean
**Default** False
**Reloadable** N
**Environment** PGRST_ADMIN_SERVER_CONFIG_ENABLED
**In-Database** `n/a`
=============== =================================
Enables the admin server :ref:`runtime_config` and :ref:`runtime_schema_cache` endpoints.
.. _app.settings.*: .. _app.settings.*:
app.settings.* app.settings.*
+1 -1
View File
@@ -1,5 +1,5 @@
name: postgrest name: postgrest
version: 12.2.10 version: 12.2.12
synopsis: REST API for any Postgres database synopsis: REST API for any Postgres database
description: Reads the schema of a PostgreSQL database and creates RESTful routes description: Reads the schema of a PostgreSQL database and creates RESTful routes
for tables, views, and functions, supporting all HTTP methods that security for tables, views, and functions, supporting all HTTP methods that security
+5 -2
View File
@@ -56,8 +56,11 @@ admin appState req respond = do
in in
respond $ Wai.responseLBS status [] mempty respond $ Wai.responseLBS status [] mempty
["config"] -> do ["config"] -> do
config <- AppState.getConfig appState config@Config.AppConfig{configAdminServerConfigEnabled} <- AppState.getConfig appState
respond $ Wai.responseLBS HTTP.status200 [] (LBS.fromStrict $ encodeUtf8 $ Config.toText config) if configAdminServerConfigEnabled then
respond $ Wai.responseLBS HTTP.status200 [] (LBS.fromStrict $ encodeUtf8 $ Config.toText config)
else
respond $ Wai.responseLBS HTTP.status404 [] mempty
["schema_cache"] -> do ["schema_cache"] -> do
sCache <- AppState.getSchemaCache appState sCache <- AppState.getSchemaCache appState
respond $ Wai.responseLBS HTTP.status200 [] (maybe mempty JSON.encode sCache) respond $ Wai.responseLBS HTTP.status200 [] (maybe mempty JSON.encode sCache)
+3
View File
@@ -128,6 +128,9 @@ exampleConfigFile =
[str|## Admin server used for checks. It's disabled by default unless a port is specified. [str|## Admin server used for checks. It's disabled by default unless a port is specified.
|# admin-server-port = 3001 |# admin-server-port = 3001
| |
|## Whether to enable the /config endpoint of the admin server
|# admin-server-config-enabled = false
|
|## The database role to use when no client authentication is provided |## The database role to use when no client authentication is provided
|# db-anon-role = "anon" |# db-anon-role = "anon"
| |
+3
View File
@@ -110,6 +110,7 @@ data AppConfig = AppConfig
, configServerUnixSocket :: Maybe FilePath , configServerUnixSocket :: Maybe FilePath
, configServerUnixSocketMode :: FileMode , configServerUnixSocketMode :: FileMode
, configAdminServerPort :: Maybe Int , configAdminServerPort :: Maybe Int
, configAdminServerConfigEnabled :: Bool
, configRoleSettings :: RoleSettings , configRoleSettings :: RoleSettings
, configRoleIsoLvl :: RoleIsolationLvl , configRoleIsoLvl :: RoleIsolationLvl
, configInternalSCSleep :: Maybe Int32 , configInternalSCSleep :: Maybe Int32
@@ -180,6 +181,7 @@ toText conf =
,("server-unix-socket", q . maybe mempty T.pack . configServerUnixSocket) ,("server-unix-socket", q . maybe mempty T.pack . configServerUnixSocket)
,("server-unix-socket-mode", q . T.pack . showSocketMode) ,("server-unix-socket-mode", q . T.pack . showSocketMode)
,("admin-server-port", maybe "\"\"" show . configAdminServerPort) ,("admin-server-port", maybe "\"\"" show . configAdminServerPort)
,("admin-server-config-enabled", T.toLower . show . configAdminServerConfigEnabled)
] ]
-- quote all app.settings -- quote all app.settings
@@ -286,6 +288,7 @@ parser optPath env dbSettings roleSettings roleIsolationLvl =
<*> (fmap T.unpack <$> optString "server-unix-socket") <*> (fmap T.unpack <$> optString "server-unix-socket")
<*> parseSocketFileMode "server-unix-socket-mode" <*> parseSocketFileMode "server-unix-socket-mode"
<*> optInt "admin-server-port" <*> optInt "admin-server-port"
<*> (fromMaybe False <$> optBool "admin-server-config-enabled")
<*> pure roleSettings <*> pure roleSettings
<*> pure roleIsolationLvl <*> pure roleIsolationLvl
<*> optInt "internal-schema-cache-sleep" <*> optInt "internal-schema-cache-sleep"
+4 -4
View File
@@ -183,14 +183,14 @@ decodeMediaType mt = decodeMediaType' $ decodeLatin1 mt
-- >>> P.parse tokenizeMediaType "" "application/vnd.pgrst.plan+text; for=\"text/xml\"; options=analyze|verbose|settings|buffers|wal" -- >>> P.parse tokenizeMediaType "" "application/vnd.pgrst.plan+text; for=\"text/xml\"; options=analyze|verbose|settings|buffers|wal"
-- Right ("application","vnd.pgrst.plan+text",[("for","text/xml"),("options","analyze|verbose|settings|buffers|wal")]) -- Right ("application","vnd.pgrst.plan+text",[("for","text/xml"),("options","analyze|verbose|settings|buffers|wal")])
-- TODO: Improve mediatype parser as per RFC 2045 https://datatracker.ietf.org/doc/html/rfc2045#section-5.1
tokenizeMediaType :: P.Parser (Text, Text, [(Text, Text)]) tokenizeMediaType :: P.Parser (Text, Text, [(Text, Text)])
tokenizeMediaType = do tokenizeMediaType = do
mainType <- P.many1 (P.alphaNum <|> P.oneOf ".*") mainType <- P.many1 (P.alphaNum <|> P.oneOf ".*")
P.char '/' P.char '/'
subType <- P.many1 (P.alphaNum <|> P.oneOf ".*+-") subType <- P.many1 (P.alphaNum <|> P.oneOf ".*+-")
params <- P.many pSemicolonSeparatedKeyVals params <- P.many pSemicolonSeparatedKeyVals
P.optional $ P.try $ P.spaces *> P.char ';' -- ending semicolon P.optional $ P.try $ P.spaces *> P.char ';' -- ending semicolon, discard input after that because it has already failed or we have hit EOF
P.eof
return (T.pack mainType, T.pack subType, params) return (T.pack mainType, T.pack subType, params)
where where
pSemicolonSeparatedKeyVals :: P.Parser (Text, Text) pSemicolonSeparatedKeyVals :: P.Parser (Text, Text)
@@ -198,12 +198,12 @@ tokenizeMediaType = do
where where
pKeyVal :: P.Parser (Text, Text) pKeyVal :: P.Parser (Text, Text)
pKeyVal = do pKeyVal = do
key <- P.many1 P.alphaNum key <- P.many1 (P.alphaNum <|> P.oneOf "-")
P.spaces P.spaces
P.char '=' P.char '='
P.spaces P.spaces
val <- P.try pQuoted <|> P.try pUnQuoted val <- P.try pQuoted <|> P.try pUnQuoted
return (T.pack key, T.pack val) return (T.pack key, T.pack val)
where where
pUnQuoted = P.many1 (P.alphaNum <|> P.oneOf "|") pUnQuoted = P.many1 (P.alphaNum <|> P.oneOf "|-")
pQuoted = P.char '\"' *> P.manyTill P.anyChar (P.char '\"') pQuoted = P.char '\"' *> P.manyTill P.anyChar (P.char '\"')
+1
View File
@@ -36,3 +36,4 @@ server-timing-enabled = false
server-unix-socket = "" server-unix-socket = ""
server-unix-socket-mode = "660" server-unix-socket-mode = "660"
admin-server-port = "" admin-server-port = ""
admin-server-config-enabled = false
@@ -36,3 +36,4 @@ server-timing-enabled = false
server-unix-socket = "" server-unix-socket = ""
server-unix-socket-mode = "660" server-unix-socket-mode = "660"
admin-server-port = "" admin-server-port = ""
admin-server-config-enabled = false
@@ -36,3 +36,4 @@ server-timing-enabled = false
server-unix-socket = "" server-unix-socket = ""
server-unix-socket-mode = "660" server-unix-socket-mode = "660"
admin-server-port = "" admin-server-port = ""
admin-server-config-enabled = false
+1
View File
@@ -36,3 +36,4 @@ server-timing-enabled = false
server-unix-socket = "" server-unix-socket = ""
server-unix-socket-mode = "660" server-unix-socket-mode = "660"
admin-server-port = "" admin-server-port = ""
admin-server-config-enabled = false
@@ -36,5 +36,6 @@ server-timing-enabled = true
server-unix-socket = "/tmp/pgrst_io_test.sock" server-unix-socket = "/tmp/pgrst_io_test.sock"
server-unix-socket-mode = "777" server-unix-socket-mode = "777"
admin-server-port = 3001 admin-server-port = 3001
admin-server-config-enabled = true
app.settings.test = "test" app.settings.test = "test"
app.settings.test2 = "test" app.settings.test2 = "test"
@@ -36,5 +36,6 @@ server-timing-enabled = false
server-unix-socket = "/tmp/pgrst_io_test.sock" server-unix-socket = "/tmp/pgrst_io_test.sock"
server-unix-socket-mode = "777" server-unix-socket-mode = "777"
admin-server-port = 3001 admin-server-port = 3001
admin-server-config-enabled = true
app.settings.test = "test" app.settings.test = "test"
app.settings.test2 = "test" app.settings.test2 = "test"
@@ -36,5 +36,6 @@ server-timing-enabled = true
server-unix-socket = "/tmp/pgrst_io_test.sock" server-unix-socket = "/tmp/pgrst_io_test.sock"
server-unix-socket-mode = "777" server-unix-socket-mode = "777"
admin-server-port = 3001 admin-server-port = 3001
admin-server-config-enabled = true
app.settings.test = "test" app.settings.test = "test"
app.settings.test2 = "test" app.settings.test2 = "test"
+1
View File
@@ -36,4 +36,5 @@ server-timing-enabled = false
server-unix-socket = "" server-unix-socket = ""
server-unix-socket-mode = "660" server-unix-socket-mode = "660"
admin-server-port = "" admin-server-port = ""
admin-server-config-enabled = false
app.settings.test = "Bool False" app.settings.test = "Bool False"
+1
View File
@@ -39,3 +39,4 @@ PGRST_SERVER_TIMING_ENABLED: true
PGRST_SERVER_UNIX_SOCKET: /tmp/pgrst_io_test.sock PGRST_SERVER_UNIX_SOCKET: /tmp/pgrst_io_test.sock
PGRST_SERVER_UNIX_SOCKET_MODE: 777 PGRST_SERVER_UNIX_SOCKET_MODE: 777
PGRST_ADMIN_SERVER_PORT: 3001 PGRST_ADMIN_SERVER_PORT: 3001
PGRST_ADMIN_SERVER_CONFIG_ENABLED: true
+1
View File
@@ -36,5 +36,6 @@ server-timing-enabled = true
server-unix-socket = "/tmp/pgrst_io_test.sock" server-unix-socket = "/tmp/pgrst_io_test.sock"
server-unix-socket-mode = "777" server-unix-socket-mode = "777"
admin-server-port = 3001 admin-server-port = 3001
admin-server-config-enabled = true
app.settings.test = "test" app.settings.test = "test"
app.settings.test2 = "test" app.settings.test2 = "test"
+9
View File
@@ -673,6 +673,15 @@ def test_admin_config(defaultenv):
"Should get a success response from the admin server containing current configuration" "Should get a success response from the admin server containing current configuration"
with run(env=defaultenv) as postgrest: with run(env=defaultenv) as postgrest:
response = postgrest.admin.get("/config")
assert response.status_code == 404
env = {
**defaultenv,
"PGRST_ADMIN_SERVER_CONFIG_ENABLED": "true",
}
with run(env=env) as postgrest:
response = postgrest.admin.get("/config") response = postgrest.admin.get("/config")
print(response.text) print(response.text)
assert response.status_code == 200 assert response.status_code == 200
@@ -382,3 +382,24 @@ spec = describe "custom media types" $ do
`shouldRespondWith` `shouldRespondWith`
[json| {"code":"PGRST107","details":null,"hint":null,"message":"None of these media types are available: undefined"} |] [json| {"code":"PGRST107","details":null,"hint":null,"message":"None of these media types are available: undefined"} |]
{ matchStatus = 406 } { matchStatus = 406 }
context "media type parser allowed characters" $ do
it "regression test allowing charset=utf-8" $
request methodPost "/rpc/overloaded_default"
[("Content-Type", "application/json; charset=utf-8")]
[json|{"must_param":1}|]
`shouldRespondWith`
[json|{"val":1}|]
{ matchStatus = 200
, matchHeaders = ["Content-Type" <:> "application/json; charset=utf-8"]
}
it "handle unrecognized parameters leniently" $ do
request methodPost "/rpc/overloaded_default"
[("Content-Type", "application/json; $$ unrecognized-chars=ignored $$")]
[json|{"must_param":1}|]
`shouldRespondWith`
[json|{"val":1}|]
{ matchStatus = 200
, matchHeaders = ["Content-Type" <:> "application/json; charset=utf-8"]
}
+1
View File
@@ -151,6 +151,7 @@ baseCfg = let secret = Just $ encodeUtf8 "reallyreallyreallyreallyverysafe" in
, configDbTxAllowOverride = True , configDbTxAllowOverride = True
, configDbTxRollbackAll = True , configDbTxRollbackAll = True
, configAdminServerPort = Nothing , configAdminServerPort = Nothing
, configAdminServerConfigEnabled = False
, configRoleSettings = mempty , configRoleSettings = mempty
, configRoleIsoLvl = mempty , configRoleIsoLvl = mempty
, configInternalSCSleep = Nothing , configInternalSCSleep = Nothing