public inbox for [email protected]
help / color / mirror / Atom feedFrom: Greg Sabino Mullane <[email protected]>
To: Andres Freund <[email protected]>
Cc: Antonin Houska <[email protected]>
Cc: Tom Lane <[email protected]>
Cc: [email protected] <[email protected]>
Subject: Re: POC: Carefully exposing information without authentication
Date: Tue, 24 Mar 2026 21:59:19 -0400
Message-ID: <CAKAnmmKxDD41mrnbM29+5d+OBrPRBr3N=T+LGxUcAKoG5JkM5A@mail.gmail.com> (raw)
In-Reply-To: <sqammd2ykcy34skbsewpfq4ftm6xl3qt5ebr2plwenoprtvdrm@ayddy4xqbkb5>
References: <CAKAnmm+T-CEDLmRezWfH-7ZEsFfD_kU2KY1TgB288K+wprB_4Q@mail.gmail.com>
<21076.1748617331@localhost>
<CAKAnmmJ77jeYZGXPBxb75U52ojNRUoKd6Za-T26xNPCouUeV8g@mail.gmail.com>
<[email protected]>
<CAKAnmm+RQbLFv1F35ZTRZfMRigwN0LN0KKRocLLpwSshBTZxvg@mail.gmail.com>
<CAKAnmmLdrvdCMbAQbfiWY3q=zv+-11zZk+jweTRVCJrNL=aD4A@mail.gmail.com>
<CAKAnmmKxP7bOO7QOLdSk8dYoUxFRus2XC1nEbk6En9GgV_4JbA@mail.gmail.com>
<11894.1767966998@localhost>
<CAKAnmm+-+2aZiK-wtwSCEpfOJbU7-PnwofByhr0F_XQB4wUJNw@mail.gmail.com>
<CAKAnmmJUGidY7cjD0rHtNVisQksy3u1KszHXkMCNPWYhMKPEvw@mail.gmail.com>
<sqammd2ykcy34skbsewpfq4ftm6xl3qt5ebr2plwenoprtvdrm@ayddy4xqbkb5>
Thank you for looking over this. New version attached.
On Tue, Feb 17, 2026 at 2:58 PM Andres Freund <[email protected]> wrote:
> What about direct TLS connections?
Not handled.
How can a cluster coordinator trust unauthenticated plain text
> communication that can just be man-in-the-middled?
>
They cannot. But that's why this is only exposing non-critical information.
Right now the security scanners that are banging on port 5432 and scraping
the returned error lines are not worried about man-in-the-middle. :)
Obviously, if your threat model is people capturing and modifying
non-encrypted traffic to your Postgres server, you would not use this.
It's not obvious that it's a good idea to expose this on the same socket as
> normal client connections. IMO you'd want to limit this to a smaller set
> of interfaces than normal client connections.
>
I'm not entirely clear what that smaller set would mean in practice.
> IIRC the socket is in blocking mode at this point (that's only changed in
> pq_init()), therefore this might actually block? While it's unlikely, I
> don't see any guarantee that a single receive would actually get the whole
> message from the client either, so this seems like it might fail spuriously.
>
Yes, there are some very unlikely edge cases, but this is meant to be good
enough, not a perfectly bulletproof HTTP server. Clients should try again
on failures. Which if they do occur for this trivial amount of traffic
probably indicates much bigger problems.
If we were to do this, I'd recommend a single expose GUC that has the
> different values as a comma separated list, instead a growing list of GUCs.
>
Done - see attached for a new version which consolidates the bools into a
single comma-separated GUC called "expose_information". I also added some
docs, and changed the "replica" to return "REPLICA" instead of "RECOVERY".
I like the latter better, but replica lines up better with existing tools.
--
Cheers,
Greg
Attachments:
[application/octet-stream] 0006-Allow-specific-information-to-be-output-directly-by-Postgres.patch (19.1K, 3-0006-Allow-specific-information-to-be-output-directly-by-Postgres.patch)
download | inline diff:
From 257aaa706cb272148d877d0f67fce2bc1e49a39b Mon Sep 17 00:00:00 2001
From: Greg Sabino Mullane <[email protected]>
Date: Tue, 24 Mar 2026 21:15:23 -0400
Subject: [PATCH] Allow-specific-information-to-be-output-directly-by-Postgres
---
doc/src/sgml/config.sgml | 61 ++++
src/backend/tcop/backend_startup.c | 262 ++++++++++++++++++
src/backend/utils/misc/guc_parameters.dat | 10 +
src/backend/utils/misc/postgresql.conf.sample | 7 +
src/include/tcop/backend_startup.h | 2 +
src/include/utils/guc_hooks.h | 2 +
src/test/modules/test_misc/meson.build | 1 +
src/test/modules/test_misc/t/011_expose.pl | 121 ++++++++
8 files changed, 466 insertions(+)
create mode 100644 src/test/modules/test_misc/t/011_expose.pl
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 8cdd826fbd3..a32c7e7f6a0 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -643,6 +643,67 @@ include_dir 'conf.d'
<variablelist>
+ <varlistentry id="guc-expose-information" xreflabel="expose_information">
+ <term><varname>expose_information</varname> (<type>string</type>)
+ <indexterm>
+ <primary><varname>expose_information</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Allows for specific information to be returned from the servers without
+ requiring a login. Requests should come in as a simple HTTP request as a
+ GET or HEAD to the PostgreSQL port.
+ The default is the empty string, <literal>''</literal>, which
+ prevents any information from being output. The following options may be
+ specified alone or in a comma-separated list:
+ </para>
+
+ <table id="expose-information-options">
+ <title>Expose Information Options</title>
+ <tgroup cols="2">
+ <colspec colname="col1" colwidth="1*"/>
+ <colspec colname="col2" colwidth="2*"/>
+ <thead>
+ <row>
+ <entry>Name</entry>
+ <entry>Description</entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry><literal>replica</literal></entry>
+ <entry>Reports if the server is a replica (i.e. is in recovery mode) or not. If the request is <literal>HEAD /replica</literal>,
+ then an HTTP response code of 200 (yes it is a replica) or 503 (not a replica) is returned. This
+ can be used as a drop-in replacement for the same functionality provided by the Patroni program.
+ For the request <literal>GET /replica</literal> or <literal>GET /info</literal>,
+ the string <literal>REPLICA: 1</literal> or <literal>REPLICA: 0</literal> is returned.</entry>
+ </row>
+
+ <row>
+ <entry><literal>sysid</literal></entry>
+ <entry>Returns the system identifier of the server. This can be useful to determine if the underlying
+ server has changed, as the initdb program will always generate a new system identifier.
+ For the request <literal>GET /sysid</literal> or <literal>GET /info</literal>,
+ the string <literal>SYSID: 12345</literal> is returned, in which "12345" will be
+ the specific system identifier (typically a 20-digit number)</entry>
+ </row>
+
+ <row>
+ <entry><literal>version</literal></entry>
+ <entry>Returns the current version of the server. Specifically, the value of
+ <literal>server_version_num</literal>.
+ For the request <literal>GET /version</literal> or <literal>GET /info</literal>,
+ the string <literal>VERSION: 190000</literal> is returned (for this example,
+ the version of Postgres is 19.0)</entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-listen-addresses" xreflabel="listen_addresses">
<term><varname>listen_addresses</varname> (<type>string</type>)
<indexterm>
diff --git a/src/backend/tcop/backend_startup.c b/src/backend/tcop/backend_startup.c
index 5abf276c898..271ea9ddcb8 100644
--- a/src/backend/tcop/backend_startup.c
+++ b/src/backend/tcop/backend_startup.c
@@ -46,6 +46,33 @@
bool Trace_connection_negotiation = false;
uint32 log_connections = 0;
char *log_connections_string = NULL;
+int Expose_information = 0;
+char *Expose_information_string = NULL;
+
+/* Expose information bitmap */
+#define EXPOSE_INFO_REPLICA 1
+#define EXPOSE_INFO_SYSID 2
+#define EXPOSE_INFO_VERSION 4
+
+#define EXPOSE_MIN_QUERY 9 /* Shortest possible line: "Get /info" */
+#define EXPOSE_MAX_QUERY 16 /* Longest possible GET line */
+
+typedef enum
+{
+ EXPOSE_NOTHING,
+ EXPOSE_HEAD_REPLICA,
+ EXPOSE_GET_ALL,
+ EXPOSE_GET_REPLICA,
+ EXPOSE_GET_SYSID,
+ EXPOSE_GET_VERSION,
+} ExposeReturnType;
+
+typedef struct
+{
+ const char *endpoint;
+ int require;
+ ExposeReturnType type;
+} endpoint_action;
/* Other globals */
@@ -65,6 +92,7 @@ static void SendNegotiateProtocolVersion(List *unrecognized_protocol_options);
static void process_startup_packet_die(SIGNAL_ARGS);
static void StartupPacketTimeoutHandler(void);
static bool validate_log_connections_options(List *elemlist, uint32 *flags);
+static bool ExposeInformation(pgsocket fd);
/*
* Entry point for a new backend process.
@@ -148,6 +176,15 @@ BackendInitialize(ClientSocket *client_sock, CAC_state cac)
StringInfoData ps_data;
MemoryContext oldcontext;
+ /*
+ * Scan for a simple GET / HEAD request. If this is detected and handled,
+ * we are done and can immediately exit.
+ */
+ if ((Expose_information > 0)
+ && ExposeInformation(client_sock->sock))
+ _exit(0); /* Safe to use exit: no state or resources
+ * created yet */
+
/* Tell fd.c about the long-lived FD associated with the client_sock */
ReserveExternalFD();
@@ -1075,6 +1112,72 @@ next: ;
}
+/*
+ * GUC check_hook for expose_information
+ */
+bool
+check_expose_information(char **newval, void **extra, GucSource source)
+{
+ char *rawstring;
+ List *elemlist;
+ ListCell *l;
+ int newexpose = 0;
+ int *myextra;
+
+ /* Need a modifiable copy of string */
+ rawstring = pstrdup(*newval);
+
+ /* Parse string into list of identifiers */
+ if (!SplitIdentifierString(rawstring, ',', &elemlist))
+ {
+ /* syntax error in list */
+ GUC_check_errdetail("List syntax is invalid.");
+ pfree(rawstring);
+ list_free(elemlist);
+ return false;
+ }
+
+ foreach(l, elemlist)
+ {
+ char *tok = (char *) lfirst(l);
+
+ if (pg_strcasecmp(tok, "replica") == 0)
+ newexpose |= EXPOSE_INFO_REPLICA;
+ else if (pg_strcasecmp(tok, "sysid") == 0)
+ newexpose |= EXPOSE_INFO_SYSID;
+ else if (pg_strcasecmp(tok, "version") == 0)
+ newexpose |= EXPOSE_INFO_VERSION;
+ else
+ {
+ GUC_check_errdetail("Unrecognized key word: \"%s\".", tok);
+ pfree(rawstring);
+ list_free(elemlist);
+ return false;
+ }
+ }
+
+ pfree(rawstring);
+ list_free(elemlist);
+
+ myextra = (int *) guc_malloc(LOG, sizeof(int));
+ if (!myextra)
+ return false;
+ *myextra = newexpose;
+ *extra = myextra;
+
+ return true;
+}
+
+/*
+ * GUC assign_hook for expose_information
+ */
+void
+assign_expose_information(const char *newval, void *extra)
+{
+ Expose_information = *((int *) extra);
+}
+
+
/*
* GUC check hook for log_connections
*/
@@ -1127,3 +1230,162 @@ assign_log_connections(const char *newval, void *extra)
{
log_connections = *((int *) extra);
}
+
+/*
+ * ExposeInformation
+ *
+ * Handle early socket probe before full backend startup.
+ * Responds to small set of predefined endpoints (e.g. GET /info)
+ *
+ * Requires the expose_information GUC to be non-empty
+ *
+ * Returns true if any endpoint is recognized.
+ */
+
+static bool
+ExposeInformation(pgsocket fd)
+{
+ static const endpoint_action endpoint_actions[] =
+ {
+ {
+ "HEAD /replica", EXPOSE_INFO_REPLICA, EXPOSE_HEAD_REPLICA
+ },
+ {
+ "GET /replica", EXPOSE_INFO_REPLICA, EXPOSE_GET_REPLICA
+ },
+ {
+ "GET /sysid", EXPOSE_INFO_SYSID, EXPOSE_GET_SYSID
+ },
+ {
+ "GET /version", EXPOSE_INFO_VERSION, EXPOSE_GET_VERSION
+ },
+ {
+ "GET /info", 0, EXPOSE_GET_ALL
+ }
+ };
+
+ ssize_t n;
+ char buf[EXPOSE_MAX_QUERY + 1];
+ ExposeReturnType type;
+
+ Assert(Expose_information > 0);
+
+ do
+ {
+ n = recv(fd, buf, EXPOSE_MAX_QUERY, MSG_PEEK);
+ } while (n < 0 && errno == EINTR);
+
+ /*
+ * Leave as soon as possible if no chance we are interested. We also leave
+ * on partial reads from slow clients. Note that we return false for n ==
+ * -1
+ */
+ if (n < EXPOSE_MIN_QUERY)
+ return false;
+
+ buf[n] = '\0';
+
+ type = EXPOSE_NOTHING;
+ for (int i = 0; i < lengthof(endpoint_actions); i++)
+ {
+ if (
+ pg_strncasecmp(buf, endpoint_actions[i].endpoint, strlen(endpoint_actions[i].endpoint)) == 0
+ &&
+ ((endpoint_actions[i].require == 0)
+ ||
+ (Expose_information & endpoint_actions[i].require)
+ ))
+ {
+ type = endpoint_actions[i].type;
+ break;
+ }
+ }
+
+ if (type == EXPOSE_NOTHING)
+ return false;
+
+ {
+ static const char http_version[] = "HTTP/1.1";
+ static const char http_type[] = "Content-Type: text/plain";
+ static const char http_conn[] = "Connection: close";
+ static const char http_len[] = "Content-Length";
+
+ StringInfoData msg;
+
+ if (type == EXPOSE_HEAD_REPLICA)
+ {
+ /*
+ * Caller only cares about the HTTP response code, so no content
+ * needed
+ */
+
+ initStringInfoExt(&msg, 64);
+
+ appendStringInfo(&msg,
+ "%s %s\r\n"
+ "%s\r\n"
+ "%s\r\n\r\n",
+ http_version,
+ (RecoveryInProgress() ? "200 OK" : "503 Service Unavailable"),
+ http_type,
+ http_conn
+ );
+ }
+ else
+ {
+ StringInfoData content;
+
+ initStringInfoExt(&content, 64);
+
+ if ((Expose_information & EXPOSE_INFO_REPLICA)
+ &&
+ (type == EXPOSE_GET_ALL || type == EXPOSE_GET_REPLICA))
+ appendStringInfo(&content, "%s%d\r\n",
+ type == EXPOSE_GET_ALL ? "REPLICA: " : "",
+ RecoveryInProgress() ? 1 : 0);
+ if ((Expose_information & EXPOSE_INFO_SYSID)
+ &&
+ (type == EXPOSE_GET_ALL || type == EXPOSE_GET_SYSID))
+ appendStringInfo(&content, "%s" UINT64_FORMAT "\r\n",
+ type == EXPOSE_GET_ALL ? "SYSID: " : "",
+ GetSystemIdentifier());
+ if ((Expose_information & EXPOSE_INFO_VERSION)
+ &&
+ (type == EXPOSE_GET_ALL || type == EXPOSE_GET_VERSION))
+ appendStringInfo(&content, "%s%d\r\n",
+ type == EXPOSE_GET_ALL ? "VERSION: " : "",
+ PG_VERSION_NUM);
+
+ initStringInfoExt(&msg, 256);
+
+ appendStringInfo(&msg,
+ "%s 200 OK\r\n"
+ "%s\r\n"
+ "%s: %d\r\n"
+ "%s\r\n\r\n"
+ "%s",
+ http_version,
+ http_type,
+ http_len, content.len,
+ http_conn,
+ content.data
+ );
+
+ pfree(content.data);
+ }
+
+ do
+ {
+ n = send(fd, msg.data, msg.len, 0);
+ } while (n < 0 && errno == EINTR);
+
+ pfree(msg.data);
+
+ if (n < 0)
+ elog(DEBUG1, "could not send to client: %m");
+
+ return true;
+
+ }
+
+}
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 0c9854ad8fc..8c8ad0d8d0a 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1010,6 +1010,16 @@
boot_val => 'false',
},
+{ name => 'expose_information', type => 'string', context => 'PGC_SIGHUP', group => 'CONN_AUTH_AUTH',
+ short_desc => 'Expose limited information without needing to login',
+ long_desc => 'Valid values are combinations of "replica", "sysid", and "version"',
+ flags => 'GUC_LIST_INPUT',
+ variable => 'Expose_information_string',
+ boot_val => '""',
+ check_hook => 'check_expose_information',
+ assign_hook => 'assign_expose_information',
+},
+
{ name => 'extension_control_path', type => 'string', context => 'PGC_SUSET', group => 'CLIENT_CONN_OTHER',
short_desc => 'Sets the path for extension control files.',
long_desc => 'The remaining extension script and secondary control files are then loaded from the same directory where the primary control file was found.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index e4abe6c0077..5e361fd4b58 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -93,6 +93,13 @@
# disconnection while running queries;
# 0 for never
+# - Expose information -
+
+expose_information = '' # comma-separated list of items to expose
+ # replica = if the server is in recovery or not
+ # sysid = the current system identifier for this server
+ # version = the current version of this server
+
# - Authentication -
#authentication_timeout = 1min # 1s-600s
diff --git a/src/include/tcop/backend_startup.h b/src/include/tcop/backend_startup.h
index d486f926319..6204dd98e81 100644
--- a/src/include/tcop/backend_startup.h
+++ b/src/include/tcop/backend_startup.h
@@ -20,6 +20,8 @@
extern PGDLLIMPORT bool Trace_connection_negotiation;
extern PGDLLIMPORT uint32 log_connections;
extern PGDLLIMPORT char *log_connections_string;
+extern PGDLLIMPORT int Expose_information;
+extern PGDLLIMPORT char *Expose_information_string;
/* Other globals */
extern PGDLLIMPORT struct ConnectionTiming conn_timing;
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b01697c1f60..04d9f024d26 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -62,6 +62,8 @@ extern void assign_default_text_search_config(const char *newval, void *extra);
extern bool check_default_with_oids(bool *newval, void **extra,
GucSource source);
extern const char *show_effective_wal_level(void);
+extern bool check_expose_information(char **newval, void **extra, GucSource source);
+extern void assign_expose_information(const char *newval, void *extra);
extern bool check_huge_page_size(int *newval, void **extra, GucSource source);
extern void assign_io_method(int newval, void *extra);
extern bool check_io_max_concurrency(int *newval, void **extra, GucSource source);
diff --git a/src/test/modules/test_misc/meson.build b/src/test/modules/test_misc/meson.build
index 6e8db1621a7..c40a0455708 100644
--- a/src/test/modules/test_misc/meson.build
+++ b/src/test/modules/test_misc/meson.build
@@ -19,6 +19,7 @@ tests += {
't/008_replslot_single_user.pl',
't/009_log_temp_files.pl',
't/010_index_concurrently_upsert.pl',
+ 't/011_expose.pl',
],
# The injection points are cluster-wide, so disable installcheck
'runningcheck': false,
diff --git a/src/test/modules/test_misc/t/011_expose.pl b/src/test/modules/test_misc/t/011_expose.pl
new file mode 100644
index 00000000000..df97b98096b
--- /dev/null
+++ b/src/test/modules/test_misc/t/011_expose.pl
@@ -0,0 +1,121 @@
+# Copyright (c) 2026, PostgreSQL Global Development Group
+
+# Test gathering information before authentication via expose_* variables
+
+# Force use of TCP/IP - must be called before the 'use' section
+INIT{ $PostgreSQL::Test::Utils::use_unix_sockets = 0; }
+
+use strict;
+use warnings;
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+my $node = PostgreSQL::Test::Cluster->new('node1');
+
+# Set as logical here so we can restart it as a replica later
+$node->init(allows_streaming => 'logical');
+$node->start;
+
+my $server_version = $node->safe_psql('postgres', 'show server_version_num');
+my $bindir = $node->config_data('--bindir');
+my $datadir = $node->data_dir;
+my $cdata = qx{$bindir/pg_controldata -D $datadir 2>&1};
+my ($sysid) = $cdata =~ /Database system identifier:\s+(\d+)/;
+my $receive_length = 200;
+
+my ($socket, $response, $test);
+
+sub call_socket {
+ my $string = shift;
+ $socket->close() if defined $socket;
+ $socket = $node->raw_connect();
+ $socket->send($string);
+ $response = '';
+ select(undef, undef, undef, 0.1);
+ $socket->recv($response, $receive_length);
+ return;
+}
+
+$test = 'GET /info returns nothing when nothing is listening';
+call_socket('GET /info');
+is ($response, '', $test);
+
+$test = 'HEAD /replica returns nothing when nothing is listening';
+call_socket('HEAD /replica');
+is ($response, '', $test);
+
+$node->append_conf('postgresql.conf', "expose_information = 'replica'");
+$node->reload();
+
+$test = q{GET /replica returns HTTP code 200 when expose_information contains 'replica' (primary)};
+call_socket('GET /replica');
+like ($response, qr{^HTTP/1.1 200 }, $test);
+
+$test = q{GET /replica returns "0" when expose_information contains 'replica' (primary)};
+like ($response, qr{\r\n0\r\n}, $test);
+
+$test = q{HEAD /replica returns HTTP code 503 when expose_information contains 'replica' (primary)};
+call_socket('HEAD /replica');
+like ($response, qr{^HTTP/1.1 503 }, $test);
+
+$test = q{GET /info returns "REPLICA: 0" when expose_information contains 'replica' (primary)};
+call_socket('GET /info');
+like ($response, qr{REPLICA: 0\r\n}, $test);
+
+$test = q{GET /info does not return version information when expose_information does not contain 'version'};
+unlike ($response, qr{VERSION}, $test);
+
+$test = q{GET /info does not return sysid information when expose_information does not contain 'sysid'};
+unlike ($response, qr{SYSID}, $test);
+
+$node->append_conf('postgresql.conf', "expose_information= 'replica,sysid,version'");
+$node->reload();
+
+$test = q{GET /info returns correct version when expose_information contains 'version'};
+call_socket('GET /info');
+like ($response, qr/VERSION: $server_version/, $test);
+
+$test = q{GET /info returns correct value when expose_information contains 'sysid'};
+like ($response, qr/SYSID: $sysid/, $test);
+
+$test = q{Get /sysid returns correct value when expose_information contains 'sysid'};
+call_socket('Get /sysid'); ## Not required to be all uppercase according to the spec!
+like ($response, qr/^$sysid\r\n/m, $test);
+
+$test = q{GET /version returns correct value when expose_information contains 'version'};
+call_socket('GET /version');
+like ($response, qr/^$server_version\r\n/m, $test);
+
+$test = 'GET /foobar returns nothing';
+call_socket('GET /foobar');
+is ($response, '', $test);
+
+$node->set_standby_mode();
+$node->restart();
+
+$test = q{GET /replica returns HTTP code 200 when expose_information contains 'replica' (replica)};
+call_socket('GET /replica');
+like ($response, qr{^HTTP/1.1 200 }, $test);
+
+$test = q{GET /replica returns "1" when expose_information contains 'replica' (replica)};
+like ($response, qr{^1\r\n}m, $test);
+
+$test = q{HEAD /replica returns HTTP code 200 when expose_information contains 'replica' (replica)};
+call_socket('HEAD /replica');
+like ($response, qr{^HTTP/1.1 200 }, $test);
+
+$test = q{GET /info returns "REPLICA: 1" when expose_information contains 'replica' (replica)};
+call_socket('GET /info');
+like ($response, qr/REPLICA: 1/, $test);
+
+$node->append_conf('postgresql.conf', "expose_information=''");
+$node->reload();
+
+$test = q{GET /version returns nothing after expose_information no longer has 'version'};
+call_socket('GET /version');
+is ($response, '', $test);
+
+$socket->close();
+
+done_testing();
--
2.47.3
view thread (12+ messages)
reply
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Reply to all the recipients using the --to and --cc options:
reply via email
To: [email protected]
Cc: [email protected], [email protected], [email protected], [email protected], [email protected]
Subject: Re: POC: Carefully exposing information without authentication
In-Reply-To: <CAKAnmmKxDD41mrnbM29+5d+OBrPRBr3N=T+LGxUcAKoG5JkM5A@mail.gmail.com>
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox