Skip to content

Commit ed54369

Browse files
jouirhackorum
authored andcommitted
Implement server-side support for the PROXY protocol
Add support for HAProxy's PROXY protocol, versions 1 and 2, so the real client address can be recovered from connections arriving through a trusted proxy. The new proxy_networks GUC lists the networks whose peers are allowed to prepend a PROXY header to declare the originating client. This list supports unix sockets with the "unix" token. It is empty by default, which disables the feature. The header is parsed lazily. The server only looks for it on connections coming from a trusted network and falls through to the normal startup path otherwise. Existing clients that are not from the proxy networks and that do not speak the PROXY protocol are unaffected. For connections from a trusted proxy, client_addr and client_port in pg_stat_activity and the host-based authentication checks reflect the address from the header, while the proxy's own endpoint is exposed via the new proxy_addr, proxy_hostname and proxy_port columns. The %H and %R escapes expose the proxy information in log_line_prefix. The proxy host and port are also emitted in the CSV and JSON log formats.
1 parent 44056f6 commit ed54369

36 files changed

Lines changed: 2131 additions & 16 deletions

doc/src/sgml/client-auth.sgml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -421,6 +421,16 @@ include_dir <replaceable>directory</replaceable>
421421
These fields do not apply to <literal>local</literal> records.
422422
</para>
423423

424+
<note>
425+
<para>
426+
When a connection arrives through a connection-forwarding proxy and a
427+
PROXY protocol header is accepted, the address matched against this
428+
field is the client address declared in that header, not the proxy's
429+
own address. See <xref linkend="guc-proxy-networks"/> for
430+
details.
431+
</para>
432+
</note>
433+
424434
<note>
425435
<para>
426436
Users sometimes wonder why host names are handled
@@ -1708,6 +1718,17 @@ omicron bryanh guest1
17081718
since <productname>PostgreSQL</productname> does not have any way to decrypt the
17091719
returned string to determine the actual user name.
17101720
</para>
1721+
1722+
<para>
1723+
Ident authentication is rejected for connections received through
1724+
the <link linkend="protocol-flow-proxy">PROXY protocol</link>. A client
1725+
connected to the proxy rather than to the server. The port pair
1726+
<replaceable>X</replaceable> and <replaceable>Y</replaceable> known to the
1727+
server does not describe any connection that the client's ident server has
1728+
a record of, and the query would reach the ident server from the server's
1729+
address rather than the proxy's. Any answer would therefore be
1730+
meaningless.
1731+
</para>
17111732
</sect1>
17121733

17131734
<sect1 id="auth-peer">

doc/src/sgml/config.sgml

Lines changed: 72 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -712,6 +712,51 @@ include_dir 'conf.d'
712712
</listitem>
713713
</varlistentry>
714714

