diff --git a/doc/guide/admin/runningslapd.sdf b/doc/guide/admin/runningslapd.sdf index 4964b1785a6be89f604c8f3bf62bb65744d4f703..831a12c61eb7772a8aac90c31918374ad39e7a22 100644 --- a/doc/guide/admin/runningslapd.sdf +++ b/doc/guide/admin/runningslapd.sdf @@ -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 diff --git a/doc/man/man8/slapd.8 b/doc/man/man8/slapd.8 index 6ef1925c7de4c92c212ba3edc994ccec70c0ffba..267188ee520a82279040001ffb30e9ac0dbb2140 100644 --- a/doc/man/man8/slapd.8 +++ b/doc/man/man8/slapd.8 @@ -185,13 +185,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 diff --git a/include/ldap_pvt.h b/include/ldap_pvt.h index 716c1a90fda6dedde3dee93702302525e886ccff..d69782672cff3079272898fee39b375004196be7 100644 --- a/include/ldap_pvt.h +++ b/include/ldap_pvt.h @@ -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(( diff --git a/libraries/libldap/ldap-int.h b/libraries/libldap/ldap-int.h index 98ad4dc051afc7fc368868222cc6fb8141d9e6b1..16a527d405b8c5fe8d616c3ec93b04ea96624c53 100644 --- a/libraries/libldap/ldap-int.h +++ b/libraries/libldap/ldap-int.h @@ -107,8 +107,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 diff --git a/libraries/libldap/url.c b/libraries/libldap/url.c index b39f70b97e30509e1c8b71a16c54f0dd0c82620c..6df729e08e7b1fa76a537726bca2cef58a1edefa 100644 --- a/libraries/libldap/url.c +++ b/libraries/libldap/url.c @@ -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 */ diff --git a/servers/slapd/Makefile.in b/servers/slapd/Makefile.in index afc992fb6b15a544afe4ee727fb725b35d682f42..36706c0995ee26bcff1ad8a7dc740228e4057ec5 100644 --- a/servers/slapd/Makefile.in +++ b/servers/slapd/Makefile.in @@ -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 \ diff --git a/servers/slapd/daemon.c b/servers/slapd/daemon.c index 50a13d1b4fb04b3dc3580c101c8040f9c0446328..1eca080a0c6e6ad524cd463a40ce9c7965b9b88e 100644 --- a/servers/slapd/daemon.c +++ b/servers/slapd/daemon.c @@ -1379,6 +1379,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; @@ -2044,6 +2046,13 @@ slap_listener( # ifdef LDAP_PF_INET6 case AF_INET6: + if ( sl->sl_is_proxied ) { + if ( !proxyp( sfd, &from ) ) { + Debug( LDAP_DEBUG_ANY, "slapd(%ld): proxyp failed\n", (long)sfd, 0, 0 ); + slapd_close( sfd ); + return 0; + } + } if ( IN6_IS_ADDR_V4MAPPED(&from.sa_in6_addr.sin6_addr) ) { #if defined( HAVE_GETADDRINFO ) && defined( HAVE_INET_NTOP ) peeraddr = inet_ntop( AF_INET, @@ -2068,6 +2077,13 @@ slap_listener( # 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, 0, 0 ); + slapd_close( sfd ); + return 0; + } + } #if defined( HAVE_GETADDRINFO ) && defined( HAVE_INET_NTOP ) peeraddr = inet_ntop( AF_INET, &from.sa_in_addr.sin_addr, addr, sizeof(addr) ); diff --git a/servers/slapd/proto-slap.h b/servers/slapd/proto-slap.h index 7f8e604fa70654408691984b9f1b9b4c14d23e90..b8bad03a1e09c42cb03e1656c13aacc651f37c02 100644 --- a/servers/slapd/proto-slap.h +++ b/servers/slapd/proto-slap.h @@ -1528,6 +1528,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 */ diff --git a/servers/slapd/proxyp.c b/servers/slapd/proxyp.c new file mode 100644 index 0000000000000000000000000000000000000000..dfed1f85e44381bd1849f74aa3733d826662930b --- /dev/null +++ b/servers/slapd/proxyp.c @@ -0,0 +1,265 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software . + * + * 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 + * . + */ + +#include "portable.h" +#include "slap.h" + +#ifdef HAVE_STDINT_H +#include +#endif +#ifdef HAVE_INTTYPES_H +#include +#endif + +#include +#include +#include + +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; + const char *peeraddr = NULL; + /* we assume INET6_ADDRSTRLEN > INET_ADDRSTRLEN */ + char addr[INET6_ADDRSTRLEN]; +#if defined(LDAP_PF_INET6) + char peername[sizeof("IP=[ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff]:65535")]; +#else + char peername[sizeof("IP=255.255.255.255:65336")]; +#endif + /* 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, 0 ); + return 0; + } + + if ( memcmp( pph.sig, proxyp_sig, 12 ) != 0 ) { + Debug( LDAP_DEBUG_ANY, "proxyp(%ld): " + "invalid header signature\n", (long)sfd, 0, 0 ); + 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, 0 ); + 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, 0 ); + 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 ( from->sa_addr.sa_family ) { +#ifdef LDAP_PF_INET6 + case AF_INET6: + if ( IN6_IS_ADDR_V4MAPPED(&from->sa_in6_addr.sin6_addr) ) { +#if defined( HAVE_GETADDRINFO ) && defined( HAVE_INET_NTOP ) + peeraddr = inet_ntop( AF_INET, + ((struct in_addr *)&from->sa_in6_addr.sin6_addr.s6_addr[12]), + addr, sizeof(addr) ); +#else /* ! HAVE_GETADDRINFO || ! HAVE_INET_NTOP */ + peeraddr = inet_ntoa( *((struct in_addr *) + &from->sa_in6_addr.sin6_addr.s6_addr[12]) ); +#endif /* ! HAVE_GETADDRINFO || ! HAVE_INET_NTOP */ + if ( !peeraddr ) peeraddr = SLAP_STRING_UNKNOWN; + sprintf( peername, "IP=%s:%d", peeraddr, + (unsigned) ntohs( from->sa_in6_addr.sin6_port ) ); + } else { + peeraddr = inet_ntop( AF_INET6, + &from->sa_in6_addr.sin6_addr, + addr, sizeof addr ); + if ( !peeraddr ) peeraddr = SLAP_STRING_UNKNOWN; + sprintf( peername, "IP=[%s]:%d", peeraddr, + (unsigned) ntohs( from->sa_in6_addr.sin6_port ) ); + } + break; +#endif /* LDAP_PF_INET6 */ + case AF_INET: +#if defined( HAVE_GETADDRINFO ) && defined( HAVE_INET_NTOP ) + peeraddr = inet_ntop( AF_INET, &from->sa_in_addr.sin_addr, + addr, sizeof(addr) ); +#else /* ! HAVE_GETADDRINFO || ! HAVE_INET_NTOP */ + peeraddr = inet_ntoa( from->sa_in_addr.sin_addr ); +#endif /* ! HAVE_GETADDRINFO || ! HAVE_INET_NTOP */ + if ( !peeraddr ) peeraddr = SLAP_STRING_UNKNOWN; + sprintf( peername, "IP=%s:%d", peeraddr, + (unsigned) ntohs( from->sa_in_addr.sin_port ) ); + break; + } + Debug( LDAP_DEBUG_STATS, "proxyp(%ld): via %s\n", + (long)sfd, peername, 0 ); + + switch ( pph.fam ) { + case 0x11: /* TCPv4 */ + 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 */ +#ifdef LDAP_PF_INET6 + 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; +#else + Debug( LDAP_DEBUG_ANY, "proxyp(%ld): " + "IPv6 proxied addresses disabled, \n", + (long)sfd, 0, 0 ); + return 0; +#endif + break; + } + + break; + + case 0x00: /* LOCAL command */ + Debug( LDAP_DEBUG_CONNS, "proxyp(%ld): " + "local connection, ignoring proxy data\n", + (long)sfd, 0, 0 ); + break; + + default: + Debug( LDAP_DEBUG_ANY, "proxyp(%ld): invalid command %x\n", + (long)sfd, pph.ver_cmd & 0x0F, 0 ); + 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, 0 ); + 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; +} diff --git a/servers/slapd/slap.h b/servers/slapd/slap.h index 7581967be59793d25d53b68e5d84d37babe1a948..165160a390052ec07b1de260a334274173316aa9 100644 --- a/servers/slapd/slap.h +++ b/servers/slapd/slap.h @@ -2988,6 +2988,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;