diff --git a/robottelo/utils/ldap.py b/robottelo/utils/ldap.py new file mode 100644 index 00000000000..29bec209dbd --- /dev/null +++ b/robottelo/utils/ldap.py @@ -0,0 +1,36 @@ +"""Utilities for LDAP-related test helpers.""" + +from robottelo.config import settings + + +def get_ldap_cacert_pem(sat, server_type, hostname): + """Fetch the LDAP CA certificate as PEM without installing it in the OS trust store. + + :param sat: Satellite host object + :param str server_type: 'IPA' or 'AD' + :param str hostname: LDAP server hostname + :returns: PEM-encoded CA certificate string + """ + if server_type == 'IPA': + result = sat.execute(f'curl -sf http://{hostname}/ipa/config/ca.crt') + assert result.status == 0, f'Failed to fetch IPA CA cert: {result.stderr}' + return result.stdout + if server_type == 'AD': + sat.execute('yum -y --disableplugin=foreman-protector install cifs-utils') + sat.execute( + rf'mount -t cifs -o username=administrator,pass={settings.ldap.password} ' + rf'//{hostname}/c\$ /mnt' + ) + try: + cert_path = '/mnt/Users/Administrator/Desktop/satqe-QE-SAT6-AD-CA.cer' + result = sat.execute(f'cat {cert_path}') + assert result.status == 0, 'Failed to read AD CA cert' + pem_content = result.stdout + if not pem_content.strip().startswith('-----BEGIN'): + result = sat.execute(f'openssl x509 -inform DER -in {cert_path}') + assert result.status == 0, 'Failed to convert AD CA cert to PEM' + pem_content = result.stdout + finally: + sat.execute('umount /mnt') + return pem_content + raise ValueError(f'Unsupported server_type for CA cert: {server_type}') diff --git a/tests/foreman/cli/test_ldapauthsource.py b/tests/foreman/cli/test_ldapauthsource.py index 1742f045bce..d848e2ad75f 100644 --- a/tests/foreman/cli/test_ldapauthsource.py +++ b/tests/foreman/cli/test_ldapauthsource.py @@ -18,6 +18,7 @@ from robottelo.constants import LDAP_ATTR, LDAP_SERVER_TYPE from robottelo.exceptions import CLIReturnCodeError from robottelo.utils.datafactory import generate_strings_list, parametrized +from robottelo.utils.ldap import get_ldap_cacert_pem @pytest.fixture @@ -203,6 +204,51 @@ def test_positive_end_to_end_with_ipa(self, default_ipa_host, server_name, modul with pytest.raises(CLIReturnCodeError): module_target_sat.cli.LDAPAuthSource.info({'name': new_name}) + @pytest.mark.usefixtures("ldap_tear_down") + def test_positive_create_with_cacert_ipa(self, default_ipa_host, module_target_sat): + """Create LDAP auth source with cacert via hammer CLI and verify it persists. + + :id: 5c35665c-05af-426f-8c7c-761c6c6a81c3 + + :steps: + 1. Fetch the IPA CA certificate PEM and write it to a file on the satellite. + 2. Create an LDAP auth source with LDAPS and --cacert-file via CLI. + 3. Read back with ``hammer auth-source ldap info``. + + :expectedresults: The cacert is stored and shown in the CLI info output. + """ + cacert_pem = get_ldap_cacert_pem(module_target_sat, 'IPA', default_ipa_host.hostname) + cacert_file = '/tmp/ipa-ca.crt' + result = module_target_sat.execute(f"cat > {cacert_file} << 'EOF'\n{cacert_pem}\nEOF") + assert result.status == 0, f'Failed to write CA cert to {cacert_file}' + try: + server_name = gen_string('alpha') + auth = module_target_sat.cli_factory.ldap_auth_source( + { + 'name': server_name, + 'onthefly-register': 'true', + 'host': default_ipa_host.hostname, + 'server-type': LDAP_SERVER_TYPE['CLI']['ipa'], + 'attr-login': LDAP_ATTR['login'], + 'attr-firstname': LDAP_ATTR['firstname'], + 'attr-lastname': LDAP_ATTR['surname'], + 'attr-mail': LDAP_ATTR['mail'], + 'account': default_ipa_host.ldap_user_cn, + 'account-password': default_ipa_host.ldap_user_passwd, + 'base-dn': default_ipa_host.base_dn, + 'groups-base': default_ipa_host.group_base_dn, + 'tls': '1', + 'port': '636', + 'cacert-file': cacert_file, + } + ) + assert auth['server']['name'] == server_name + auth_info = module_target_sat.cli.LDAPAuthSource.info({'id': auth['server']['id']}) + cacert_found = "\n".join(auth_info['server']['ca-certificate']) + assert cacert_found.strip() == cacert_pem.strip() + finally: + module_target_sat.execute(f'rm -f {cacert_file}') + @pytest.mark.parametrize('server_type', ['ipa', 'nonposix_schema']) @pytest.mark.usefixtures("ldap_tear_down") def test_usergroup_sync_with_refresh(self, default_ipa_host, server_type, module_target_sat): diff --git a/tests/foreman/destructive/test_ldap_authentication.py b/tests/foreman/destructive/test_ldap_authentication.py index 208592c3960..939d0abe8d0 100644 --- a/tests/foreman/destructive/test_ldap_authentication.py +++ b/tests/foreman/destructive/test_ldap_authentication.py @@ -12,7 +12,6 @@ """ -import os from time import sleep from navmazing import NavigationTriesExceeded @@ -21,7 +20,6 @@ from robottelo.config import settings from robottelo.constants import ( - CERT_PATH, HAMMER_CONFIG, HAMMER_SESSIONS, LDAP_ATTR, @@ -30,6 +28,7 @@ from robottelo.exceptions import CLIReturnCodeError from robottelo.logging import logger from robottelo.utils.datafactory import gen_string +from robottelo.utils.ldap import get_ldap_cacert_pem pytestmark = [pytest.mark.destructive, pytest.mark.run_in_one_thread] @@ -52,36 +51,6 @@ UNABLE_AUTH = 'Unable to authenticate user' # attempting to do something without being logged in -def set_certificate_in_satellite(server_type, sat, hostname=None): - """update the cert settings in satellite based on type of ldap server""" - if server_type == 'IPA': - certfile = 'ipa.crt' - idm_cert_path_url = os.path.join(settings.ipa.hostname, 'ipa/config/ca.crt') - sat.download_file(file_url=idm_cert_path_url, local_path=CERT_PATH, file_name=certfile) - elif server_type == 'AD': - certfile = 'satqe-QE-SAT6-AD-CA.cer' - assert hostname is not None - sat.execute('yum -y --disableplugin=foreman-protector install cifs-utils') - command = r'mount -t cifs -o username=administrator,pass={0} //{1}/c\$ /mnt' - sat.execute(command.format(settings.ldap.password, hostname)) - result = sat.execute( - f'cp /mnt/Users/Administrator/Desktop/satqe-QE-SAT6-AD-CA.cer {CERT_PATH}' - ) - if result.status != 0: - raise AssertionError('Failed to copy the AD server certificate at right path') - result = sat.execute(f'update-ca-trust extract && restorecon -R {CERT_PATH}') - if result.status != 0: - raise AssertionError('Failed to update and trust the certificate') - sat.execute(f'install /{CERT_PATH}/{certfile} /etc/pki/tls/certs/') - sat.execute( - f'ln -s {certfile} /etc/pki/tls/certs/$(openssl x509 ' - f'-noout -hash -in /etc/pki/tls/certs/{certfile}).0' - ) - result = sat.execute('systemctl restart httpd') - if result.status != 0: - raise AssertionError(f'Failed to restart the httpd after applying {server_type} cert') - - @pytest.fixture def ldap_tear_down(module_target_sat): """Teardown the all ldap settings user, usergroup and ldap delete""" @@ -122,6 +91,20 @@ def rhsso_groups_teardown(module_target_sat, default_sso_host): default_sso_host.delete_sso_group(group_name) +@pytest.fixture +def untrust_rhit_cas(module_target_sat): + """Temporarily remove Red Hat IT Root CAs from the system trust store.""" + anchors = '/etc/pki/ca-trust/source/anchors' + backup_dir = '/tmp/ca-backup' + module_target_sat.execute(f'mkdir -p {backup_dir}') + module_target_sat.execute(f'mv {anchors}/*-IT-Root-CA.pem {backup_dir}/') + module_target_sat.execute('update-ca-trust extract') + yield + module_target_sat.execute(f'mv {backup_dir}/* {anchors}/') + module_target_sat.execute(f'rm -rf {backup_dir}') + module_target_sat.execute('update-ca-trust extract') + + @pytest.fixture def configure_hammer_session(parametrized_enrolled_sat, enable=True): """Take backup of the hammer config file and enable use_sessions""" @@ -143,37 +126,42 @@ def generate_otp(secret): @pytest.mark.upgrade @pytest.mark.parametrize('auth_data', ['AD_2019', 'IPA'], indirect=True) def test_positive_create_with_https( - session, module_subscribe_satellite, test_name, auth_data, ldap_tear_down, module_target_sat + session, + module_subscribe_satellite, + test_name, + auth_data, + ldap_tear_down, + module_target_sat, + untrust_rhit_cas, ): - """Create LDAP auth_source for IDM with HTTPS. + """Create LDAP auth_source for IDM/AD with LDAPS. :id: 7ff3daa4-2317-11ea-aeb8-d46d6dd3b5b2 :customerscenario: true :steps: - 1. Create a new LDAP Auth source with HTTPS, provide organization and + 1. Create a new LDAP Auth source with LDAPS, provide organization and location information. 2. Fill in all the fields appropriately. 3. Login with existing LDAP user present. :BZ: 1785621 - :expectedresults: LDAP auth source for HTTPS should be successful and LDAP login + :expectedresults: LDAP auth source for LDAPS should be successful and LDAP login should work as expected. :parametrized: yes """ if auth_data['auth_type'] == 'ipa': - set_certificate_in_satellite(server_type='IPA', sat=module_target_sat) username = settings.ipa.user account_name = auth_data['ldap_user_cn'] + server_type = 'IPA' else: - set_certificate_in_satellite( - server_type='AD', sat=module_target_sat, hostname=auth_data['ldap_hostname'] - ) username = settings.ldap.username account_name = f"cn={auth_data['ldap_user_cn']},{auth_data['base_dn']}" + server_type = 'AD' + cacert_pem = get_ldap_cacert_pem(module_target_sat, server_type, auth_data['ldap_hostname']) org = module_target_sat.api.Organization().create() loc = module_target_sat.api.Location().create() ldap_auth_name = gen_string('alphanumeric') @@ -184,6 +172,7 @@ def test_positive_create_with_https( 'ldap_server.name': ldap_auth_name, 'ldap_server.host': auth_data['ldap_hostname'], 'ldap_server.ldaps': True, + 'ldap_server.cacert': cacert_pem, 'ldap_server.server_type': auth_data['server_type'], 'account.account_name': account_name, 'account.password': auth_data['ldap_user_passwd'], @@ -205,6 +194,13 @@ def test_positive_create_with_https( assert ldap_source['ldap_server']['name'] == ldap_auth_name assert ldap_source['ldap_server']['host'] == auth_data['ldap_hostname'] assert ldap_source['ldap_server']['port'] == '636' + + # In AD's case, the CA cert uses CRLF line endings + # when this cert is uploaded to Satellite, the line endings are replaced + # with unix LF-only line endings. + assert ( + ldap_source['ldap_server']['cacert'].strip() == cacert_pem.replace("\r\n", "\n").strip() + ) with ( module_target_sat.ui_session( test_name, username, auth_data['ldap_user_passwd'] diff --git a/tests/foreman/ui/test_ldap_authentication.py b/tests/foreman/ui/test_ldap_authentication.py index 86e6a57271d..d05b7825d54 100644 --- a/tests/foreman/ui/test_ldap_authentication.py +++ b/tests/foreman/ui/test_ldap_authentication.py @@ -12,47 +12,21 @@ """ -import os - from fauxfactory import gen_url from navmazing import NavigationTriesExceeded import pyotp import pytest from robottelo.config import settings -from robottelo.constants import ANY_CONTEXT, CERT_PATH, LDAP_ATTR, PERMISSIONS +from robottelo.constants import ANY_CONTEXT, LDAP_ATTR, PERMISSIONS from robottelo.utils.datafactory import gen_string +from robottelo.utils.ldap import get_ldap_cacert_pem pytestmark = [pytest.mark.run_in_one_thread] EXTERNAL_GROUP_NAME = 'foobargroup' -def set_certificate_in_satellite(server_type, target_sat, hostname=None): - """update the cert settings in satellite based on type of ldap server""" - if server_type == 'IPA': - idm_cert_path_url = os.path.join(settings.ipa.hostname, 'ipa/config/ca.crt') - target_sat.download_file( - file_url=idm_cert_path_url, local_path=CERT_PATH, file_name='ipa.crt' - ) - elif server_type == 'AD': - assert hostname is not None - target_sat.execute('yum -y --disableplugin=foreman-protector install cifs-utils') - command = r'mount -t cifs -o username=administrator,pass={0} //{1}/c\$ /mnt' - target_sat.execute(command.format(settings.ldap.password, hostname)) - result = target_sat.execute( - f'cp /mnt/Users/Administrator/Desktop/satqe-QE-SAT6-AD-CA.cer {CERT_PATH}' - ) - if result.status != 0: - raise AssertionError('Failed to copy the AD server certificate at right path') - result = target_sat.execute(f'update-ca-trust extract && restorecon -R {CERT_PATH}') - if result.status != 0: - raise AssertionError('Failed to update and trust the certificate') - result = target_sat.execute('systemctl restart httpd') - if result.status != 0: - raise AssertionError(f'Failed to restart the httpd after applying {server_type} cert') - - @pytest.fixture def ldap_usergroup_name(target_sat): """Return some random usergroup name, @@ -1070,6 +1044,54 @@ def test_login_failure_if_internal_user_exist( target_sat.api.User(id=user.id).delete() +@pytest.mark.parametrize('ldap_auth_source', ['IPA'], indirect=True) +def test_ldaps_cacert_test_connection(session, ldap_auth_source, target_sat): + """LDAPS auth source fails test connection with a wrong cacert and succeeds with the right one. + + :id: eddf4ddf-03d2-4aa1-981f-c30078e3dc54 + + :steps: + 1. Open an existing LDAP auth source created without TLS. + 2. Enable LDAPS and provide a wrong cacert (the Satellite host certificate). + 3. Click test connection — expect failure. + 4. Replace the cacert with the real IPA CA certificate. + 5. Click test connection — expect success. + + :expectedresults: Test connection fails with a wrong CA certificate and passes once the + correct IPA CA certificate is provided. + + :parametrized: yes + """ + ldap_data, auth_source = ldap_auth_source + wrong_cacert_result = target_sat.execute( + 'cat /etc/pki/ca-trust/source/anchors/katello_server-host-cert.crt' + ) + assert wrong_cacert_result.status == 0, ( + f'Failed to read Satellite host cert: {wrong_cacert_result.stderr}' + ) + wrong_cacert = wrong_cacert_result.stdout + cacert = get_ldap_cacert_pem(target_sat, 'IPA', ldap_data['ldap_hostname']) + with session: + with pytest.raises(AssertionError) as error: + session.ldapauthentication.test_connection( + { + 'ldap_server.host': ldap_data['ldap_hostname'], + 'ldap_server.ldaps': True, + 'ldap_server.cacert': wrong_cacert, + } + ) + + assert error.match('certificate verify failed') + + session.ldapauthentication.test_connection( + { + 'ldap_server.host': ldap_data['ldap_hostname'], + 'ldap_server.ldaps': True, + 'ldap_server.cacert': cacert, + } + ) + + def test_userlist_with_external_admin( session, auth_source_ipa,