From d9437e78fe3bf9a4827573c20aef3ef2c80820a4 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Gr=C3=A9gory=20Oestreicher?= Date: Wed, 14 Sep 2016 22:25:51 +0200 Subject: [PATCH] Add LDAP GSSAPI authenticator. Also add in the LDAP backend docs the new configuration settings to use it. --- docs/markdown/authoritative/backend-ldap.md | 23 ++- modules/ldapbackend/ldapauthenticator.cc | 189 ++++++++++++++++++++ modules/ldapbackend/ldapauthenticator_p.hh | 27 +++ modules/ldapbackend/ldapbackend.cc | 13 +- modules/ldapbackend/powerldap.cc | 50 ++++-- 5 files changed, 272 insertions(+), 30 deletions(-) diff --git a/docs/markdown/authoritative/backend-ldap.md b/docs/markdown/authoritative/backend-ldap.md index a875e614df..522e0ceb82 100644 --- a/docs/markdown/authoritative/backend-ldap.md +++ b/docs/markdown/authoritative/backend-ldap.md @@ -58,20 +58,25 @@ There can be multiple LDAP URIs specified for load balancing and high availabili In case the used LDAP client library doesn't support LDAP URIs as connection parameter, use plain host names or IP addresses instead (both may optionally be followed by a colon and the port). ## `ldap-starttls` -(default "no") : Use TLS encrypted connections to the LDAP server. -This is only allowed if `ldap-host` is a `ldap://` URI or a host name / IP address. +(default "no") : Use TLS encrypted connections to the LDAP server. This is only allowed if ldap-host is a URI or a host name / IP address. -## `ldap-basedn` -(default "") : The PowerDNS LDAP DNS backend searches below this path for objects containing the specified DNS information. -The retrieval of attributes is limited to this subtree. -This option must be set to the path according to the layout of the LDAP tree, e.g. `ou=hosts,o=example,c=net` is the DN to my objects containing the DNS information. +## `ldap-authmethod` +(default: "simple") : How to authenticate to the LDAP server. Actually only two methods are supported: "simple", which uses the classical DN / password, or "gssapi", which requires a Kerberos keytab. ## `ldap-binddn` -(default "") : Path to the object to authenticate against. -Should only be used if the LDAP server doesn't support anonymous binds. +(default "") : Path to the object to authenticate against. Should only be used, if the LDAP server doesn't support anonymous binds and with the "simple" authmethod. ## `ldap-secret` -(default "") : Password for authentication against the object specified by `ldap-binddn`. +(default "") : Password for authentication against the object specified by ldap-binddn. Only used when "authmethod" is "simple". + +## `ldap-krb5-keytab` +(default: "") : Full path to the keytab file to use to authenticate. This is only used when "authmethod" is set to "gssapi". The keytab must, ideally, contain only one principal (or to put it otherwise, only the first principal found in the keytab will be used). + +## `ldap-krb5-ccache` +(default: "") : Full path to the Kerberos credential cache file to use. Actually only files are supported, and the "FILE:" prefix must not be set. The PowerDNS process must be able to write to this file and it *must* be the only one able to read it. + +## `ldap-basedn` +(default "") : The PowerDNS LDAP DNS backend searches below this path for objects containing the specified DNS information. The retrieval of attributes is limited to this subtree. This option must be set to the path according to the layout of your LDAP tree, e.g. ou=hosts,o=linuxnetworks,c=de is the DN to my objects containing the DNS information. ## `ldap-method` (default "simple") : diff --git a/modules/ldapbackend/ldapauthenticator.cc b/modules/ldapbackend/ldapauthenticator.cc index 7510a5df9b..2632facf7a 100644 --- a/modules/ldapbackend/ldapauthenticator.cc +++ b/modules/ldapbackend/ldapauthenticator.cc @@ -17,6 +17,7 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ +#include #include #include "ldapauthenticator_p.hh" #include "ldaputils.hh" @@ -69,3 +70,191 @@ void LdapSimpleAuthenticator::fillLastError( LDAP* conn, int code ) { lastError = ldapGetError( conn, code ); } + +/***************************** + * + * LdapGssapiAuthenticator + * + ****************************/ + +static int ldapGssapiAuthenticatorSaslInteractCallback( LDAP *conn, unsigned flags, void *defaults, void *in ) +{ + return LDAP_SUCCESS; +} + +LdapGssapiAuthenticator::LdapGssapiAuthenticator( const std::string& kt, const std::string &ccache, int tmout ) + : keytabFile( kt ), cCacheFile( ccache ), timeout( tmout ) +{ +} + +bool LdapGssapiAuthenticator::authenticate( LDAP *conn ) +{ + int code = attemptAuth( conn ); + + if ( code == -1 ) { + return false; + } + else if ( code == -2 ) { + // Here it may be possible to retry after obtainting a fresh ticket + L<setOption( LDAP_OPT_DEREF, LDAP_DEREF_ALWAYS ); - m_pldap->bind( getArg( "binddn" ), getArg( "secret" ), LDAP_AUTH_SIMPLE, getArgAsNum( "timeout" ) ); - m_authenticator = new LdapSimpleAuthenticator( getArg( "binddn" ), getArg( "secret" ), getArgAsNum( "timeout" ) ); + + string bindmethod = getArg( "bindmethod" ); + if ( bindmethod == "gssapi" ) { + m_authenticator = new LdapGssapiAuthenticator( getArg( "krb5-keytab" ), getArg( "krb5-ccache" ), getArgAsNum( "timeout" ) ); + } + else { + m_authenticator = new LdapSimpleAuthenticator( getArg( "binddn" ), getArg( "secret" ), getArgAsNum( "timeout" ) ); + } m_pldap->bind( m_authenticator ); L << Logger::Notice << m_myname << " Ldap connection succeeded" << endl; @@ -540,8 +546,11 @@ public: declare( suffix, "starttls", "Use TLS to encrypt connection (unused for LDAP URIs)", "no" ); declare( suffix, "basedn", "Search root in ldap tree (must be set)","" ); declare( suffix, "basedn-axfr-override", "Override base dn for AXFR subtree search", "no" ); + declare( suffix, "bindmethod", "Bind method to use (simple or gssapi)", "simple" ); declare( suffix, "binddn", "User dn for non anonymous binds","" ); declare( suffix, "secret", "User password for non anonymous binds", "" ); + declare( suffix, "krb5-keytab", "The keytab to use for GSSAPI authentication", "" ); + declare( suffix, "krb5-ccache", "The credentials cache used for GSSAPI authentication", "" ); declare( suffix, "timeout", "Seconds before connecting to server fails", "5" ); declare( suffix, "method", "How to search entries (simple, strict or tree)", "simple" ); declare( suffix, "filter-axfr", "LDAP filter for limiting AXFR results", "(:target:)" ); diff --git a/modules/ldapbackend/powerldap.cc b/modules/ldapbackend/powerldap.cc index f6c48c39f6..646c642a52 100644 --- a/modules/ldapbackend/powerldap.cc +++ b/modules/ldapbackend/powerldap.cc @@ -155,14 +155,13 @@ void PowerLDAP::simpleBind( const string& ldapbinddn, const string& ldapsecret ) int PowerLDAP::search( const string& base, int scope, const string& filter, const char** attr ) { - int msgid, rc; + int msgid, rc; - if( ( rc = ldap_search_ext( d_ld, base.c_str(), scope, filter.c_str(), const_cast (attr), 0, NULL, NULL, NULL, LDAP_NO_LIMIT, &msgid ) ) != LDAP_SUCCESS ) - { - throw LDAPException( "Starting LDAP search: " + getError( rc ) ); - } + if ( ( rc = ldap_search_ext( d_ld, base.c_str(), scope, filter.c_str(), const_cast (attr), 0, NULL, NULL, NULL, LDAP_NO_LIMIT, &msgid ) ) ) { + throw LDAPException( "Starting LDAP search: " + getError( rc ) ); + } - return msgid; + return msgid; } @@ -193,19 +192,32 @@ bool PowerLDAP::getSearchEntry( int msgid, sentry_t& entry, bool dn, int timeout vector values; LDAPMessage* result; LDAPMessage* object; - - - if( ( i = waitResult( msgid, timeout, &result ) ) == LDAP_RES_SEARCH_RESULT ) - { - ldap_msgfree( result ); - return false; - } - - if( i != LDAP_RES_SEARCH_ENTRY ) - { - ldap_msgfree( result ); - throw LDAPException( "Search returned an unexpected result" ); - } + bool hasResult = false; + + while ( !hasResult ) { + i = waitResult( msgid, timeout, &result ); + // Here we deliberately ignore LDAP_RES_SEARCH_REFERENCE as we don't follow them. + // Instead we get the next result. + // If the function returned an error (i <= 0) we'll deal with after this loop too. + if ( i == LDAP_RES_SEARCH_ENTRY || i == LDAP_RES_SEARCH_RESULT || i <= 0 ) + hasResult = true; + } + + if ( i == -1 ) { + // Error while retrieving the message + throw LDAPException( "Error when retrieving LDAP result: " + getError() ); + } + + if ( i == 0 ) { + // Timeout expired before the message could be retrieved + throw LDAPTimeout(); + } + + if ( i == LDAP_RES_SEARCH_RESULT ) { + // We're done with this request + ldap_msgfree( result ); + return false; + } if( ( object = ldap_first_entry( d_ld, result ) ) == NULL ) { -- 2.47.2