Make SSL/TLS configurable for LDAP resources

refs #7771
This commit is contained in:
Johannes Meyer 2015-03-12 15:17:19 +01:00
parent 4a982a382e
commit 1b440a4f1b
2 changed files with 59 additions and 28 deletions

View File

@ -7,6 +7,7 @@ use Exception;
use Icinga\Web\Form; use Icinga\Web\Form;
use Icinga\Data\ConfigObject; use Icinga\Data\ConfigObject;
use Icinga\Data\ResourceFactory; use Icinga\Data\ResourceFactory;
use Icinga\Protocol\Ldap\Connection;
/** /**
* Form class for adding/modifying ldap resources * Form class for adding/modifying ldap resources
@ -57,6 +58,40 @@ class LdapResourceForm extends Form
'value' => 389 'value' => 389
) )
); );
$this->addElement(
'select',
'connection',
array(
'required' => true,
'autosubmit' => true,
'label' => $this->translate('Connection'),
'description' => $this->translate(
'The type of connection to use. Unencrypted (Plaintext) or encrypted (SSL, TLS).'
),
'multiOptions' => array(
'plaintext' => $this->translate('Plaintext'),
Connection::SSL => 'Secure Sockets Layer (SSL)',
Connection::STARTTLS => 'Transport Layer Security (TLS)'
)
)
);
if (isset($formData['connection']) && $formData['connection'] !== 'plaintext') {
// TODO(jom): Do not show this checkbox unless the connection is actually failing due to certificate errors
$this->addElement(
'checkbox',
'reqcert',
array(
'required' => true,
'label' => $this->translate('Require Certificate'),
'description' => $this->translate(
'When checked, the LDAP server must provide a valid and known (trusted) certificate.'
),
'value' => 1
)
);
}
$this->addElement( $this->addElement(
'text', 'text',
'root_dn', 'root_dn',

View File

@ -3,7 +3,6 @@
namespace Icinga\Protocol\Ldap; namespace Icinga\Protocol\Ldap;
use Exception;
use Icinga\Exception\ProgrammingError; use Icinga\Exception\ProgrammingError;
use Icinga\Protocol\Ldap\Exception as LdapException; use Icinga\Protocol\Ldap\Exception as LdapException;
use Icinga\Application\Platform; use Icinga\Application\Platform;
@ -35,6 +34,8 @@ class Connection
const LDAP_SIZELIMIT_EXCEEDED = 4; const LDAP_SIZELIMIT_EXCEEDED = 4;
const LDAP_ADMINLIMIT_EXCEEDED = 11; const LDAP_ADMINLIMIT_EXCEEDED = 11;
const PAGE_SIZE = 1000; const PAGE_SIZE = 1000;
const STARTTLS = 'tls';
const SSL = 'ssl';
protected $ds; protected $ds;
protected $hostname; protected $hostname;
@ -43,6 +44,8 @@ class Connection
protected $bind_pw; protected $bind_pw;
protected $root_dn; protected $root_dn;
protected $count; protected $count;
protected $connectionType;
protected $reqCert = true;
/** /**
* Whether the bind on this connection was already performed * Whether the bind on this connection was already performed
@ -66,8 +69,6 @@ class Connection
/** /**
* Constructor * Constructor
* *
* TODO: Allow to pass port and SSL options
*
* @param ConfigObject $config * @param ConfigObject $config
*/ */
public function __construct(ConfigObject $config) public function __construct(ConfigObject $config)
@ -77,6 +78,8 @@ class Connection
$this->bind_pw = $config->bind_pw; $this->bind_pw = $config->bind_pw;
$this->root_dn = $config->root_dn; $this->root_dn = $config->root_dn;
$this->port = $config->get('port', $this->port); $this->port = $config->get('port', $this->port);
$this->connectionType = $config->connection;
$this->reqCert = (bool) $config->get('reqcert', $this->reqCert);
} }
public function getHostname() public function getHostname()
@ -470,59 +473,53 @@ class Connection
*/ */
protected function prepareNewConnection() protected function prepareNewConnection()
{ {
$use_tls = false; if ($this->connectionType === static::STARTTLS || $this->connectionType === static::SSL) {
$force_tls = false;
if ($use_tls) {
$this->prepareTlsEnvironment(); $this->prepareTlsEnvironment();
} }
$ds = ldap_connect($this->hostname, $this->port); $hostname = $this->hostname;
if ($this->connectionType === static::SSL) {
$hostname = 'ldaps://' . $hostname;
}
$ds = ldap_connect($hostname, $this->port);
try { try {
$this->capabilities = $this->discoverCapabilities($ds); $this->capabilities = $this->discoverCapabilities($ds);
$this->discoverySuccess = true; $this->discoverySuccess = true;
} catch (LdapException $e) { } catch (LdapException $e) {
// create empty default capabilities // create empty default capabilities
Logger::warning('LADP discovery failed, assuming default LDAP settings.'); Logger::warning('LADP discovery failed, assuming default LDAP settings.');
$this->capabilities = new Capability(); $this->capabilities = new Capability();
} }
if ($use_tls) { if ($this->connectionType === static::STARTTLS) {
$force_tls = false;
if ($this->capabilities->hasStartTLS()) { if ($this->capabilities->hasStartTLS()) {
if (@ldap_start_tls($ds)) { if (@ldap_start_tls($ds)) {
Logger::debug('LDAP STARTTLS succeeded'); Logger::debug('LDAP STARTTLS succeeded');
} else { } else {
Logger::debug('LDAP STARTTLS failed: %s', ldap_error($ds)); Logger::error('LDAP STARTTLS failed: %s', ldap_error($ds));
throw new LdapException( throw new LdapException('LDAP STARTTLS failed: %s', ldap_error($ds));
'LDAP STARTTLS failed: %s',
ldap_error($ds)
);
} }
} elseif ($force_tls) { } elseif ($force_tls) {
throw new LdapException( throw new LdapException('STARTTLS is required but not announced by %s', $this->hostname);
'TLS is required but not announced by %s',
$this->hostname
);
} else { } else {
Logger::warning('LDAP TLS enabled but not announced'); Logger::warning('LDAP STARTTLS enabled but not announced');
} }
} }
// ldap_rename requires LDAPv3: // ldap_rename requires LDAPv3:
if ($this->capabilities->hasLdapV3()) { if ($this->capabilities->hasLdapV3()) {
if (! ldap_set_option($ds, LDAP_OPT_PROTOCOL_VERSION, 3)) { if (! ldap_set_option($ds, LDAP_OPT_PROTOCOL_VERSION, 3)) {
throw new LdapException('LDAPv3 is required'); throw new LdapException('LDAPv3 is required');
} }
} else { } else {
// TODO: remove this -> FORCING v3 for now // TODO: remove this -> FORCING v3 for now
ldap_set_option($ds, LDAP_OPT_PROTOCOL_VERSION, 3); ldap_set_option($ds, LDAP_OPT_PROTOCOL_VERSION, 3);
Logger::warning('No LDAPv3 support detected'); Logger::warning('No LDAPv3 support detected');
} }
// Not setting this results in "Operations error" on AD when using the // Not setting this results in "Operations error" on AD when using the whole domain as search base
// whole domain as search base:
ldap_set_option($ds, LDAP_OPT_REFERRALS, 0); ldap_set_option($ds, LDAP_OPT_REFERRALS, 0);
// ldap_set_option($ds, LDAP_OPT_DEREF, LDAP_DEREF_NEVER); // ldap_set_option($ds, LDAP_OPT_DEREF, LDAP_DEREF_NEVER);
return $ds; return $ds;
@ -530,17 +527,16 @@ class Connection
protected function prepareTlsEnvironment() protected function prepareTlsEnvironment()
{ {
$strict_tls = true;
// TODO: allow variable known CA location (system VS Icinga) // TODO: allow variable known CA location (system VS Icinga)
if (Platform::isWindows()) { if (Platform::isWindows()) {
// putenv('LDAP...') putenv('LDAPTLS_REQCERT=never');
} else { } else {
if ($strict_tls) { if ($this->reqCert) {
$ldap_conf = $this->getConfigDir('ldap_ca.conf'); $ldap_conf = $this->getConfigDir('ldap_ca.conf');
} else { } else {
$ldap_conf = $this->getConfigDir('ldap_nocert.conf'); $ldap_conf = $this->getConfigDir('ldap_nocert.conf');
} }
putenv('LDAPRC=' . $ldap_conf); putenv('LDAPRC=' . $ldap_conf); // TODO: Does not have any effect
if (getenv('LDAPRC') !== $ldap_conf) { if (getenv('LDAPRC') !== $ldap_conf) {
throw new LdapException('putenv failed'); throw new LdapException('putenv failed');
} }