diff --git a/requirements.in b/requirements.in index cc03e1dc845b2ace0885daf0e2161ad030569bec..50ed055de09341fafe1332b56046d63cab931ce0 100644 --- a/requirements.in +++ b/requirements.in @@ -1,4 +1,5 @@ ansible~=2.9.0 +defusedxml dnspython gimmecert~=0.4.0 molecule~=2.22.0 diff --git a/requirements.txt b/requirements.txt index 153458d401531caf98437608bdb6bf51cf6faf7e..10c837d4e3e1018f321710237b7bc865ebb62d2a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,18 +6,18 @@ # alabaster==0.7.12 # via sphinx ansible-lint==4.2.0 # via molecule -ansible==2.9.11 # via -r requirements.in, ansible-lint, molecule +ansible==2.9.13 # via -r requirements.in, ansible-lint, molecule anyconfig==0.9.7 # via molecule appdirs==1.4.4 # via virtualenv arrow==0.16.0 # via jinja2-time aspy.yaml==1.3.0 # via pre-commit -attrs==20.1.0 # via pytest +attrs==20.2.0 # via pytest babel==2.8.0 # via sphinx bcrypt==3.1.7 # via paramiko binaryornot==0.4.4 # via cookiecutter cerberus==1.3.2 # via molecule certifi==2020.6.20 # via requests -cffi==1.14.2 # via bcrypt, cryptography, pynacl +cffi==1.14.3 # via bcrypt, cryptography, pynacl cfgv==2.0.1 # via pre-commit chardet==3.0.4 # via binaryornot, requests click-completion==0.5.2 # via molecule @@ -25,6 +25,7 @@ click==7.1.2 # via click-completion, cookiecutter, molecule, pip-to colorama==0.4.3 # via molecule, python-gilt cookiecutter==1.7.2 # via molecule cryptography==2.9.2 # via ansible, gimmecert, paramiko +defusedxml==0.6.0 # via -r requirements.in distlib==0.3.1 # via virtualenv dnspython==1.16.0 # via -r requirements.in docutils==0.16 # via sphinx @@ -33,7 +34,7 @@ filelock==3.0.12 # via virtualenv flake8==3.8.3 # via molecule gimmecert==0.4.0 # via -r requirements.in git-url-parse==1.2.2 # via python-gilt -identify==1.4.29 # via pre-commit +identify==1.5.4 # via pre-commit idna==2.10 # via requests imagesize==1.2.0 # via sphinx importlib-metadata==1.7.0 # via flake8, pluggy, pre-commit, pytest, virtualenv @@ -45,13 +46,13 @@ markupsafe==1.1.1 # via cookiecutter, jinja2 mccabe==0.6.1 # via flake8 molecule==2.22 # via -r requirements.in monotonic==1.5 # via fasteners -more-itertools==8.4.0 # via pytest +more-itertools==8.5.0 # via pytest nodeenv==1.5.0 # via pre-commit packaging==20.4 # via pytest, sphinx -paramiko==2.7.1 # via -r requirements.in, molecule +paramiko==2.7.2 # via -r requirements.in, molecule pathlib2==2.3.5 # via pytest pathspec==0.8.0 # via yamllint -pbr==5.4.5 # via git-url-parse, python-gilt +pbr==5.5.0 # via git-url-parse, python-gilt pexpect==4.8.0 # via molecule pip-tools==5.3.1 # via -r requirements.in pluggy==0.13.1 # via pytest @@ -65,10 +66,10 @@ pyasn1==0.4.8 # via pyasn1-modules, python-ldap pycodestyle==2.6.0 # via flake8 pycparser==2.20 # via cffi pyflakes==2.2.0 # via flake8 -pygments==2.6.1 # via sphinx +pygments==2.7.1 # via sphinx pynacl==1.4.0 # via paramiko pyparsing==2.4.7 # via packaging -pytest==6.0.1 # via testinfra +pytest==6.0.2 # via testinfra python-dateutil==2.8.1 # via arrow, gimmecert python-gilt==1.2.2 # via molecule python-ldap==3.3.1 # via -r requirements.in @@ -77,9 +78,9 @@ python-vagrant==0.5.15 # via -r requirements.in pytz==2020.1 # via babel pyyaml==5.3.1 # via ansible, ansible-lint, aspy.yaml, molecule, pre-commit, python-gilt, yamllint requests==2.24.0 # via cookiecutter, sphinx -ruamel.yaml.clib==0.2.0 # via ruamel.yaml -ruamel.yaml==0.16.10 # via ansible-lint -sh==1.13.1 # via molecule, python-gilt +ruamel.yaml.clib==0.2.2 # via ruamel.yaml +ruamel.yaml==0.16.12 # via ansible-lint +sh==1.14.0 # via molecule, python-gilt shellingham==1.3.2 # via click-completion six==1.15.0 # via ansible-lint, bcrypt, cfgv, click-completion, cookiecutter, cryptography, fasteners, molecule, packaging, pathlib2, pip-tools, pre-commit, pynacl, python-dateutil, sphinx, testinfra, virtualenv snowballstemmer==2.0.0 # via sphinx @@ -98,5 +99,5 @@ yamllint==1.24.2 # via molecule zipp==1.2.0 # via importlib-metadata, importlib-resources # The following packages are considered to be unsafe in a requirements file: -pip==20.2.2 # via -r requirements.in, pip-tools -setuptools==49.6.0 # via -r requirements.in, cerberus, sphinx +pip==20.2.3 # via -r requirements.in, pip-tools +setuptools==50.3.0 # via -r requirements.in, cerberus, sphinx diff --git a/roles/ldap_server/molecule/default/prepare.yml b/roles/ldap_server/molecule/default/prepare.yml index 32a6c778bc3b4ce3472fbbc54d712cb64977551a..7325fe141a03fda734ff3a57120daa705f15f746 100644 --- a/roles/ldap_server/molecule/default/prepare.yml +++ b/roles/ldap_server/molecule/default/prepare.yml @@ -140,3 +140,8 @@ apt: name: net-tools state: present + + - name: Install nmap utility for testing TLS + apt: + name: nmap + state: present diff --git a/roles/ldap_server/molecule/default/tests/test_mandatory.py b/roles/ldap_server/molecule/default/tests/test_mandatory.py index 51efd6cd9cfce0361c962aaebb822b70802ec914..8bdafacc5c7f210c5cdf966babd6a5983c17155c 100644 --- a/roles/ldap_server/molecule/default/tests/test_mandatory.py +++ b/roles/ldap_server/molecule/default/tests/test_mandatory.py @@ -1,11 +1,9 @@ import os -import pytest +import defusedxml.ElementTree as ElementTree import testinfra.utils.ansible_runner -from tls_ciphers import ALL_CIPHERS - testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner( os.environ['MOLECULE_INVENTORY_FILE']).get_hosts('parameters-mandatory') @@ -55,9 +53,10 @@ def test_certificate_validity_check_configuration(host): assert config.content_string == "/etc/ssl/certs/%s_ldap.pem" % inventory_hostname -def test_tls_configuration(host): +def test_tls_connectivity(host): """ - Tests if the TLS has been configured correctly and works. + Tests if it is possible to connect to the LDAP server using + STARTTLS/TLS. """ starttls = host.run('ldapwhoami -Z -x -H ldap://parameters-mandatory.local/') @@ -68,54 +67,50 @@ def test_tls_configuration(host): assert tls.rc == 0 assert tls.stdout == 'anonymous\n' - old_tls_versions_disabled = host.run("echo 'Q' | openssl s_client -no_tls1_2 -connect parameters-mandatory.local:636") - assert old_tls_versions_disabled.rc != 0 - assert "CONNECTED" in old_tls_versions_disabled.stdout - -# @TODO: Under Debian Stretch, the DHE ciphers are not usable due to a -# bug present in OpenLDAP 2.4.44. See -# https://bugs.launchpad.net/ubuntu/+source/openldap/+bug/1656979 for -# details. It should be possible to fix this problem once switch to -# buster is made. -ENABLED_CIPHERS = [ - # "DHE-RSA-AES128-GCM-SHA256", - # "DHE-RSA-AES256-GCM-SHA384", - # "DHE-RSA-CHACHA20-POLY1305", - "ECDHE-RSA-AES128-GCM-SHA256", - "ECDHE-RSA-AES256-GCM-SHA384", - "ECDHE-RSA-CHACHA20-POLY1305", -] +def test_tls_version_and_ciphers(host): + """ + Tests if the correct TLS version and ciphers have been enabled. + """ -DISABLED_CIPHERS = sorted(list(set(ALL_CIPHERS)-set(ENABLED_CIPHERS))) + expected_tls_versions = ["TLSv1.2"] + # @TODO: Under Debian Stretch, the DHE ciphers are not usable due + # to a bug present in OpenLDAP 2.4.44. See + # https://bugs.launchpad.net/ubuntu/+source/openldap/+bug/1656979 + # for details. It should be possible to fix this problem once + # switch to buster is mad.e + expected_tls_ciphers = [ + # "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", + # "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", + # "TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256", + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", + ] -@pytest.mark.parametrize("cipher", ENABLED_CIPHERS) -def test_enabled_tls_ciphers(host, cipher): - """ - Tests available TLS ciphers on the server. - """ + # Run the nmap scanner against the LDAP server, and fetch the + # results. + nmap = host.run("nmap -sV --script ssl-enum-ciphers -p 636 localhost -oX /tmp/report.xml") + assert nmap.rc == 0 + report_content = host.file('/tmp/report.xml').content_string - hostname = host.run('hostname').stdout.strip() - fqdn = hostname + report_root = ElementTree.fromstring(report_content) - client = host.run("echo 'Q' | openssl s_client -cipher %s -connect %s:636", cipher, fqdn) - assert client.rc == 0 - assert cipher in client.stdout + tls_versions = [] + tls_ciphers = set() + for child in report_root.findall("./host/ports/port/script/table"): + tls_versions.append(child.attrib['key']) -@pytest.mark.parametrize("cipher", DISABLED_CIPHERS) -def test_disabled_tls_ciphers(host, cipher): - """ - Tests available TLS ciphers on the server. - """ + for child in report_root.findall(".//table[@key='ciphers']/table/elem[@key='name']"): + tls_ciphers.add(child.text) - hostname = host.run('hostname').stdout.strip() - fqdn = hostname + tls_versions.sort() + tls_ciphers = sorted(list(tls_ciphers)) - client = host.run("echo 'Q' | openssl s_client -cipher %s -connect %s:636", cipher, fqdn) - assert client.rc != 0 - assert cipher not in client.stdout + assert tls_versions == expected_tls_versions + assert tls_ciphers == expected_tls_ciphers def test_ssf_configuration(host): diff --git a/roles/ldap_server/molecule/default/tests/test_optional.py b/roles/ldap_server/molecule/default/tests/test_optional.py index a3d5ae759900d1d26b29794e9b18d35d5dc46c05..374e601ac1116e3e322abd4d17a69d61e9951869 100644 --- a/roles/ldap_server/molecule/default/tests/test_optional.py +++ b/roles/ldap_server/molecule/default/tests/test_optional.py @@ -1,13 +1,11 @@ import os -import pytest +import defusedxml.ElementTree as ElementTree import testinfra.utils.ansible_runner from helpers import parse_ldif -from tls_ciphers import ALL_CIPHERS - testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner( os.environ['MOLECULE_INVENTORY_FILE']).get_hosts('parameters-optional') @@ -57,72 +55,67 @@ def test_certificate_validity_check_configuration(host): assert config.content_string == "/etc/ssl/certs/%s_ldap.pem" % inventory_hostname -def test_tls_configuration(host): +def test_tls_connectivity(host): """ - Tests if the TLS has been configured correctly and works. + Tests if it is possible to connect to the LDAP server using + STARTTLS/TLS. """ - ldap_starttls = host.run('ldapwhoami -Z -x -H ldap://parameters-optional/') - assert ldap_starttls.rc == 0 - assert ldap_starttls.stdout == 'anonymous\n' - - ldap_tls = host.run('ldapwhoami -x -H ldaps://parameters-optional/') - assert ldap_tls.rc == 0 - assert ldap_tls.stdout == 'anonymous\n' + starttls = host.run('ldapwhoami -Z -x -H ldap://parameters-optional/') + assert starttls.rc == 0 + assert starttls.stdout == 'anonymous\n' - old_tls_versions_disabled = host.run("echo 'Q' | openssl s_client -no_tls1_2 -connect parameters-optional:636") - assert old_tls_versions_disabled.rc == 0 - assert "CONNECTED" in old_tls_versions_disabled.stdout + tls = host.run('ldapwhoami -x -H ldaps://parameters-optional/') + assert tls.rc == 0 + assert tls.stdout == 'anonymous\n' -# @TODO: Under Debian Stretch, the DHE ciphers are not usable due to a -# bug present in OpenLDAP 2.4.44. See -# https://bugs.launchpad.net/ubuntu/+source/openldap/+bug/1656979 for -# details. It should be possible to fix this problem once switch to -# buster is made. -ENABLED_CIPHERS = [ - # "DHE-RSA-AES128-GCM-SHA256", - # "DHE-RSA-AES256-GCM-SHA384", - # "DHE-RSA-CHACHA20-POLY1305", - "ECDHE-RSA-AES128-SHA256", - "ECDHE-RSA-AES128-SHA", - "ECDHE-RSA-AES128-GCM-SHA256", - "ECDHE-RSA-AES128-SHA", - "ECDHE-RSA-AES128-SHA256", - "ECDHE-RSA-AES256-GCM-SHA384", - "ECDHE-RSA-AES256-SHA", - "ECDHE-RSA-AES256-SHA384", -] +def test_tls_version_and_ciphers(host): + """ + Tests if the correct TLS version and ciphers have been enabled. + """ -DISABLED_CIPHERS = sorted(list(set(ALL_CIPHERS)-set(ENABLED_CIPHERS))) + expected_tls_versions = ["TLSv1.1", "TLSv1.2"] + # @TODO: Under Debian Stretch, the DHE ciphers are not usable due + # to a bug present in OpenLDAP 2.4.44. See + # https://bugs.launchpad.net/ubuntu/+source/openldap/+bug/1656979 + # for details. It should be possible to fix this problem once + # switch to buster is mad.e + expected_tls_ciphers = [ + # "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", + # "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", + # "TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256", + "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", + "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", + "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384", + "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", + ] -@pytest.mark.parametrize("cipher", ENABLED_CIPHERS) -def test_enabled_tls_ciphers(host, cipher): - """ - Tests available TLS ciphers on the server. - """ + # Run the nmap scanner against the LDAP server, and fetch the + # results. + nmap = host.run("nmap -sV --script ssl-enum-ciphers -p 636 localhost -oX /tmp/report.xml") + assert nmap.rc == 0 + report_content = host.file('/tmp/report.xml').content_string - hostname = host.run('hostname').stdout.strip() - fqdn = hostname + report_root = ElementTree.fromstring(report_content) - client = host.run("echo 'Q' | openssl s_client -cipher %s -connect %s:636", cipher, fqdn) - assert client.rc == 0 - assert cipher in client.stdout + tls_versions = [] + tls_ciphers = set() + for child in report_root.findall("./host/ports/port/script/table"): + tls_versions.append(child.attrib['key']) -@pytest.mark.parametrize("cipher", DISABLED_CIPHERS) -def test_disabled_tls_ciphers(host, cipher): - """ - Tests available TLS ciphers on the server. - """ + for child in report_root.findall(".//table[@key='ciphers']/table/elem[@key='name']"): + tls_ciphers.add(child.text) - hostname = host.run('hostname').stdout.strip() - fqdn = hostname + tls_versions.sort() + tls_ciphers = sorted(list(tls_ciphers)) - client = host.run("echo 'Q' | openssl s_client -cipher %s -connect %s:636", cipher, fqdn) - assert client.rc != 0 - assert cipher not in client.stdout + assert tls_versions == expected_tls_versions + assert tls_ciphers == expected_tls_ciphers def test_ssf_configuration(host): diff --git a/roles/ldap_server/molecule/default/tests/tls_ciphers.py b/roles/ldap_server/molecule/default/tests/tls_ciphers.py deleted file mode 100644 index 9a99acdeb943d946eadc058c414580e5f66fb8d5..0000000000000000000000000000000000000000 --- a/roles/ldap_server/molecule/default/tests/tls_ciphers.py +++ /dev/null @@ -1,59 +0,0 @@ -ALL_CIPHERS = [ - "AES128-GCM-SHA256", - "AES128-SHA", - "AES128-SHA256", - "AES256-GCM-SHA384", - "AES256-SHA", - "AES256-SHA256", - "DHE-PSK-AES128-CBC-SHA", - "DHE-PSK-AES128-CBC-SHA256", - "DHE-PSK-AES128-GCM-SHA256", - "DHE-PSK-AES256-CBC-SHA", - "DHE-PSK-AES256-CBC-SHA384", - "DHE-PSK-AES256-GCM-SHA384", - "DHE-PSK-CHACHA20-POLY1305", - "DHE-RSA-AES128-GCM-SHA256", - "DHE-RSA-AES128-SHA", - "DHE-RSA-AES128-SHA256", - "DHE-RSA-AES256-GCM-SHA384", - "DHE-RSA-AES256-SHA", - "DHE-RSA-AES256-SHA256", - "DHE-RSA-CHACHA20-POLY1305", - "ECDHE-ECDSA-AES128-GCM-SHA256", - "ECDHE-ECDSA-AES128-SHA", - "ECDHE-ECDSA-AES128-SHA256", - "ECDHE-ECDSA-AES256-GCM-SHA384", - "ECDHE-ECDSA-AES256-SHA", - "ECDHE-ECDSA-AES256-SHA384", - "ECDHE-ECDSA-CHACHA20-POLY1305", - "ECDHE-PSK-AES128-CBC-SHA", - "ECDHE-PSK-AES128-CBC-SHA256", - "ECDHE-PSK-AES256-CBC-SHA", - "ECDHE-PSK-AES256-CBC-SHA384", - "ECDHE-PSK-CHACHA20-POLY1305", - "ECDHE-RSA-AES128-GCM-SHA256", - "ECDHE-RSA-AES128-SHA", - "ECDHE-RSA-AES128-SHA256", - "ECDHE-RSA-AES256-GCM-SHA384", - "ECDHE-RSA-AES256-SHA", - "ECDHE-RSA-AES256-SHA384", - "ECDHE-RSA-CHACHA20-POLY1305", - "PSK-AES128-CBC-SHA", - "PSK-AES128-CBC-SHA256", - "PSK-AES128-GCM-SHA256", - "PSK-AES256-CBC-SHA", - "PSK-AES256-CBC-SHA384", - "PSK-AES256-GCM-SHA384", - "PSK-CHACHA20-POLY1305", - "RSA-PSK-AES128-CBC-SHA", - "RSA-PSK-AES128-CBC-SHA256", - "RSA-PSK-AES128-GCM-SHA256", - "RSA-PSK-AES256-CBC-SHA", - "RSA-PSK-AES256-CBC-SHA384", - "RSA-PSK-AES256-GCM-SHA384", - "RSA-PSK-CHACHA20-POLY1305", - "SRP-AES-128-CBC-SHA", - "SRP-AES-256-CBC-SHA", - "SRP-RSA-AES-128-CBC-SHA", - "SRP-RSA-AES-256-CBC-SHA", -]