From 6166805d55a1d3b8c6d9629db14f7613706e4b4a Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Wed, 27 Mar 2019 16:27:57 +0100 Subject: [PATCH] Add a test to cover all assertions. Remove the CSR logic for now --- .../certbot_tests/test_main.py | 80 +++++++++++++++---- .../certbot_integration_tests/utils/misc.py | 65 --------------- examples/generate-csr.py | 72 ----------------- 3 files changed, 63 insertions(+), 154 deletions(-) delete mode 100644 examples/generate-csr.py diff --git a/certbot-ci/certbot_integration_tests/certbot_tests/test_main.py b/certbot-ci/certbot_integration_tests/certbot_tests/test_main.py index 9442b9f3f..031f90f6a 100644 --- a/certbot-ci/certbot_integration_tests/certbot_tests/test_main.py +++ b/certbot-ci/certbot_integration_tests/certbot_tests/test_main.py @@ -1,10 +1,14 @@ """Module executing integration tests against certbot core.""" from __future__ import print_function +import os +import shutil +from os.path import join import pytest from certbot_integration_tests.certbot_tests import context as certbot_context from certbot_integration_tests.certbot_tests.assertions import ( - assert_hook_execution, assert_save_renew_hook + assert_hook_execution, assert_save_renew_hook, assert_certs_count_for_lineage, + assert_world_permissions, assert_equals_group_owner, assert_equals_permissions, ) from certbot_integration_tests.utils import misc @@ -19,21 +23,63 @@ def context(request): integration_test_context.cleanup() -def test_manual_http_auth(context): - """Test the HTTP-01 challenge using manual plugin.""" - with misc.create_http_server(context.http_01_port) as webroot: - manual_http_hooks = misc.manual_http_hooks(webroot) +def test_manual_dns_auth(context): + """Test the DNS-01 challenge using manual plugin.""" + certname = context.domain('dns') + context.certbot([ + '-a', 'manual', '-d', certname, '--preferred-challenges', 'dns', + 'run', '--cert-name', certname, + '--manual-auth-hook', context.manual_dns_auth_hook, + '--manual-cleanup-hook', context.manual_dns_cleanup_hook, + '--pre-hook', 'echo wtf.pre >> "{0}"'.format(context.hook_probe), + '--post-hook', 'echo wtf.post >> "{0}"'.format(context.hook_probe), + '--renew-hook', 'echo renew >> "{0}"'.format(context.hook_probe) + ]) - certname = context.domain() - context.certbot([ - 'certonly', '-a', 'manual', '-d', certname, - '--cert-name', certname, - '--manual-auth-hook', manual_http_hooks[0], - '--manual-cleanup-hook', manual_http_hooks[1], - '--pre-hook', 'echo wtf.pre >> "{0}"'.format(context.hook_probe), - '--post-hook', 'echo wtf.post >> "{0}"'.format(context.hook_probe), - '--deploy-hook', 'echo deploy >> "{0}"'.format(context.hook_probe) - ]) - - assert_hook_execution(context.hook_probe, 'deploy') + with pytest.raises(AssertionError): + assert_hook_execution(context.hook_probe, 'renew') assert_save_renew_hook(context.config_dir, certname) + + +def test_renew(context): + """Test various certificate renew scenarios.""" + # First, we create a target certificate, with all hook dirs instantiated. + # We should have a new certificate, with hooks executed. + # Check also file permissions. + certname = context.domain('renew') + context.certbot([ + 'certonly', '-d', certname, '--rsa-key-size', '4096', + '--preferred-challenges', 'http-01' + ]) + + assert_certs_count_for_lineage(context.config_dir, certname, 1) + assert_world_permissions( + join(context.config_dir, 'archive/{0}/privkey1.pem'.format(certname)), 0) + + # Second, we force renew, and ensure that renewal hooks files are executed. + # Also check that file permissions are correct. + misc.generate_test_file_hooks(context.config_dir, context.hook_probe) + context.certbot(['renew']) + + assert_certs_count_for_lineage(context.config_dir, certname, 2) + assert_hook_execution(context.hook_probe, 'deploy') + assert_world_permissions( + join(context.config_dir, 'archive/{0}/privkey2.pem'.format(certname)), 0) + assert_equals_group_owner( + join(context.config_dir, 'archive/{0}/privkey1.pem'.format(certname)), + join(context.config_dir, 'archive/{0}/privkey2.pem'.format(certname))) + assert_equals_permissions( + join(context.config_dir, 'archive/{0}/privkey1.pem'.format(certname)), + join(context.config_dir, 'archive/{0}/privkey2.pem'.format(certname)), 0o074) + + os.chmod(join(context.config_dir, 'archive/{0}/privkey2.pem'.format(certname)), 0o444) + + # Third, we try to renew without force. + # It is not time, so no renew should occur, and no hooks should be executed. + open(context.hook_probe, 'w').close() + misc.generate_test_file_hooks(context.config_dir, context.hook_probe) + context.certbot_no_force_renew(['renew']) + + assert_certs_count_for_lineage(context.config_dir, certname, 2) + with pytest.raises(AssertionError): + assert_hook_execution(context.hook_probe, 'deploy') diff --git a/certbot-ci/certbot_integration_tests/utils/misc.py b/certbot-ci/certbot_integration_tests/utils/misc.py index e6d3815e1..787475740 100644 --- a/certbot-ci/certbot_integration_tests/utils/misc.py +++ b/certbot-ci/certbot_integration_tests/utils/misc.py @@ -40,71 +40,6 @@ def check_until_timeout(url): raise ValueError('Error, url did not respond after 150 attempts: {0}'.format(url)) -def find_certbot_root_directory(): - """ - Find the certbot root directory. To do so, this method will recursively move up in the directory - hierarchy, until finding the git root file, that corresponds to the certbot root directory. - :return str: the path to the certbot root directory - """ - script_path = os.path.realpath(__file__) - current_dir = os.path.dirname(script_path) - - while '.git' not in os.listdir(current_dir) and current_dir != os.path.dirname(current_dir): - current_dir = os.path.dirname(current_dir) - - dirs = os.listdir(current_dir) - if '.git' not in dirs: - raise ValueError('Error, could not find certbot sources root directory') - - return current_dir - - -def generate_csr(domains, key_path, csr_path, key_type='RSA'): - """ - Generate a CSR for the given domains, using the provided private key path. - This method uses the script demo to generate CSR that is available in `examples/generate-csr.py`. - :param str[] domains: the domain names to include in the CSR - :param str key_path: path to the private key - :param str csr_path: path to the CSR that will be generated - :param str key_type: type of the key (RSA or ECDSA) - """ - certbot_root_directory = find_certbot_root_directory() - script_path = os.path.join(certbot_root_directory, 'examples', 'generate-csr.py') - - command = [ - sys.executable, script_path, '--key-path', key_path, '--csr-path', csr_path, - '--key-type', key_type - ] - command.extend(domains) - subprocess.check_call(command) - - -def load_sample_data_path(workspace): - """ - Load the certbot configuration example designed to make OCSP tests, and return its path - :param str workspace: current test workspace directory path - :return str: the path to the loaded sample data directory - """ - certbot_root_directory = find_certbot_root_directory() - original = os.path.join(certbot_root_directory, 'tests', 'integration', 'sample-config') - copied = os.path.join(workspace, 'sample-config') - shutil.copytree(original, copied, symlinks=True) - return copied - - -def read_certificate(cert_path): - """ - Load the certificate from the provided path, and return a human readable version of it (TEXT mode). - :param str cert_path: the path to the certificate - :return: the TEXT version of the certificate, as it would be displayed by openssl binary - """ - with open(cert_path, 'r') as file: - data = file.read() - - cert = crypto.load_certificate(crypto.FILETYPE_PEM, data.encode('utf-8')) - return crypto.dump_certificate(crypto.FILETYPE_TEXT, cert).decode('utf-8') - - class GracefulTCPServer(socketserver.TCPServer): """ This subclass of TCPServer allows to gracefully reuse an address that has diff --git a/examples/generate-csr.py b/examples/generate-csr.py deleted file mode 100644 index 26ff035f3..000000000 --- a/examples/generate-csr.py +++ /dev/null @@ -1,72 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# This script generates a simple SAN CSR to be used with Let's Encrypt CA. -import os -import argparse -from OpenSSL import crypto - -try: - from cryptography.hazmat.backends import default_backend - from cryptography.hazmat.primitives.asymmetric import rsa, ec - from cryptography.hazmat.primitives.serialization import Encoding, PrivateFormat, NoEncryption - cryptography = True -except ImportError: - cryptography = False - -CWD = os.getcwd() - - -def generate_parser(): - one_parser = argparse.ArgumentParser(description='Generate a simple SAN CSR') - one_parser.add_argument('domain', nargs='+', help='A FQDN to add in the CSR.') - one_parser.add_argument('--key-path', default=os.path.join(CWD, 'key.pem'), - help='Path for the generated key ' - '(default is key.pem in current directory)') - one_parser.add_argument('--csr-path', default=os.path.join(CWD, 'csr.der'), - help='Path for the generated certificate ' - '(default is csr.der in current directory)') - one_parser.add_argument('--key-type', default='RSA', - choices=['RSA', 'ECDSA'], - help='Key type to use, ECDSA is supported only ' - 'if cryptography module is available') - return one_parser - - -def main(domains, key_path, csr_path, key_type): - san = ','.join(['DNS:{0}'.format(item) for item in domains]) - - if key_type == 'RSA': - key = crypto.PKey() - key.generate_key(crypto.TYPE_RSA, 2048) - elif key_type == 'ECDSA': - if not cryptography: - raise ValueError('Error, cryptography module is not installed,' - 'but is required to use ECDSA') - - key = ec.generate_private_key(ec.SECP384R1(), default_backend()) - key = key.private_bytes(encoding=Encoding.PEM, format=PrivateFormat.TraditionalOpenSSL, - encryption_algorithm=NoEncryption()) - key = crypto.load_privatekey(crypto.FILETYPE_PEM, key) - else: - raise ValueError('Invalid key type: {0}'.format(key_type)) - - with open(key_path, 'wb') as file: - file.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, key)) - - req = crypto.X509Req() - san_constraint = crypto.X509Extension(b'subjectAltName', False, san.encode('utf-8')) - req.add_extensions([san_constraint]) - - req.set_pubkey(key) - req.sign(key, 'sha256') - - with open(csr_path, 'wb') as file: - file.write(crypto.dump_certificate_request(crypto.FILETYPE_ASN1, req)) - - print('You can now run: certbot auth --csr {0}'.format(csr_path)) - - -if __name__ == '__main__': - parser = generate_parser() - namespace = parser.parse_args() - main(namespace.domain, namespace.key_path, namespace.csr_path, namespace.key_type)