715+
<varlistentry id="guc-proxy-networks" xreflabel="proxy_networks">
716+
<term><varname>proxy_networks</varname> (<type>string</type>)
717+
<indexterm>
718+
<primary><varname>proxy_networks</varname> configuration parameter</primary>
719+
</indexterm>
720+
<indexterm>
721+
<primary>PROXY protocol</primary>
722+
</indexterm>
723+
</term>
724+
<listitem>
725+
<para>
726+
Specifies the networks from which the server will accept a
727+
PROXY protocol header. When the server is reached through a
728+
connection-forwarding proxy (like <productname>HAProxy</productname>),
729+
the proxy can prepend a small header, as defined by the
730+
<ulink url="https://www.haproxy.org/download/2.9/doc/proxy-protocol.txt">PROXY
731+
protocol</ulink>, that declares the address of the real client.
732+
When such a header is accepted, the declared client
733+
address replaces the proxy's address for host-based authentication
734+
(see <xref linkend="auth-pg-hba-conf"/>) and in the server log.
735+
</para>
736+
<para>
737+
The value is a comma-separated list of CIDR networks or the special
738+
token <literal>unix</literal> for Unix-domain socket (for example
739+
<literal>10.0.0.0/8, 192.168.1.10, ::1/128, unix</literal>). A
740+
PROXY protocol header is honored only when the actual peer address
741+
falls within one of these networks. A header received from any
742+
other address is rejected as an invalid connection attempt, so that
743+
ordinary clients cannot spoof their address. The default is an
744+
empty string, which disables the PROXY protocol support entirely.
745+
This parameter can only be set in the
746+
<filename>postgresql.conf</filename> file or on the server command
747+
line.
748+
</para>
749+
<para>
750+
Because a peer within these networks is treated as a proxy rather than
751+
a direct client, it is <emphasis>required</emphasis> to lead with a
752+
PROXY protocol header. A connection from one of these networks that
753+
begins with an ordinary startup packet, or an SSL or GSS negotiation
754+
request, instead of a PROXY header is rejected. This prevents the
755+
proxy's own address from being mistaken for a client's.
756+
</para>
757+
</listitem>
758+
</varlistentry>
759+
715760
<varlistentry id="guc-max-connections" xreflabel="max_connections">
716761
<term><varname>max_connections</varname> (<type>integer</type>)
717762
<indexterm>
@@ -8131,11 +8176,12 @@ local0.* /var/log/postgresql
81318176
<listitem>
81328177
<para>
81338178
By default, connection log messages only show the IP address of the
8134-
connecting host. Turning this parameter on causes logging of the
8135-
host name as well. Note that depending on your host name resolution
8136-
setup this might impose a non-negligible performance penalty.
8137-
This parameter can only be set in the <filename>postgresql.conf</filename>
8138-
file or on the server command line.
8179+
connecting host and the proxy host if the connection came from a
8180+
trusted proxy. Turning this parameter on causes logging of the host
8181+
names as well. Note that depending on your host name resolution setup
8182+
this might impose a non-negligible performance penalty. This parameter
8183+
can only be set in the <filename>postgresql.conf</filename> file or on
8184+
the server command line.
81398185
</para>
81408186
</listitem>
81418187
</varlistentry>
@@ -8205,6 +8251,16 @@ local0.* /var/log/postgresql
82058251
<entry>Remote host name or IP address</entry>
82068252
<entry>yes</entry>
82078253
</row>
8254+
<row>
8255+
<entry><literal>%H</literal></entry>
8256+
<entry>Proxy host name or IP address</entry>
8257+
<entry>yes</entry>
8258+
</row>
8259+
<row>
8260+
<entry><literal>%R</literal></entry>
8261+
<entry>Proxy host name or IP address, and proxy port</entry>
8262+
<entry>yes</entry>
8263+
</row>
82088264
<row>
82098265
<entry><literal>%L</literal></entry>
82108266
<entry>Local address (the IP address on the server that the
@@ -8693,6 +8749,7 @@ CREATE TABLE postgres_log
86938749
backend_type text,
86948750
leader_pid integer,
86958751
query_id bigint,
8752+
proxy_connection text,
86968753
PRIMARY KEY (session_id, session_line_num)
86978754
);
86988755
</programlisting>
@@ -8819,6 +8876,16 @@ COPY postgres_log FROM '/full/path/to/logfile.csv' WITH csv;
88198876
<entry>number</entry>
88208877
<entry>Client port</entry>
88218878
</row>
8879+
<row>
8880+
<entry><literal>proxy_host</literal></entry>
8881+
<entry>string</entry>
8882+
<entry>Proxy host</entry>
8883+
</row>
8884+
<row>
8885+
<entry><literal>proxy_port</literal></entry>
8886+
<entry>number</entry>
8887+
<entry>Proxy port</entry>
8888+
</row>
88228889
<row>
88238890
<entry><literal>session_id</literal></entry>
88248891
<entry>string</entry>

doc/src/sgml/monitoring.sgml

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -863,6 +863,10 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser
863863
If this field is null, it indicates either that the client is
864864
connected via a Unix socket on the server machine or that this is an
865865
internal process such as autovacuum.
866+
When the connection was made through a trusted proxy using the PROXY
867+
protocol, this is the client address sent by the proxy, not the address
868+
of the proxy itself (which is shown in <structfield>proxy_addr</structfield>
869+
instead).
866870
</para></entry>
867871
</row>
868872

