Changeset - 75bfe558bba9
[Not reviewed]
1 5 0
Branko Majic (branko) - 4 years ago 2020-09-23 14:07:07
branko@majic.rs
MAR-158: Refactor ldap_server TLS-related tests to use nmap:

- Updated requirements to include defusedxml for safe parsing of XML
reports from nmap.
- Install nmap as part of preparation step.
- Refactored tests for TLS to use nmap ssl-enum-ciphers script for
listing available TLS versions and ciphers.
6 files changed with 106 insertions and 170 deletions:
0 comments (0 inline, 0 general)
requirements.in
Show inline comments
 
ansible~=2.9.0
 
defusedxml
 
dnspython
 
gimmecert~=0.4.0
 
molecule~=2.22.0
requirements.txt
Show inline comments
 
@@ -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
roles/ldap_server/molecule/default/prepare.yml
Show inline comments
 
@@ -140,3 +140,8 @@
 
      apt:
 
        name: net-tools
 
        state: present
 

	
 
    - name: Install nmap utility for testing TLS
 
      apt:
 
        name: nmap
 
        state: present
roles/ldap_server/molecule/default/tests/test_mandatory.py
Show inline comments
 
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):
roles/ldap_server/molecule/default/tests/test_optional.py
Show inline comments
 
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):
roles/ldap_server/molecule/default/tests/tls_ciphers.py
Show inline comments
 
deleted file
0 comments (0 inline, 0 general)