Commit 146889f2 authored by Paul Henson's avatar Paul Henson Committed by Quanah Gibson-Mount
Browse files

ITS#9419 Add support for HAProxy proxy protocol v2

parent dcca7337
......@@ -36,13 +36,23 @@ This option specifies alternative listener configurations. The
default is {{EX:ldap:///}} which implies {{TERM:LDAP}} over
{{TERM:TCP}} on all interfaces on the default LDAP port 389. You
can specify specific host-port pairs or other protocol schemes (such
as {{EX:ldaps://}} or {{EX:ldapi://}}).
as {{EX:ldaps://}} or {{EX:ldapi://}}). slapd supports the HAProxy
proxy protocol version 2, which allows a load balancer or proxy
server to provide the remote client IP address to slapd to be used
for access control or logging. Listeners configured using either
{{EX:pldap:///}} or {{EX:pldaps:///}} URLS will only accept
connections that include the necessary proxy protocol header.
Connections to the ports used by these listeners should be restricted
at the network level to only trusted load balancers or proxies to
avoid spoofing of client IP addresses by third parties.
!block table
URL Protocol Transport
ldap:/// LDAP TCP port 389
ldaps:/// LDAP over SSL TCP port 636
ldapi:/// LDAP IPC (Unix-domain socket)
URL Protocol Transport
ldap:/// LDAP TCP port 389
pldap:/// proxied LDAP TCP port 389
ldaps:/// LDAP over SSL TCP port 636
pldaps:/// proxied LDAP over SSL TCP port 636
ldapi:/// LDAP IPC (Unix-domain socket)
!endblock
For example, {{EX:-h
......
......@@ -142,13 +142,24 @@ For example, if lloadd is given
it will listen on 127.0.0.1:9009 for LDAP, 0.0.0.0:636 for LDAP over TLS,
and LDAP over IPC (Unix domain sockets). Host 0.0.0.0 represents
INADDR_ANY (any interface).
A space separated list of URLs is expected. The URLs should be of
the LDAP, LDAPS, or LDAPI schemes, and generally
without a DN or other optional parameters (excepting as discussed below).
Support for the latter two schemes depends on selected configuration
options. Hosts may be specified by name or IPv4 and IPv6 address formats.
Ports, if specified, must be numeric. The default ldap:// port is \fB389\fP
and the default ldaps:// port is \fB636\fP.
A space separated list of URLs is expected. The URLs should be of the LDAP,
PLDAP, LDAPS, PLDAPS, or LDAPI schemes, and generally without a DN or other
optional parameters (excepting as discussed below). Support for the latter
three schemes depends on selected configuration options. Hosts may be specified
by name or IPv4 and IPv6 address formats. Ports, if specified, must be
numeric. The default ldap:// port is \fB389\fP and the default ldaps:// port
is \fB636\fP, same for the proxy enabled variants.
The PLDAP and PLDAPS URL schemes provide support for the HAProxy proxy protocol
version 2, which allows a load balancer or proxy server to provide the remote
client IP address to slapd to be used for access control or logging. Ports
configured for PLDAP or PLDAPS will only accept connections that include the
necessary proxy protocol header. Connections to these ports should be
restricted at the network level to only trusted load balancers or proxies to
avoid spoofing of client IP addresses by third parties.
At the moment, the load balancer does not act on the recorded address in any
way.
For LDAP over IPC,
.B name
......
......@@ -192,13 +192,21 @@ For example, if slapd is given
it will listen on 127.0.0.1:9009 for LDAP, 0.0.0.0:636 for LDAP over TLS,
and LDAP over IPC (Unix domain sockets). Host 0.0.0.0 represents
INADDR_ANY (any interface).
A space separated list of URLs is expected. The URLs should be of
the LDAP, LDAPS, or LDAPI schemes, and generally
without a DN or other optional parameters (excepting as discussed below).
Support for the latter two schemes depends on selected configuration
options. Hosts may be specified by name or IPv4 and IPv6 address formats.
Ports, if specified, must be numeric. The default ldap:// port is \fB389\fP
and the default ldaps:// port is \fB636\fP.
A space separated list of URLs is expected. The URLs should be of the LDAP,
PLDAP, LDAPS, PLDAPS, or LDAPI schemes, and generally without a DN or other
optional parameters (excepting as discussed below). Support for the latter
three schemes depends on selected configuration options. Hosts may be specified
by name or IPv4 and IPv6 address formats. Ports, if specified, must be
numeric. The default ldap:// port is \fB389\fP and the default ldaps:// port
is \fB636\fP, same for the proxy enabled variants.
The PLDAP and PLDAPS URL schemes provide support for the HAProxy proxy protocol
version 2, which allows a load balancer or proxy server to provide the remote
client IP address to slapd to be used for access control or logging. Ports
configured for PLDAP or PLDAPS will only accept connections that include the
necessary proxy protocol header. Connections to these ports should be
restricted at the network level to only trusted load balancers or proxies to
avoid spoofing of client IP addresses by third parties.
For LDAP over IPC,
.B name
......
......@@ -32,6 +32,9 @@ ldap_pvt_url_scheme2proto LDAP_P((
LDAP_F ( int )
ldap_pvt_url_scheme2tls LDAP_P((
const char * ));
LDAP_F ( int )
ldap_pvt_url_scheme2proxied LDAP_P((
const char * ));
LDAP_F ( int )
ldap_pvt_url_scheme_port LDAP_P((
......
......@@ -123,8 +123,12 @@ LDAP_BEGIN_DECL
#define LDAP_URL_PREFIX "ldap://"
#define LDAP_URL_PREFIX_LEN STRLENOF(LDAP_URL_PREFIX)
#define PLDAP_URL_PREFIX "pldap://"
#define PLDAP_URL_PREFIX_LEN STRLENOF(PLDAP_URL_PREFIX)
#define LDAPS_URL_PREFIX "ldaps://"
#define LDAPS_URL_PREFIX_LEN STRLENOF(LDAPS_URL_PREFIX)
#define PLDAPS_URL_PREFIX "pldaps://"
#define PLDAPS_URL_PREFIX_LEN STRLENOF(PLDAPS_URL_PREFIX)
#define LDAPI_URL_PREFIX "ldapi://"
#define LDAPI_URL_PREFIX_LEN STRLENOF(LDAPI_URL_PREFIX)
#ifdef LDAP_CONNECTIONLESS
......
......@@ -20,7 +20,7 @@
/*
* LDAP URLs look like this:
* ldap[is]://host[:port][/[dn[?[attributes][?[scope][?[filter][?exts]]]]]]
* [p]ldap[is]://host[:port][/[dn[?[attributes][?[scope][?[filter][?exts]]]]]]
*
* where:
* attributes is a comma separated list
......@@ -59,7 +59,7 @@ int ldap_pvt_url_scheme2proto( const char *scheme )
return -1;
}
if( strcmp("ldap", scheme) == 0 ) {
if( strcmp("ldap", scheme) == 0 || strcmp("pldap", scheme) == 0 ) {
return LDAP_PROTO_TCP;
}
......@@ -67,7 +67,7 @@ int ldap_pvt_url_scheme2proto( const char *scheme )
return LDAP_PROTO_IPC;
}
if( strcmp("ldaps", scheme) == 0 ) {
if( strcmp("ldaps", scheme) == 0 || strcmp("pldaps", scheme) == 0 ) {
return LDAP_PROTO_TCP;
}
#ifdef LDAP_CONNECTIONLESS
......@@ -86,7 +86,7 @@ int ldap_pvt_url_scheme_port( const char *scheme, int port )
if( port ) return port;
if( scheme == NULL ) return port;
if( strcmp("ldap", scheme) == 0 ) {
if( strcmp("ldap", scheme) == 0 || strcmp("pldap", scheme) == 0 ) {
return LDAP_PORT;
}
......@@ -94,7 +94,7 @@ int ldap_pvt_url_scheme_port( const char *scheme, int port )
return -1;
}
if( strcmp("ldaps", scheme) == 0 ) {
if( strcmp("ldaps", scheme) == 0 || strcmp("pldaps", scheme) == 0 ) {
return LDAPS_PORT;
}
......@@ -116,7 +116,19 @@ ldap_pvt_url_scheme2tls( const char *scheme )
return -1;
}
return strcmp("ldaps", scheme) == 0;
return strcmp("ldaps", scheme) == 0 || strcmp("pldaps", scheme) == 0;
}
int
ldap_pvt_url_scheme2proxied( const char *scheme )
{
assert( scheme != NULL );
if( scheme == NULL ) {
return -1;
}
return strcmp("pldap", scheme) == 0 || strcmp("pldaps", scheme) == 0;
}
int
......@@ -150,7 +162,7 @@ ldap_is_ldaps_url( LDAP_CONST char *url )
return 0;
}
return strcmp(scheme, "ldaps") == 0;
return strcmp(scheme, "ldaps") == 0 || strcmp(scheme, "pldaps");
}
int
......@@ -228,6 +240,14 @@ skip_url_prefix(
return( p );
}
/* check for "pldap://" prefix */
if ( strncasecmp( p, PLDAP_URL_PREFIX, PLDAP_URL_PREFIX_LEN ) == 0 ) {
/* skip over "pldap://" prefix and return success */
p += PLDAP_URL_PREFIX_LEN;
*scheme = "pldap";
return( p );
}
/* check for "ldaps://" prefix */
if ( strncasecmp( p, LDAPS_URL_PREFIX, LDAPS_URL_PREFIX_LEN ) == 0 ) {
/* skip over "ldaps://" prefix and return success */
......@@ -236,6 +256,14 @@ skip_url_prefix(
return( p );
}
/* check for "pldaps://" prefix */
if ( strncasecmp( p, PLDAPS_URL_PREFIX, PLDAPS_URL_PREFIX_LEN ) == 0 ) {
/* skip over "pldaps://" prefix and return success */
p += PLDAPS_URL_PREFIX_LEN;
*scheme = "pldaps";
return( p );
}
/* check for "ldapi://" prefix */
if ( strncasecmp( p, LDAPI_URL_PREFIX, LDAPI_URL_PREFIX_LEN ) == 0 ) {
/* skip over "ldapi://" prefix and return success */
......
......@@ -22,7 +22,7 @@ NT_SRCS = ../slapd/nt_svc.c
NT_OBJS = ../slapd/nt_svc.o ../../libraries/liblutil/slapdmsg.res
SRCS += main.c value.c \
../slapd/ch_malloc.c ../slapd/sl_malloc.c ../slapd/user.c
../slapd/ch_malloc.c ../slapd/proxyp.c ../slapd/sl_malloc.c ../slapd/user.c
OBJS = $(patsubst %.c,%.o,$(SRCS)) $(@PLAT@_OBJS)
......
......@@ -404,6 +404,8 @@ lload_open_listener(
l.sl_is_tls = ldap_pvt_url_scheme2tls( lud->lud_scheme );
#endif /* HAVE_TLS */
l.sl_is_proxied = ldap_pvt_url_scheme2proxied( lud->lud_scheme );
#ifdef LDAP_TCP_BUFFER
l.sl_tcp_rmem = 0;
l.sl_tcp_wmem = 0;
......@@ -889,6 +891,16 @@ lload_listener(
}
#endif /* SO_KEEPALIVE || TCP_NODELAY */
if ( sl->sl_is_proxied ) {
if ( !proxyp( s, from ) ) {
Debug( LDAP_DEBUG_ANY, "lload_listener: "
"proxyp(%ld) failed\n",
(long)s );
lloadd_close( s );
return;
}
}
cflag = 0;
switch ( from->sa_addr.sa_family ) {
#ifdef LDAP_PF_LOCAL
......@@ -1118,8 +1130,9 @@ lload_listener_activate( void )
lload_listeners[l]->sl_busy = 1;
listener = evconnlistener_new( listener_base, lload_listener,
lload_listeners[l], LEV_OPT_THREADSAFE, SLAPD_LISTEN_BACKLOG,
lload_listeners[l]->sl_sd );
lload_listeners[l],
LEV_OPT_THREADSAFE|LEV_OPT_DEFERRED_ACCEPT,
SLAPD_LISTEN_BACKLOG, lload_listeners[l]->sl_sd );
if ( !listener ) {
int err = sock_errno();
......
......@@ -469,6 +469,7 @@ struct LloadListener {
#ifdef HAVE_TLS
int sl_is_tls;
#endif
int sl_is_proxied;
struct event_base *base;
struct evconnlistener *listener;
int sl_mute; /* Listener is temporarily disabled due to emfile */
......
......@@ -29,7 +29,7 @@ SRCS = main.c globals.c bconfig.c config.c daemon.c \
dn.c compare.c modify.c delete.c modrdn.c ch_malloc.c \
value.c ava.c bind.c unbind.c abandon.c filterentry.c \
phonetic.c acl.c str2filter.c aclparse.c init.c user.c \
lock.c controls.c extended.c passwd.c \
lock.c controls.c extended.c passwd.c proxyp.c \
schema.c schema_check.c schema_init.c schema_prep.c \
schemaparse.c ad.c at.c mr.c syntax.c oc.c saslauthz.c \
oidm.c starttls.c index.c sets.c referral.c root_dse.c \
......@@ -47,7 +47,7 @@ OBJS = main.o globals.o bconfig.o config.o daemon.o \
dn.o compare.o modify.o delete.o modrdn.o ch_malloc.o \
value.o ava.o bind.o unbind.o abandon.o filterentry.o \
phonetic.o acl.o str2filter.o aclparse.o init.o user.o \
lock.o controls.o extended.o passwd.o \
lock.o controls.o extended.o passwd.o proxyp.o \
schema.o schema_check.o schema_init.o schema_prep.o \
schemaparse.o ad.o at.o mr.o syntax.o oc.o saslauthz.o \
oidm.o starttls.o index.o sets.o referral.o root_dse.o \
......
......@@ -1549,6 +1549,8 @@ slap_open_listener(
}
#endif /* HAVE_TLS */
l.sl_is_proxied = ldap_pvt_url_scheme2proxied( lud->lud_scheme );
#ifdef LDAP_TCP_BUFFER
l.sl_tcp_rmem = 0;
l.sl_tcp_wmem = 0;
......@@ -2270,6 +2272,13 @@ slap_listener(
case AF_INET6:
# endif /* LDAP_PF_INET6 */
case AF_INET:
if ( sl->sl_is_proxied ) {
if ( !proxyp( sfd, &from ) ) {
Debug( LDAP_DEBUG_ANY, "slapd(%ld): proxyp failed\n", (long)sfd );
slapd_close( sfd );
return 0;
}
}
lutil_sockaddrstr( &from, &peerbv );
break;
......
......@@ -1556,6 +1556,11 @@ LDAP_SLAPD_F (void) slap_passwd_init (void);
*/
LDAP_SLAPD_F (char *) phonetic LDAP_P(( char *s ));
/*
* proxyp.c
*/
LDAP_SLAPD_F (int) proxyp LDAP_P((ber_socket_t sfd, Sockaddr *from));
/*
* referral.c
*/
......
/* $OpenLDAP$ */
/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
*
* Copyright 2000-2020 The OpenLDAP Foundation.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted only as authorized by the OpenLDAP
* Public License.
*
* A copy of this license is available in the file LICENSE in the
* top-level directory of the distribution or, alternatively, at
* <http://www.OpenLDAP.org/license.html>.
*/
#include "portable.h"
#include "slap.h"
#ifdef HAVE_STDINT_H
#include <stdint.h>
#endif
#ifdef HAVE_INTTYPES_H
#include <inttypes.h>
#endif
#include <lber_types.h>
#include <ac/string.h>
#include <ac/errno.h>
typedef struct {
uint8_t sig[12]; /* hex 0d 0a 0d 0a 00 0d 0a 51 55 49 54 0a */
uint8_t ver_cmd; /* protocol version and command */
uint8_t fam; /* protocol family and address */
uint16_t len; /* length of address data */
} proxyp_header;
typedef union {
struct { /* for TCP/UDP over IPv4, len = 12 */
uint32_t src_addr;
uint32_t dst_addr;
uint16_t src_port;
uint16_t dst_port;
} ip4;
struct { /* for TCP/UDP over IPv6, len = 36 */
uint8_t src_addr[16];
uint8_t dst_addr[16];
uint16_t src_port;
uint16_t dst_port;
} ip6;
struct { /* for AF_UNIX sockets, len = 216 */
uint8_t src_addr[108];
uint8_t dst_addr[108];
} unx;
} proxyp_addr;
static const uint8_t proxyp_sig[12] = {
0x0d, 0x0a, 0x0d, 0x0a, 0x00, 0x0d, 0x0a, 0x51, 0x55, 0x49, 0x54, 0x0a,
};
int
proxyp( ber_socket_t sfd, Sockaddr *from ) {
proxyp_header pph;
proxyp_addr ppa;
char peername[LUTIL_ADDRLEN];
struct berval peerbv = BER_BVC(peername);
/* Maximum size of header minus static component size is max option size */
uint8_t proxyp_options[536 - 16];
int pph_len;
int ret;
peername[0] = '\0';
do {
ret = tcp_read( SLAP_FD2SOCK( sfd ), &pph, sizeof(pph) );
} while ( ret == -1 && errno == EINTR );
if ( ret == -1 ) {
char ebuf[128];
int save_errno = errno;
Debug( LDAP_DEBUG_ANY, "proxyp(%ld): "
"header read failed %d (%s)\n",
(long)sfd, save_errno,
AC_STRERROR_R( save_errno, ebuf, sizeof(ebuf) ) );
return 0;
} else if ( ret != sizeof(pph) ) {
Debug( LDAP_DEBUG_ANY, "proxyp(%ld): "
"header read insufficient data %d\n",
(long)sfd, ret );
return 0;
}
if ( memcmp( pph.sig, proxyp_sig, 12 ) != 0 ) {
Debug( LDAP_DEBUG_ANY, "proxyp(%ld): "
"invalid header signature\n", (long)sfd );
return 0;
}
if ( ( pph.ver_cmd & 0xF0 ) != 0x20 ) {
Debug( LDAP_DEBUG_ANY, "proxyp(%ld): "
"invalid header version %x\n",
(long)sfd, pph.ver_cmd & 0xF0 );
return 0;
}
pph_len = ntohs( pph.len );
if ( ( pph.ver_cmd & 0x0F ) == 0x01 ) { /* PROXY command */
int addr_len;
switch ( pph.fam ) {
case 0x11: /* TCPv4 */
addr_len = sizeof( ppa.ip4 );
break;
case 0x21: /* TCPv6 */
addr_len = sizeof( ppa.ip6 );
break;
default:
Debug( LDAP_DEBUG_ANY, "proxyp(%ld): "
"unsupported protocol %x\n",
(long)sfd, pph.fam );
return 0;
}
if ( pph_len < addr_len ) {
Debug( LDAP_DEBUG_ANY, "proxyp(%ld): "
"address length %d too small, expecting %d\n",
(long)sfd, pph_len, addr_len );
return 0;
}
do {
ret = tcp_read( SLAP_FD2SOCK (sfd), &ppa, addr_len );
} while ( ret == -1 && errno == EINTR );
if ( ret == -1 ) {
char ebuf[128];
int save_errno = errno;
Debug( LDAP_DEBUG_ANY, "proxyp(%ld): "
"address read failed %d (%s)\n",
(long)sfd, save_errno,
AC_STRERROR_R( save_errno, ebuf, sizeof(ebuf) ) );
return 0;
} else if ( ret != addr_len ) {
Debug( LDAP_DEBUG_ANY, "proxyp(%ld): "
"address read insufficient data, expecting %d, read %d\n",
(long)sfd, addr_len, ret );
return 0;
}
pph_len -= addr_len;
}
switch ( pph.ver_cmd & 0x0F ) {
case 0x01: /* PROXY command */
switch ( pph.fam ) {
case 0x11: /* TCPv4 */
lutil_sockaddrstr( from, &peerbv );
Debug( LDAP_DEBUG_STATS, "proxyp(%ld): via %s\n",
(long)sfd, peername );
from->sa_in_addr.sin_family = AF_INET;
from->sa_in_addr.sin_addr.s_addr = ppa.ip4.src_addr;
from->sa_in_addr.sin_port = ppa.ip4.src_port;
break;
case 0x21: /* TCPv6 */
lutil_sockaddrstr( from, &peerbv );
Debug( LDAP_DEBUG_STATS, "proxyp(%ld): via %s\n",
(long)sfd, peername );
from->sa_in6_addr.sin6_family = AF_INET6;
memcpy( &from->sa_in6_addr.sin6_addr, ppa.ip6.src_addr,
sizeof(ppa.ip6.src_addr) );
from->sa_in6_addr.sin6_port = ppa.ip6.src_port;
break;
}
break;
case 0x00: /* LOCAL command */
Debug( LDAP_DEBUG_CONNS, "proxyp(%ld): "
"local connection, ignoring proxy data\n",
(long)sfd );
break;
default:
Debug( LDAP_DEBUG_ANY, "proxyp(%ld): invalid command %x\n",
(long)sfd, pph.ver_cmd & 0x0F );
return 0;
}
/* Clear out any options left in proxy packet */
if ( pph_len > 0 ) {
if (pph_len > sizeof( proxyp_options ) ) {
Debug( LDAP_DEBUG_ANY, "proxyp(%ld): "
"options size %d too big\n",
(long)sfd, pph_len );
return 0;
}
do {
ret = tcp_read( SLAP_FD2SOCK (sfd), &proxyp_options, pph_len );
} while ( ret == -1 && errno == EINTR );
if ( ret == -1 ) {
char ebuf[128];
int save_errno = errno;
Debug( LDAP_DEBUG_ANY, "proxyp(%ld): "
"options read failed %d (%s)\n",
(long)sfd, save_errno,
AC_STRERROR_R( save_errno, ebuf, sizeof(ebuf) ) );
return 0;
} else if ( ret != pph_len ) {
Debug( LDAP_DEBUG_ANY, "proxyp(%ld): "
"options read insufficient data, expecting %d, read %d\n",
(long)sfd, pph_len, ret );
return 0;
}
}
return 1;
}
......@@ -3003,6 +3003,7 @@ struct Listener {
#ifdef LDAP_CONNECTIONLESS
int sl_is_udp; /* UDP listener is also data port */
#endif
int sl_is_proxied;
int sl_mute; /* Listener is temporarily disabled due to emfile */
int sl_busy; /* Listener is busy (accept thread activated) */
ber_socket_t sl_sd;
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment