From cf2f1f2d3429650444685e07ee4e11bcd596bba8 Mon Sep 17 00:00:00 2001 From: vg Date: Thu, 12 Mar 2020 17:43:52 +0100 Subject: Rework test (coverage and refactorization) - better coverage (add contact tests) - split one big test with a loop on a dict to single test functions, giving better view on test advance and report - factorization of _csr and _san_csr to a single function --- tests/test_acme_dns_tiny.py | 189 +++++++++++++++++++++++++++----------------- 1 file changed, 115 insertions(+), 74 deletions(-) diff --git a/tests/test_acme_dns_tiny.py b/tests/test_acme_dns_tiny.py index 9a2562d..bb96765 100644 --- a/tests/test_acme_dns_tiny.py +++ b/tests/test_acme_dns_tiny.py @@ -2,9 +2,13 @@ import collections +import inspect +import logging import os +import subprocess import sys import tempfile + import pytest @@ -14,20 +18,45 @@ sys.path.append(os.path.realpath(".")) import acme_dns_tiny -DOMAIN = os.getenv('TEST_DOMAIN') ACME_STAGING_DIRECTORY = \ 'https://acme-staging-v02.api.letsencrypt.org/directory' +DOMAIN = os.getenv('TEST_DOMAIN') CONTACT = os.getenv('TEST_CONTACT') # form: mailto:name@domain +SCRIPT = os.getenv('TEST_SCRIPT') -def test_sanity_env(): - assert DOMAIN - assert CONTACT +def funcname(): + return inspect.currentframe().f_back.f_code.co_name -@pytest.fixture(scope='module') -def casemap(tmpdir_factory): +def parent_funcname(): + return inspect.currentframe().f_back.f_back.f_code.co_name + +def assert_success(capsys, args): + name = parent_funcname() + logging.info(f'assert_success({name}, {args})') + acme_dns_tiny.main(args) + captured = capsys.readouterr() + #assert not captured.err + certlist = captured.out.split('-----BEGIN CERTIFICATE-----') + assert len(certlist) == 3 + assert certlist[0] == '' + assert '-----END CERTIFICATE-----\n' in certlist[1] + assert '-----END CERTIFICATE-----\n' in certlist[2] + textchain = acme_dns_tiny.openssl(['x509', '-text', '-noout'], + stdin=captured.out) + assert 'Issuer' in textchain + + +def assert_assertion(args): + name = parent_funcname() + logging.info(f'assert_assertion({name}, {args})') + with pytest.raises(ValueError): + acme_dns_tiny.main(args) + +@pytest.fixture(scope='module') +def keys(tmpdir_factory): tmpdir = tmpdir_factory.mktemp("data") def _gen_key(name, size): @@ -35,98 +64,110 @@ def casemap(tmpdir_factory): acme_dns_tiny.openssl(['genrsa', '-out', path, str(size)]) return path - account_key = _gen_key('account_key', 4096) - domain_key = _gen_key('domain_key', 4096) - weak_account_key = _gen_key('weak_account_key', 1024) - weak_domain_key = _gen_key('weak_domain_key', 1024) + return { + 'account_key': _gen_key('account_key', 4096), + 'domain_key': _gen_key('domain_key', 4096), + 'weak_account_key': _gen_key('weak_account_key', 1024), + 'weak_domain_key': _gen_key('weak_domain_key', 1024), + } + + +@pytest.fixture(scope='module') +def csr_args(tmpdir_factory, keys): + tmpdir = tmpdir_factory.mktemp("data") default_args = { '--acme-directory': ACME_STAGING_DIRECTORY, - '--script': 'tests/test_script.sh', - '--account-key': account_key, + '--script': SCRIPT, + '--account-key': keys['account_key'], #'--ttl': 60, # already the default '--ttl': None, '--contact': None, - #'--contact': [CONTACT], # not supported by staging directory '--verbose': None, '--quiet': None, } - def _csr(name, domain_key, subj=f'/CN={DOMAIN}'): + def _csr(subj, key_name='domain_key', san=None, args={}): + name = parent_funcname() + domain_key = keys[key_name] path = str(tmpdir.join(name)) - acme_dns_tiny.openssl([ - 'req', '-new', '-key', domain_key, '-subj', subj, '-out', path - ]) - return {**default_args, '--csr': path} - - def _san_csr(name, subj='/', san=None): - path = str(tmpdir.join(name)) - with tempfile.NamedTemporaryFile('w', encoding='utf8') as conf, \ - open('/etc/ssl/openssl.cnf', 'r', encoding='utf8') as orig: - conf.write(orig.read()) - conf.write(f'\n[SAN]\nsubjectAltName={san}\n') - conf.flush() + if san is None: acme_dns_tiny.openssl([ - 'req', '-new', '-key', domain_key, '-subj', subj, - '-reqexts', 'SAN', '-config', conf.name, '-out', path + 'req', '-new', '-key', domain_key, '-subj', subj, '-out', path ]) - return {**default_args, '--csr': path} + else: + with tempfile.NamedTemporaryFile('w', encoding='utf8') as conf, \ + open('/etc/ssl/openssl.cnf', 'r', encoding='utf8') as orig: + conf.write(orig.read()) + conf.write(f'\n[SAN]\nsubjectAltName={san}\n') + conf.flush() + acme_dns_tiny.openssl([ + 'req', '-new', '-key', domain_key, '-subj', subj, + '-reqexts', 'SAN', '-config', conf.name, '-out', path + ]) + return {**default_args, '--csr': path, **args} - return { - # single domain - 'single': _csr('csr_single', domain_key), + return _csr - # single, but weak account key used - 'weakaccountkey': { - **_csr('csr_simple', domain_key), - '--account-key': weak_account_key, - }, - # single, but weak domain key used to sign csr - 'weakdomainkey': _csr('csr_weakdomain', weak_domain_key), +def test_sanity_env(): + assert bool(DOMAIN) + assert bool(CONTACT) + assert bool(SCRIPT) - # single, csr wrongly signed by account key - 'csrbyaccount': _csr('csr_byaccount', account_key), - # wildcard domain in CN - 'wildcard': _csr('csr_wildcard', domain_key, f'/CN=*.{DOMAIN}'), +def test_sanity_command(): + subprocess.run([SCRIPT, 'add', f'_acme-challenge.{DOMAIN}.', 'dummy']) - # san only - 'sanonly': _san_csr('csr_sanonly', - san=f'DNS:{DOMAIN},DNS:www.{DOMAIN}'), - # san and cn - 'san': _san_csr('csr_san', subj=f'/CN={DOMAIN}', - san=f'DNS:www.{DOMAIN}'), +def test_success_single_domain_in_cn(csr_args, capsys): + subj = f'/CN={DOMAIN}' + assert_success(capsys, csr_args(subj)) - # san wildcard (domain + *.domain, contrary to wildcard in cn) - 'wildcardsan': _san_csr('csr_wildcardsan', - san=f'DNS:{DOMAIN},DNS:*.{DOMAIN}') - } +def test_success_wildcard_in_cn(csr_args, capsys): + subj = f'/CN=*.{DOMAIN}' + assert_success(capsys, csr_args(subj)) -def assert_certificate_chain(captured): - #assert not captured.err - certlist = captured.out.split('-----BEGIN CERTIFICATE-----') - assert len(certlist) == 3 - assert certlist[0] == '' - assert '-----END CERTIFICATE-----\n' in certlist[1] - assert '-----END CERTIFICATE-----\n' in certlist[2] - textchain = acme_dns_tiny.openssl(['x509', '-text', '-noout'], - stdin=captured.out) - assert 'Issuer' in textchain + +def test_success_san_only(csr_args, capsys): + subj = '/' + san = f'DNS:{DOMAIN},DNS:www.{DOMAIN}' + assert_success(capsys, csr_args(subj=subj, san=san)) + + +def test_success_san_only_contact(csr_args, capsys): + subj = '/' + san = f'DNS:{DOMAIN},DNS:www.{DOMAIN}' + args = {'--contact': [CONTACT]} + assert_success(capsys, csr_args(subj=subj, san=san, args=args)) + + +def test_success_san_contact(csr_args, capsys): + subj = f'/CN={DOMAIN}' + san = f'DNS:www.{DOMAIN}' + args = {'--contact': [CONTACT]} + assert_success(capsys, csr_args(subj=subj, san=san, args=args)) + + +def test_success_wildcard_san(csr_args, capsys): + subj = '/' + san = f'DNS:{DOMAIN},DNS:*.{DOMAIN}' + assert_success(capsys, csr_args(subj=subj, san=san)) + + +def test_fail_weak_account_key(csr_args, keys): + subj = f'/CN={DOMAIN}' + args = {'--account-key': keys['weak_account_key']} + assert_assertion(csr_args(subj, args=args)) -def test_success(capsys, casemap): - for name in ('single', 'wildcard', 'sanonly', 'san', 'wildcardsan'): - print(f'test_success case: {name}', file=sys.stderr) - acme_dns_tiny.main(casemap[name]) - captured = capsys.readouterr() - assert_certificate_chain(captured) +def test_fail_weak_domain_key(csr_args): + subj = f'/CN={DOMAIN}' + assert_assertion(csr_args(subj, key_name='weak_domain_key')) -def test_assert(casemap): - for name in ('weakaccountkey', 'weakdomainkey', 'csrbyaccount'): - print(f'test_assert case: {name}', file=sys.stderr) - with pytest.raises(ValueError): - acme_dns_tiny.main(casemap[name]) +def test_fail_csr_by_account(csr_args): + subj = f'/CN={DOMAIN}' + # single, csr wrongly signed by account key + assert_assertion(csr_args(subj, key_name='account_key')) -- cgit v1.2.3