def test_deploy(ssl_manager): key_path, crt_path = tests_util.create_autossl_signed_cert( ssl_blueprint=ssl_manager.ssl_blueprint, output_folder=os.environ['AUTOSSL_STORAGE_PATH'], ) # deploy them on server ssl_manager.deploy(certificate_path=crt_path, private_key_path=key_path) to_be_renewed, servers_to_update = ssl_manager.get_renewal_status() assert to_be_renewed is False assert len(servers_to_update) == 0 # ensure first server contain only server certificate crt_path_server_1 = util.Path(os.environ['AUTOSSL_CRT_PATH']).joinpath( ssl_manager.ssl_blueprint.name + '.crt') assert crt_path_server_1.read_text().count( '-----BEGIN CERTIFICATE-----') == 1 assert crt_path_server_1.read_bytes() == crt_path.read_bytes() # and 2nd server contains certificate with full chain of trust (2 certificates) crt_path_server_2 = util.Path(os.environ['AUTOSSL_CRT_PATH_2']).joinpath( ssl_manager.ssl_blueprint.name + '.crt') crt_server_2_content = crt_path_server_2.read_text() # we should have 2 certificates: server + CA certificates assert crt_server_2_content.count('-----BEGIN CERTIFICATE-----') == 2 # retrieve server certificate (= the first one) and verify it first_certificate = '-----BEGIN CERTIFICATE-----' + crt_server_2_content.split( '-----BEGIN CERTIFICATE-----')[1] assert first_certificate.strip() == crt_path.read_text().strip()
def env_config(): # create temporary directories use by blueprint temp_crt_dir = tempfile.mkdtemp() os.environ['AUTOSSL_CRT_PATH'] = temp_crt_dir temp_storage_dir = tempfile.mkdtemp() os.environ['AUTOSSL_STORAGE_PATH'] = temp_storage_dir temp_tracking_dir = tempfile.mkdtemp() os.environ['AUTOSSL_TRACKING_PATH'] = temp_tracking_dir yield EnvConfig(util.Path(temp_crt_dir), util.Path(temp_storage_dir), util.Path(temp_tracking_dir)) # cleanup generated artifacts shutil.rmtree(temp_crt_dir, ignore_errors=True) shutil.rmtree(temp_storage_dir, ignore_errors=True)
def create_self_signed_certificate(crt_name, output_path, common_name, sans=None, certificate_validity_days=365): """Generate self signed SSL certificate :param crt_name: name of file generated (without extension) :type crt_name: str :param output_path: ouput directory to generate certificate :type output_path: pathlib.Path or str :param common_name: certificate common name :type common_name: str :param sans: certificate alternate names :type sans: list :param certificate_validity_days: validity duration of certificate in days :type certificate_validity_days: int """ if not isinstance(output_path, util.Path): output_path = util.Path(str(output_path)) key_path = output_path.joinpath(crt_name + '.key') key_content, csr_path = ssl.generate_csr(name=crt_name, common_name=common_name, sans=sans, output_path=output_path) crt_path = output_path.joinpath(crt_name + '.crt') crt_content = create_signed_certificate( csr_path=csr_path, certificate_validity_days=certificate_validity_days, ) crt_path.write_bytes(crt_content) key_path.write_bytes(key_content) return key_path, crt_path
def ca_keypair_path(): key, crt = tests_util.create_ca_certificate(ca_name='Autossl') ca_temp_dir = util.Path(tempfile.mkdtemp()) ca_crt_path = ca_temp_dir / 'local_ca.crt' ca_key_path = ca_temp_dir / 'local_ca.key' ca_crt_path.write_bytes(crt) ca_key_path.write_bytes(key) yield CertificateKeyPair(ca_key_path, ca_crt_path) # cleanup temp folders shutil.rmtree(str(ca_temp_dir), ignore_errors=True)
def subca_keypair_path(subca_manager, ca_keypair_path): storage_path = util.Path(os.environ['AUTOSSL_STORAGE_PATH']) key_path = storage_path.joinpath(subca_manager.ssl_blueprint.name + '.key') csr_path = storage_path.joinpath(subca_manager.ssl_blueprint.name + '.csr') crt_path = storage_path.joinpath(subca_manager.ssl_blueprint.name + '.crt') bundle_path = storage_path.joinpath(subca_manager.ssl_blueprint.name + '.bundle') # generate sub-CA certificate request and key subca_manager.request_renewal( force=True, # disable interactive user input ) # simulate CA signing crt_content = tests_util.create_signed_certificate( csr_path=csr_path, ca_crt_path=ca_keypair_path.crt, ca_key_path=ca_keypair_path.key, certificate_validity_days=100) crt_path.write_bytes(crt_content) bundle_path.write_bytes(ca_keypair_path.crt.read_bytes() + crt_content) yield CertificateKeyPair(key_path, crt_path)
def create_autossl_signed_cert(ssl_blueprint, output_folder): """Create certificate signed by Autossl CA for specified blueprint and in specified location :param ssl_blueprint: SSL blueprint for which to generate certificate :type ssl_blueprint: ssl.SslBlueprint :param output_folder: local folder where to generate new key and signed certificate :type output_folder: pathlib.Path :return: 2-tuple(local path to private key, local path to signed certificate) :rtype: tuple(pathlib.Path, pathlib.Path) """ if not isinstance(output_folder, util.Path): output_folder = util.Path(output_folder) ca_crt_path = DATA_PATH / 'ca' / 'autossl_ca.crt' ca_key_path = DATA_PATH / 'ca' / 'autossl_ca.key' crt_path = output_folder.joinpath(ssl_blueprint.name + '.crt') key_path = output_folder.joinpath(ssl_blueprint.name + '.key') # generate new CSR key_content, csr_path = ssl.generate_csr( name=ssl_blueprint.name, common_name=ssl_blueprint.certificate.common_name, sans=ssl_blueprint.certificate.sans, output_path=output_folder, ) key_path.write_bytes(key_content) # sign a new certificate with the CA crt_content = create_signed_certificate( csr_path=csr_path, ca_crt_path=ca_crt_path, ca_key_path=ca_key_path, ) crt_path.write_bytes(crt_content) return key_path, crt_path
def parse_arguments(args=None): parser = argparse.ArgumentParser(description=autossl.__description__) parser.add_argument( '--credentials', action='append', default=[], help='Generic user/password credentials. Format: name:user:password') parser.add_argument('--credentials-file', type=util.Path, default=util.Path('~/.autossl').expanduser(), help='Local file to store credentials.') parser.add_argument('--debug', action="store_const", const=logging.DEBUG, default=logging.INFO, help='Use DEBUG log level rather than INFO') parser.add_argument( "--staging", action='store_true', help="Testing mode (for example, use staging CA servers)") parser.add_argument( "--config", type=util.Path, help= "Path to a file containing common configuration. Same format than SSL blueprint." ) parser.add_argument( "--blueprint", required=False, type=util.Path, help= "Path to the definition of certificates. Can be single blueprint or a folder" ) action_subparser = parser.add_subparsers(title="action", dest="action") action_subparser.required = True # needed for python3 compatibility ################ # VERSION ################ action_subparser.add_parser("version", help="Display autossl current version") ######################################## # Parser to check if certificate needs to be renewed ######################################## action_subparser.add_parser("check", help="Check for expired certificate.") ######################################## # Parser to request certificate renewal ######################################## action_parser_renew = action_subparser.add_parser( "renew", help="Request certificate renewal") action_parser_renew.add_argument( "--force", action='store_true', help="Renew certificate now (even if current one is still valid)") ######################################## # Parser to deploy certificate on server ######################################## action_parser_deploy = action_subparser.add_parser( "deploy", help="Deploy certificate") action_parser_deploy.add_argument( '-t', '--tracking-record', help='Tracking record ID for the change, ' 'and also potentially containing certificate to deploy.') action_parser_deploy.add_argument( '-k', '--private-key', type=util.Path, help='Local path to certificate private key') action_parser_deploy.add_argument( '-c', '--certificate', type=util.Path, help='Local path to ssl signed certificate') action_parser_deploy.add_argument( '--all-servers', action='store_true', default=False, help= 'Deploy certificate on all servers rather than just the ones out of synch.' ) return parser.parse_args(args)
def main(args=None): if args is None: args = sys.argv[1:] options = parse_arguments(args=args) logger.setLevel(options.debug or logger.level) # this is the only action that does not require authentication if options.action == "version": print(display_version()) return # actions that can be done on multiple blueprints if options.action in ['check', 'renew']: # build list of all blueprints to process blueprints = [] if options.blueprint.is_file(): blueprints.append(options.blueprint) elif options.blueprint.is_dir(): for root_path, _, filenames in os.walk(options.blueprint): for filename in fnmatch.filter(filenames, '*.yaml'): blueprints.append(util.Path(root_path) / filename) else: raise IOError("Invalid path specified: '%s'" % options.blueprint) # process blueprints 1 by 1 for blueprint in blueprints: my_ssl_manager = manager.SslManager( global_config=options.config, blueprint_path=blueprint, credentials=parse_credentials( cli_credentials=options.credentials, credentials_file=options.credentials_file, ), staging=options.staging, ) if options.action == 'check': logger.info("Processing blueprint %s" % blueprint) to_be_renewed, servers_to_update = my_ssl_manager.get_renewal_status( ) if not to_be_renewed and len(servers_to_update) == 0: logger.info( "Certificate and all servers up to date for '%s'. Nothing to do.", my_ssl_manager.ssl_blueprint.name) elif options.action == 'renew': my_ssl_manager.renew(force=options.force) # action on a single blueprint elif options.action == 'deploy': my_ssl_manager = manager.SslManager( global_config=options.config, blueprint_path=options.blueprint, credentials=parse_credentials( cli_credentials=options.credentials, credentials_file=options.credentials_file), staging=options.staging, ) my_ssl_manager.deploy( tracking_record_id=options.tracking_record, certificate_path=options.certificate, private_key_path=options.private_key, all_servers=options.all_servers, )
# functions to help testing and made available for use in autossl plugins from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import serialization, hashes from cryptography.hazmat.primitives.asymmetric import rsa from cryptography.x509.oid import NameOID from cryptography import x509 import datetime import random import string from autossl import ssl, util DATA_PATH = util.Path(__file__).parent.joinpath('data') def create_ca_certificate(ca_name, key_size=4096, certificate_validity_days=365): key = rsa.generate_private_key(public_exponent=65537, key_size=key_size, backend=default_backend()) key_id = x509.SubjectKeyIdentifier.from_public_key(key.public_key()) subject = issuer = x509.Name( [x509.NameAttribute(NameOID.COMMON_NAME, u"%s" % ca_name)]) now = datetime.datetime.utcnow() serial = x509.random_serial_number() cert = x509.CertificateBuilder() \ .subject_name(subject) \
import base64 import collections import datetime import json try: from unittest import mock # py3 except ImportError: import mock # py2 import pytest from autossl import exception, ssl, util from autossl.server import incapsula from tests import util as tests_util DATA_PATH = util.Path(__file__).parent / 'data' CertificateKeyPair = collections.namedtuple('CertificateKeyPair', 'key crt') SITE_ID = '12345678' API_KEY = '6f4d1878-f2c6-446b-8932-636b8f1705a7' API_ID = '12345' CHAIN_OF_TRUST = [ """"-----BEGIN CERTIFICATE----- MIIEkjCCA3qgAwIBAgIQCgFBQgAAAVOFc2oLheynCDANBgkqhkiG9w0BAQsFADA/MSQwIgYDVQQK ExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMTDkRTVCBSb290IENBIFgzMB4X DTE2MDMxNzE2NDA0NloXDTIxMDMxNzE2NDA0NlowSjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUxl dCdzIEVuY3J5cHQxIzAhBgNVBAMTGkxldCdzIEVuY3J5cHQgQXV0aG9yaXR5IFgzMIIBIjANBgkq hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnNMM8FrlLke3cl03g7NoYzDq1zUmGSXhvb418XCSL7e4 S0EFq6meNQhY7LEqxGiHC6PjdeTm86dicbp5gWAf15Gan/PQeGdxyGkOlZHP/uaZ6WA8SMx+yk13 EiSdRxta67nsHjcAHJyse6cF6s5K671B5TaYucv9bTyWaN8jKkKQDIZ0Z8h/pZq4UmEUEz9l6YKH y9v6Dlb2honzhT+Xhq+w3Brvaw2VFn3EK6BlspkENnWAa6xK8xuQSXgvopZPKiAlKQTGdMDQMc2P
from autossl import util from autossl.server.local import LocalServer @pytest.mark.parametrize('class_path, exception_type', [ ('MyClass', ValueError), ('my.package.does.not.exist.MyClass', ImportError), ('autossl.server.local.Dummy', AttributeError), ]) def test_str_to_class_error(class_path, exception_type): with pytest.raises(exception_type): util.str_to_class(class_path=class_path) @pytest.mark.parametrize('class_path,class_type', [ ('autossl.server.local.LocalServer', LocalServer), ]) def test_str_to_class(class_path, class_type): assert util.str_to_class(class_path=class_path) == class_type @pytest.mark.parametrize('path', [ None, util.Path(tempfile.mkdtemp()) ]) def test_TempDir(path): with util.TempDir(path=path) as temp_folder: assert temp_folder.path.exists() assert not temp_folder.path.exists()