From 7fbee99b841eeef9a29306e5ac8846c7dc4f48f9 Mon Sep 17 00:00:00 2001 From: Simon Pichugin Date: Tue, 17 Sep 2024 18:11:41 -0700 Subject: [PATCH] Issue 6324 - Provide more information in the error message during setup_ol_tls_conn() (#6325) Description: When there's a problem with creating a new TLS context, we just fail with -1 error code. We can improve it by providing more information that can be extracted from LDAP_OPT_DIAGNOSTIC_MESSAGE or LDAP_OPT_ERROR_STRING. Use slapi_ldap_get_lderrno to get more information from the ld structure. Add a test case. Fixes: https://github.com/389ds/389-ds-base/issues/6324 Reviewed by: @progier389 (Thanks!) --- .../suites/tls/tls_repl_clientauth_test.py | 140 ++++++++++++++++++ ldap/servers/slapd/ldaputil.c | 56 +++++-- 2 files changed, 180 insertions(+), 16 deletions(-) create mode 100644 dirsrvtests/tests/suites/tls/tls_repl_clientauth_test.py diff --git a/dirsrvtests/tests/suites/tls/tls_repl_clientauth_test.py b/dirsrvtests/tests/suites/tls/tls_repl_clientauth_test.py new file mode 100644 index 0000000000..011927f767 --- /dev/null +++ b/dirsrvtests/tests/suites/tls/tls_repl_clientauth_test.py @@ -0,0 +1,140 @@ +# --- BEGIN COPYRIGHT BLOCK --- +# Copyright (C) 2024 Red Hat, Inc. +# All rights reserved. +# +# License: GPL (version 3 or any later version). +# See LICENSE for details. +# --- END COPYRIGHT BLOCK --- +# +import os +import pytest +import logging +from lib389.replica import Replicas, ReplicationManager +from lib389._constants import DEFAULT_SUFFIX +from lib389.config import CertmapLegacy +from lib389.idm.services import ServiceAccounts +from lib389.topologies import topology_m2 as topo + +pytestmark = pytest.mark.tier1 + +DEBUGGING = os.getenv("DEBUGGING", default=False) +if DEBUGGING: + logging.getLogger(__name__).setLevel(logging.DEBUG) +else: + logging.getLogger(__name__).setLevel(logging.INFO) +log = logging.getLogger(__name__) + + +@pytest.fixture(scope="module") +def topo_tls_ldapi(topo): + """Enable TLS on both suppliers and reconfigure both agreements + to use TLS Client auth. Also, setup ldapi and export DB + """ + + m1, m2 = topo.ms["supplier1"], topo.ms["supplier2"] + + # Create and configure certmaps + cm_m1, cm_m2 = CertmapLegacy(m1), CertmapLegacy(m2) + certmaps = cm_m1.list() + certmaps['default'].update({'DNComps': None, 'CmapLdapAttr': 'nsCertSubjectDN'}) + cm_m1.set(certmaps) + cm_m2.set(certmaps) + + # Enable TLS on both instances + for instance in topo: + instance.enable_tls() + + # Create replication DNs + services = ServiceAccounts(m1, DEFAULT_SUFFIX) + for instance in (m1, m2): + repl = services.get(f'{instance.host}:{instance.sslport}') + repl.set('nsCertSubjectDN', instance.get_server_tls_subject()) + + # Check the replication is "done". + repl = ReplicationManager(DEFAULT_SUFFIX) + repl.wait_for_replication(m1, m2) + + # Now change the auth type + for instance, other in [(m1, m2), (m2, m1)]: + replica = Replicas(instance).get(DEFAULT_SUFFIX) + agmt = replica.get_agreements().list()[0] + agmt.replace_many( + ('nsDS5ReplicaBindMethod', 'SSLCLIENTAUTH'), + ('nsDS5ReplicaTransportInfo', 'SSL'), + ('nsDS5ReplicaPort', str(other.sslport)), + ) + agmt.remove_all('nsDS5ReplicaBindDN') + + # Set up LDAPI + for instance in topo: + instance.config.set('nsslapd-ldapilisten', 'on') + instance.config.set('nsslapd-ldapifilepath', f'/var/run/slapd-{instance.serverid}.socket') + instance.restart() + + repl.test_replication(m1, m2) + repl.test_replication(m2, m1) + + return topo + + +def find_ca_files(): + ca_files = [] + for root, dirs, files in os.walk('/tmp'): + if 'Self-Signed-CA.pem' in files and 'dirsrv@' in root: + ca_files.append(os.path.join(root, 'Self-Signed-CA.pem')) + return ca_files + + +def test_new_tls_context_error(topo_tls_ldapi): + """Test TLS context error when CA certificate is removed + + :id: 88d4c841-9f91-499b-ba30-f834225effd8 + :setup: Two supplier replication with SSLCLIENTAUTH agmts + :steps: + 1. Remove tmp's Self-Signed-CA.pem dirsrv file + 2. Reinit agreement + 3. Check errors log and make sure the detailed error is there + :expectedresults: + 1. Self-Signed-CA.pem file is removed + 2. Replication reinitialization fails + 3. Error log contains the expected SSL certificate verify failed message + """ + + m1 = topo_tls_ldapi.ms["supplier1"] + + log.info('Find and remove Self-Signed-CA.pem dirsrv files') + ca_files = find_ca_files() + if not ca_files: + pytest.skip("No Self-Signed-CA.pem files found. Skipping test.") + + log.info(f'Found {len(ca_files)} Self-Signed-CA.pem files') + log.info(f'CA files are: {", ".join(ca_files)}') + + for ca_file in ca_files: + try: + os.remove(ca_file) + log.info(f"Removed file: {ca_file}") + except OSError as e: + log.info(f"Error removing file {ca_file}: {e}") + + log.info('Reinit agreement') + replica_m1 = Replicas(m1).get(DEFAULT_SUFFIX) + agmt_m1 = replica_m1.get_agreements().list()[0] + agmt_m1.begin_reinit() + agmt_m1.wait_reinit() + + log.info('Restart the server to flush logs') + m1.restart() + + log.info('Check errors log for certificate verify failed message') + error_msg = "error:80000002:system library::No such file or directory" + assert m1.searchErrorsLog(error_msg), f"Expected error message not found: {error_msg}" + + log.info('Test completed successfully') + + +if __name__ == '__main__': + # Run isolated + # -s for DEBUG mode + CURRENT_FILE = os.path.realpath(__file__) + pytest.main(["-s", CURRENT_FILE]) diff --git a/ldap/servers/slapd/ldaputil.c b/ldap/servers/slapd/ldaputil.c index 5c161cf6d9..3910bdf7f3 100644 --- a/ldap/servers/slapd/ldaputil.c +++ b/ldap/servers/slapd/ldaputil.c @@ -508,6 +508,7 @@ setup_ol_tls_conn(LDAP *ld, int clientauth) int ssl_strength = 0; int rc = 0; const char *cacert = NULL; + char *errmsg = NULL; /* certdir is used to setup outgoing secure connection (openldap) * It refers to the place where PEM files have been extracted @@ -526,19 +527,25 @@ setup_ol_tls_conn(LDAP *ld, int clientauth) ssl_strength = LDAP_OPT_X_TLS_NEVER; } - if (ldap_set_option(ld, LDAP_OPT_X_TLS_REQUIRE_CERT, &ssl_strength)) { + rc = ldap_set_option(ld, LDAP_OPT_X_TLS_REQUIRE_CERT, &ssl_strength); + if (rc != LDAP_SUCCESS) { + rc = slapi_ldap_get_lderrno(ld, NULL, &errmsg); slapi_log_err(SLAPI_LOG_ERR, "setup_ol_tls_conn", - "failed: unable to set REQUIRE_CERT option to %d\n", ssl_strength); + "failed: unable to set REQUIRE_CERT option to %d: %d (%s) %s\n", + ssl_strength, rc, ldap_err2string(rc), errmsg ? errmsg : ""); + slapi_ch_free_string(&errmsg); } if (slapi_client_uses_non_nss(ld) && config_get_extract_pem()) { cacert = slapi_get_cacertfile(); if (cacert) { /* CA Cert PEM file exists. Set the path to openldap option. */ rc = ldap_set_option(ld, LDAP_OPT_X_TLS_CACERTFILE, cacert); - if (rc) { + if (rc != LDAP_SUCCESS) { + rc = slapi_ldap_get_lderrno(ld, NULL, &errmsg); slapi_log_err(SLAPI_LOG_ERR, "setup_ol_tls_conn", - "Could not set CA cert path [%s]: %d:%s\n", - cacert, rc, ldap_err2string(rc)); + "Could not set CA cert path [%s]: %d (%s) %s\n", + cacert, rc, ldap_err2string(rc), errmsg ? errmsg : ""); + slapi_ch_free_string(&errmsg); } } } @@ -552,33 +559,46 @@ setup_ol_tls_conn(LDAP *ld, int clientauth) } /* Sets the CRL evaluation strategy. */ rc = ldap_set_option(ld, LDAP_OPT_X_TLS_CRLCHECK, &crlcheck); - if (rc) { + if (rc != LDAP_SUCCESS) { + rc = slapi_ldap_get_lderrno(ld, NULL, &errmsg); slapi_log_err(SLAPI_LOG_ERR, "setup_ol_tls_conn", - "Could not set CRLCHECK [%d]: %d:%s\n", - crlcheck, rc, ldap_err2string(rc)); + "Could not set CRLCHECK [%d]: %d (%s) %s\n", + crlcheck, rc, ldap_err2string(rc), errmsg ? errmsg : ""); + slapi_ch_free_string(&errmsg); } } /* tell it where our cert db/file is */ - if (ldap_set_option(ld, LDAP_OPT_X_TLS_CACERTDIR, certdir)) { + rc = ldap_set_option(ld, LDAP_OPT_X_TLS_CACERTDIR, certdir); + if (rc != LDAP_SUCCESS) { + rc = slapi_ldap_get_lderrno(ld, NULL, &errmsg); slapi_log_err(SLAPI_LOG_ERR, "setup_ol_tls_conn", - "failed: unable to set CACERTDIR option to %s\n", certdir); + "failed: unable to set CACERTDIR option to %s: %d (%s) %s\n", + certdir, rc, ldap_err2string(rc), errmsg ? errmsg : ""); + slapi_ch_free_string(&errmsg); } slapi_ch_free_string(&certdir); #if defined(LDAP_OPT_X_TLS_PROTOCOL_MIN) getSSLVersionRangeOL(&optval, NULL); - if (ldap_set_option(ld, LDAP_OPT_X_TLS_PROTOCOL_MIN, &optval)) { + rc = ldap_set_option(ld, LDAP_OPT_X_TLS_PROTOCOL_MIN, &optval); + if (rc != LDAP_SUCCESS) { char *minstr = NULL; (void)getSSLVersionRange(&minstr, NULL); + rc = slapi_ldap_get_lderrno(ld, NULL, &errmsg); slapi_log_err(SLAPI_LOG_ERR, "setup_ol_tls_conn", - "failed: unable to set minimum TLS protocol level to %s\n", minstr); + "failed: unable to set minimum TLS protocol level to %s: %d (%s) %s\n", + minstr, rc, ldap_err2string(rc), errmsg ? errmsg : ""); slapi_ch_free_string(&minstr); + slapi_ch_free_string(&errmsg); } #endif /* LDAP_OPT_X_TLS_PROTOCOL_MIN */ if (clientauth) { rc = slapd_SSL_client_auth(ld); - if (rc) { + if (rc != LDAP_SUCCESS) { + rc = slapi_ldap_get_lderrno(ld, NULL, &errmsg); slapi_log_err(SLAPI_LOG_ERR, "setup_ol_tls_conn", - "failed: unable to setup connection for TLS/SSL EXTERNAL client cert authentication - %d\n", rc); + "failed: unable to setup connection for TLS/SSL EXTERNAL client cert authentication: %d (%s) %s\n", + rc, ldap_err2string(rc), errmsg ? errmsg : ""); + slapi_ch_free_string(&errmsg); } } @@ -586,9 +606,13 @@ setup_ol_tls_conn(LDAP *ld, int clientauth) all of the parameters set above into that TLS handle context - note that optval is zero, meaning create a context for a client */ optval = 0; - if ((rc = ldap_set_option(ld, LDAP_OPT_X_TLS_NEWCTX, &optval))) { + rc = ldap_set_option(ld, LDAP_OPT_X_TLS_NEWCTX, &optval); + if (rc != LDAP_SUCCESS) { + rc = slapi_ldap_get_lderrno(ld, NULL, &errmsg); slapi_log_err(SLAPI_LOG_ERR, "setup_ol_tls_conn", - "failed: unable to create new TLS context - %d\n", rc); + "failed: unable to create new TLS context: %d (%s) %s\n", + rc, ldap_err2string(rc), errmsg ? errmsg : ""); + slapi_ch_free_string(&errmsg); } return rc;