From 1f28efa9bd847ad0274c0240677e562d5ba6f3c0 Mon Sep 17 00:00:00 2001 From: steve-chavez Date: Wed, 30 Apr 2025 18:48:36 -0500 Subject: [PATCH] fix: don't enable admin server `/config` by default This now requires setting `admin-server-config-enabled`. --- CHANGELOG.md | 5 ++++ docs/references/admin_server.rst | 6 ++++- docs/references/configuration.rst | 24 +++++++++++++++++++ src/PostgREST/Admin.hs | 7 ++++-- src/PostgREST/CLI.hs | 3 +++ src/PostgREST/Config.hs | 3 +++ test/io/configs/expected/aliases.config | 1 + .../configs/expected/boolean-numeric.config | 1 + .../io/configs/expected/boolean-string.config | 1 + test/io/configs/expected/defaults.config | 1 + ...efaults-with-db-other-authenticator.config | 1 + .../expected/no-defaults-with-db.config | 1 + test/io/configs/expected/no-defaults.config | 1 + test/io/configs/expected/types.config | 1 + test/io/configs/no-defaults-env.yaml | 1 + test/io/configs/no-defaults.config | 1 + test/io/test_io.py | 9 +++++++ test/spec/SpecHelper.hs | 1 + 18 files changed, 65 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d276c3f0b..502d668b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,11 @@ This project adheres to [Semantic Versioning](http://semver.org/). ## Unreleased +### 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 diff --git a/docs/references/admin_server.rst b/docs/references/admin_server.rst index 09faecc20..bfaeb7155 100644 --- a/docs/references/admin_server.rst +++ b/docs/references/admin_server.rst @@ -55,10 +55,12 @@ Metrics Provides :ref:`metrics`. +.. _runtime_config: + 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 @@ -72,6 +74,8 @@ Provides a ``config`` endpoint that returns the runtime :ref:`configuration`. db-channel-enabled = false ... +.. _runtime_schema_cache: + Runtime Schema Cache ==================== diff --git a/docs/references/configuration.rst b/docs/references/configuration.rst index 8b02d3e1e..210584073 100644 --- a/docs/references/configuration.rst +++ b/docs/references/configuration.rst @@ -161,6 +161,30 @@ admin-server-port 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.* diff --git a/src/PostgREST/Admin.hs b/src/PostgREST/Admin.hs index 325e95c06..1d2360e4f 100644 --- a/src/PostgREST/Admin.hs +++ b/src/PostgREST/Admin.hs @@ -56,8 +56,11 @@ admin appState req respond = do in respond $ Wai.responseLBS status [] mempty ["config"] -> do - config <- AppState.getConfig appState - respond $ Wai.responseLBS HTTP.status200 [] (LBS.fromStrict $ encodeUtf8 $ Config.toText config) + config@Config.AppConfig{configAdminServerConfigEnabled} <- AppState.getConfig appState + if configAdminServerConfigEnabled then + respond $ Wai.responseLBS HTTP.status200 [] (LBS.fromStrict $ encodeUtf8 $ Config.toText config) + else + respond $ Wai.responseLBS HTTP.status404 [] mempty ["schema_cache"] -> do sCache <- AppState.getSchemaCache appState respond $ Wai.responseLBS HTTP.status200 [] (maybe mempty JSON.encode sCache) diff --git a/src/PostgREST/CLI.hs b/src/PostgREST/CLI.hs index 0ba71144b..e8e50bcd5 100644 --- a/src/PostgREST/CLI.hs +++ b/src/PostgREST/CLI.hs @@ -128,6 +128,9 @@ exampleConfigFile = [str|## Admin server used for checks. It's disabled by default unless a port is specified. |# 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 |# db-anon-role = "anon" | diff --git a/src/PostgREST/Config.hs b/src/PostgREST/Config.hs index 4a885c7f6..02e5591ca 100644 --- a/src/PostgREST/Config.hs +++ b/src/PostgREST/Config.hs @@ -110,6 +110,7 @@ data AppConfig = AppConfig , configServerUnixSocket :: Maybe FilePath , configServerUnixSocketMode :: FileMode , configAdminServerPort :: Maybe Int + , configAdminServerConfigEnabled :: Bool , configRoleSettings :: RoleSettings , configRoleIsoLvl :: RoleIsolationLvl , configInternalSCSleep :: Maybe Int32 @@ -180,6 +181,7 @@ toText conf = ,("server-unix-socket", q . maybe mempty T.pack . configServerUnixSocket) ,("server-unix-socket-mode", q . T.pack . showSocketMode) ,("admin-server-port", maybe "\"\"" show . configAdminServerPort) + ,("admin-server-config-enabled", T.toLower . show . configAdminServerConfigEnabled) ] -- quote all app.settings @@ -286,6 +288,7 @@ parser optPath env dbSettings roleSettings roleIsolationLvl = <*> (fmap T.unpack <$> optString "server-unix-socket") <*> parseSocketFileMode "server-unix-socket-mode" <*> optInt "admin-server-port" + <*> (fromMaybe False <$> optBool "admin-server-config-enabled") <*> pure roleSettings <*> pure roleIsolationLvl <*> optInt "internal-schema-cache-sleep" diff --git a/test/io/configs/expected/aliases.config b/test/io/configs/expected/aliases.config index 00d9414e0..25da87327 100644 --- a/test/io/configs/expected/aliases.config +++ b/test/io/configs/expected/aliases.config @@ -36,3 +36,4 @@ server-timing-enabled = false server-unix-socket = "" server-unix-socket-mode = "660" admin-server-port = "" +admin-server-config-enabled = false diff --git a/test/io/configs/expected/boolean-numeric.config b/test/io/configs/expected/boolean-numeric.config index 5c025e0c1..553bdb296 100644 --- a/test/io/configs/expected/boolean-numeric.config +++ b/test/io/configs/expected/boolean-numeric.config @@ -36,3 +36,4 @@ server-timing-enabled = false server-unix-socket = "" server-unix-socket-mode = "660" admin-server-port = "" +admin-server-config-enabled = false diff --git a/test/io/configs/expected/boolean-string.config b/test/io/configs/expected/boolean-string.config index 5c025e0c1..553bdb296 100644 --- a/test/io/configs/expected/boolean-string.config +++ b/test/io/configs/expected/boolean-string.config @@ -36,3 +36,4 @@ server-timing-enabled = false server-unix-socket = "" server-unix-socket-mode = "660" admin-server-port = "" +admin-server-config-enabled = false diff --git a/test/io/configs/expected/defaults.config b/test/io/configs/expected/defaults.config index 1d0c095d6..0c8719392 100644 --- a/test/io/configs/expected/defaults.config +++ b/test/io/configs/expected/defaults.config @@ -36,3 +36,4 @@ server-timing-enabled = false server-unix-socket = "" server-unix-socket-mode = "660" admin-server-port = "" +admin-server-config-enabled = false diff --git a/test/io/configs/expected/no-defaults-with-db-other-authenticator.config b/test/io/configs/expected/no-defaults-with-db-other-authenticator.config index 3717e99e5..b5efbe3ca 100644 --- a/test/io/configs/expected/no-defaults-with-db-other-authenticator.config +++ b/test/io/configs/expected/no-defaults-with-db-other-authenticator.config @@ -36,5 +36,6 @@ server-timing-enabled = true server-unix-socket = "/tmp/pgrst_io_test.sock" server-unix-socket-mode = "777" admin-server-port = 3001 +admin-server-config-enabled = true app.settings.test = "test" app.settings.test2 = "test" diff --git a/test/io/configs/expected/no-defaults-with-db.config b/test/io/configs/expected/no-defaults-with-db.config index 5f78327ba..ac8eee791 100644 --- a/test/io/configs/expected/no-defaults-with-db.config +++ b/test/io/configs/expected/no-defaults-with-db.config @@ -36,5 +36,6 @@ server-timing-enabled = false server-unix-socket = "/tmp/pgrst_io_test.sock" server-unix-socket-mode = "777" admin-server-port = 3001 +admin-server-config-enabled = true app.settings.test = "test" app.settings.test2 = "test" diff --git a/test/io/configs/expected/no-defaults.config b/test/io/configs/expected/no-defaults.config index 57f6b2dc0..69b05d630 100644 --- a/test/io/configs/expected/no-defaults.config +++ b/test/io/configs/expected/no-defaults.config @@ -36,5 +36,6 @@ server-timing-enabled = true server-unix-socket = "/tmp/pgrst_io_test.sock" server-unix-socket-mode = "777" admin-server-port = 3001 +admin-server-config-enabled = true app.settings.test = "test" app.settings.test2 = "test" diff --git a/test/io/configs/expected/types.config b/test/io/configs/expected/types.config index b363c94bb..cc0d927b4 100644 --- a/test/io/configs/expected/types.config +++ b/test/io/configs/expected/types.config @@ -36,4 +36,5 @@ server-timing-enabled = false server-unix-socket = "" server-unix-socket-mode = "660" admin-server-port = "" +admin-server-config-enabled = false app.settings.test = "Bool False" diff --git a/test/io/configs/no-defaults-env.yaml b/test/io/configs/no-defaults-env.yaml index 915497b18..d1d5f29e7 100644 --- a/test/io/configs/no-defaults-env.yaml +++ b/test/io/configs/no-defaults-env.yaml @@ -39,3 +39,4 @@ PGRST_SERVER_TIMING_ENABLED: true PGRST_SERVER_UNIX_SOCKET: /tmp/pgrst_io_test.sock PGRST_SERVER_UNIX_SOCKET_MODE: 777 PGRST_ADMIN_SERVER_PORT: 3001 +PGRST_ADMIN_SERVER_CONFIG_ENABLED: true diff --git a/test/io/configs/no-defaults.config b/test/io/configs/no-defaults.config index 1f54505a3..fcde277d2 100644 --- a/test/io/configs/no-defaults.config +++ b/test/io/configs/no-defaults.config @@ -36,5 +36,6 @@ server-timing-enabled = true server-unix-socket = "/tmp/pgrst_io_test.sock" server-unix-socket-mode = "777" admin-server-port = 3001 +admin-server-config-enabled = true app.settings.test = "test" app.settings.test2 = "test" diff --git a/test/io/test_io.py b/test/io/test_io.py index b60f980b1..0577a362e 100644 --- a/test/io/test_io.py +++ b/test/io/test_io.py @@ -673,6 +673,15 @@ def test_admin_config(defaultenv): "Should get a success response from the admin server containing current configuration" 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") print(response.text) assert response.status_code == 200 diff --git a/test/spec/SpecHelper.hs b/test/spec/SpecHelper.hs index e4e64b41b..034bd0abc 100644 --- a/test/spec/SpecHelper.hs +++ b/test/spec/SpecHelper.hs @@ -151,6 +151,7 @@ baseCfg = let secret = Just $ encodeUtf8 "reallyreallyreallyreallyverysafe" in , configDbTxAllowOverride = True , configDbTxRollbackAll = True , configAdminServerPort = Nothing + , configAdminServerConfigEnabled = False , configRoleSettings = mempty , configRoleIsoLvl = mempty , configInternalSCSleep = Nothing