@@ -885,6 +889,46 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser
885889
TCP port number that the client is using for communication
886890
with this backend, or <literal>-1</literal> if a Unix socket is used.
887891
If this field is null, it indicates that this is an internal server process.
892+
When the connection was made through a trusted proxy using the PROXY
893+
protocol, this is the client port sent by the proxy, not the port of the
894+
proxy itself (which is shown in <structfield>proxy_port</structfield>
895+
instead).
896+
</para></entry>
897+
</row>
898+
899+
<row>
900+
<entry role="catalog_table_entry"><para role="column_definition">
901+
<structfield>proxy_addr</structfield> <type>inet</type>
902+
</para>
903+
<para>
904+
IP address of the trusted proxy that forwarded this connection.
905+
If this field is null, it indicates either that the PROXY protocol has
906+
not been used, or the proxy forwarded the connection over a Unix socket,
907+
or the proxy used a <literal>LOCAL</literal> command.
908+
</para></entry>
909+
</row>
910+
911+
<row>
912+
<entry role="catalog_table_entry"><para role="column_definition">
913+
<structfield>proxy_hostname</structfield> <type>text</type>
914+
</para>
915+
<para>
916+
Host name of the proxy that forwarded this connection, as reported by a
917+
reverse DNS lookup of <structfield>proxy_addr</structfield>. This field
918+
will only be non-null when the client connected through a trusted proxy
919+
using the PROXY protocol, and only when <xref linkend="guc-log-hostname"/>
920+
is enabled.
921+
</para></entry>
922+
</row>
923+
924+
<row>
925+
<entry role="catalog_table_entry"><para role="column_definition">
926+
<structfield>proxy_port</structfield> <type>integer</type>
927+
</para>
928+
<para>
929+
TCP port number of the trusted proxy that forwarded this connection, or
930+
<literal>-1</literal> if the proxy forwarded it over a Unix socket.
931+
This field is null when the PROXY protocol was not used.
888932
</para></entry>
889933
</row>
890934

doc/src/sgml/protocol.sgml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1693,6 +1693,22 @@ SELCT 1/0;<!-- this typo is intentional -->
16931693
</para>
16941694
</sect2>
16951695

1696+
<sect2 id="protocol-flow-proxy">
1697+
<title><acronym>PROXY</acronym> Protocol</title>
1698+
1699+
<para>
1700+
When a connection is forwarded by a proxy, the address the server sees is
1701+
the proxy's, not the real client's. To preserve the
1702+
originating address, the proxy can prepend a header conforming to the
1703+
<ulink url="https://www.haproxy.org/download/2.9/doc/proxy-protocol.txt">PROXY
1704+
protocol</ulink> designed by <productname>HAProxy</productname>. The
1705+
header carries the original source and destination addresses and ports.
1706+
The header is sent once, before any other data, ahead of the SSLRequest,
1707+
GSSENCRequest, or StartupMessage. Both version 1 (text) and version 2
1708+
(binary) of the protocol are accepted.
1709+
</para>
1710+
</sect2>
1711+
16961712
<sect2 id="protocol-flow-ssl">
16971713
<title><acronym>SSL</acronym> Session Encryption</title>
16981714

src/backend/catalog/system_views.sql

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -948,6 +948,9 @@ CREATE VIEW pg_stat_activity AS
948948
S.client_addr,
949949
S.client_hostname,
950950
S.client_port,
951+
S.proxy_addr,
952+
S.proxy_hostname,
953+
S.proxy_port,
951954
S.backend_start,
952955
S.xact_start,
953956
S.query_start,

src/backend/libpq/Makefile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ OBJS = \
2828
pqcomm.o \
2929
pqformat.o \
3030
pqmq.o \
31-
pqsignal.o
31+
pqsignal.o \
32+
proxy_protocol.o
3233

3334
ifeq ($(with_ssl),openssl)
3435
OBJS += be-secure-openssl.o

src/backend/libpq/auth.c

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1734,6 +1734,18 @@ ident_inet(Port *port)
17341734
*la = NULL,
17351735
hints;
17361736

1737+
/*
1738+
* Ident is incompatible with the PROXY protocol. A proxied client
1739+
* connected to the proxy, not to the server, so its ident server has no
1740+
* record of any connection matching the address pair in the query.
1741+
*/
1742+
if (port->proxy_protocol)
1743+
{
1744+
ereport(LOG,
1745+
(errmsg("ident authentication is not supported over connections using the PROXY protocol")));
1746+
return STATUS_ERROR;
1747+
}
1748+
17371749
/*
17381750
* Might look a little weird to first convert it to text and then back to
17391751
* sockaddr, but it's protocol independent.

src/backend/libpq/meson.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ backend_sources += files(
1515
'pqformat.c',
1616
'pqmq.c',
1717
'pqsignal.c',
18+
'proxy_protocol.c',
1819
)
1920

2021
if ssl.found()

src/backend/libpq/pqcomm.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1094,7 +1094,7 @@ pq_getbytes(void *b, size_t len)
10941094
* returns 0 if OK, EOF if trouble
10951095
* --------------------------------
10961096
*/
1097-
static int
1097+
int
10981098
pq_discardbytes(size_t len)
10991099
{
11001100
size_t amount;

0 commit comments

Comments
 (0)