def handler(event: typing.Any, context: typing.Any) -> typing.Mapping[str, typing.Any]: client = boto3.client("s3") storage = S3Storage(bucket=S3Storage.Bucket(os.environ["BUCKET"], client)) authenticators = [HTTP01Authenticator(storage=storage)] params: typing.Any = { "acme_account_email": os.environ["ACME_ACCOUNT_EMAIL"], "acme_directory_url": os.environ["ACME_DIRECTORY_URL"], "storage": storage, } if event["action"] == "renew": certificates = [ certificate for certificate, _ in find_certificates_to_renew(storage) ] failure = [] for certificate in certificates: try: renew(certificate=certificate, authenticators=authenticators, **params) except Exception as exc: logger.error(str(exc)) failure.append(certificate.name) if len(failure) == len(certificates): raise RuntimeError(f"All renew operations failed: {failure}") elif event["action"] == "issue": issue(domains=[event["domain"]], authenticators=authenticators, **params) elif event["action"] == "revoke": cert = storage.get_certificate(name=event["domain"]) assert cert revoke(certificate=cert, **params) return {"statusCode": 200}
def test_san_mixed(get_dns_txt_records, acme_directory_url, minio_bucket, pebble, full_infra, r53): storage = S3Storage(bucket=minio_bucket) domains = ["*.example.com", "fake.com", "www.fake.com", "my.com"] dns_auth = Route53Authenticator(r53, { "example.com": "ZONEID2", "www.fake.com": "ZONEID2" }) http_auth = HTTP01Authenticator(storage=storage) issue( domains=domains, storage=storage, acme_directory_url=acme_directory_url, acme_account_email="*****@*****.**", authenticators=[dns_auth, http_auth], ) pem_data = storage.get_certificate(domains=domains).fullchain assert pem_data cert = x509.load_pem_x509_certificate(pem_data, default_backend()) assert cert.subject.rfc4514_string() == f"CN={domains[0]}" sans = cert.extensions.get_extension_for_class( x509.extensions.SubjectAlternativeName).value assert [x.value for x in sans] == domains valid_from = cert.not_valid_before assert datetime.datetime.now() > valid_from
def test_s3_find_expired(bucket, acm, moto_certs): key_pem, fullchain_pem = moto_certs storage = S3Storage(bucket=bucket) storage.subscribe(ACMStorageObserver(acm=acm)) certificate = Certificate(["*.example.com"], private_key=key_pem) certificate.set_fullchain(fullchain_pem) storage.save_certificate(certificate) now = datetime.datetime.utcnow().replace(tzinfo=tzutc()) assert not list(find_certificates_to_renew(storage)) with time_machine.travel(now + datetime.timedelta(days=59)): assert not list(find_certificates_to_renew(storage)) with time_machine.travel(now - datetime.timedelta(days=10)): assert not list(find_certificates_to_renew(storage)) with time_machine.travel(now + datetime.timedelta(days=61)): certs = list(find_certificates_to_renew(storage)) assert len(certs) == 1 assert certs[0][0].name == "*.example.com" with time_machine.travel(now + datetime.timedelta(days=356)): certs = list(find_certificates_to_renew(storage)) assert len(certs) == 1 assert certs[0][0].name == "*.example.com" with time_machine.travel(now + datetime.timedelta(days=90)): certificate = Certificate(["new.example.com"], private_key=key_pem) certificate.set_fullchain(fullchain_pem) storage.save_certificate(certificate) certs = list(find_certificates_to_renew(storage)) assert len(certs) == 1 with time_machine.travel(now + datetime.timedelta(days=180)): certs = list(find_certificates_to_renew(storage)) assert len(certs) == 2
def test_dns01(get_dns_txt_records, acme_directory_url, minio_bucket, pebble, disable_ssl, r53): storage = S3Storage(bucket=minio_bucket) domain_name = "*.example.com" auth = Route53Authenticator( r53, { "my.example.com": "ZONEID1", "example.com": "ZONEID2", "my.example.org": "ZONEID0", }, ) issue( domains=[domain_name], storage=storage, acme_directory_url=acme_directory_url, acme_account_email="*****@*****.**", authenticators=[auth], ) pem_data = storage.get_certificate(domains=[domain_name]).fullchain assert pem_data cert = x509.load_pem_x509_certificate(pem_data, default_backend()) assert cert.subject.rfc4514_string() == f"CN={domain_name}" valid_from = cert.not_valid_before assert datetime.datetime.now() > valid_from
def test_load_balancer_redirect(minio_bucket, load_balancer, acm): storage = S3Storage(bucket=minio_bucket) storage.subscribe(ACMStorageObserver(acm=acm)) storage.set_validation("/.well-known/acme-challenge/randomkey", b"secretstring") r = urllib.request.urlopen(load_balancer["url"] + "/.well-known/acme-challenge/randomkey") assert r.status == 200 assert r.read() == b"secretstring"
def test_acm_issue_renew_revoke(minio_bucket, full_infra, acm, acme_directory_url): storage = S3Storage(bucket=minio_bucket) observer = ACMStorageObserver(acm=acm) storage.subscribe(observer) domain_name = "my3.example.com" auth = HTTP01Authenticator(storage=storage) issue( domains=[domain_name], storage=storage, acme_directory_url=acme_directory_url, acme_account_email="*****@*****.**", authenticators=[auth], ) certificate = storage.get_certificate(domains=[domain_name]) pem_data = certificate.fullchain assert pem_data cert = x509.load_pem_x509_certificate(pem_data, default_backend()) assert cert.subject.rfc4514_string() == f"CN={domain_name}" valid_from = cert.not_valid_before assert datetime.datetime.now() > valid_from acm_arn = observer._acm_arn_resolver.get(domain_name) assert acm_arn renew( certificate=certificate, storage=storage, acme_directory_url=acme_directory_url, acme_account_email="*****@*****.**", authenticators=[auth], ) pem_data = storage.get_certificate(domains=[domain_name]).fullchain assert pem_data cert = x509.load_pem_x509_certificate(pem_data, default_backend()) assert cert.subject.rfc4514_string() == f"CN={domain_name}" assert cert.not_valid_before > valid_from assert datetime.datetime.now() > cert.not_valid_before new_acm_arn = observer._acm_arn_resolver.get(domain_name) assert acm_arn == new_acm_arn revoke( certificate=certificate, storage=storage, acme_directory_url=acme_directory_url, acme_account_email="*****@*****.**", ) assert storage.get_certificate(domains=[domain_name]) is None
def _minio_bucket(minio, minio_boto3_settings, minio_settings): client = boto3.client("s3", **minio_boto3_settings) name = minio_settings["BUCKET"] client.create_bucket(Bucket=name) policy = { "Version": "2012-10-17", "Statement": [{ "Sid": "AddPerm", "Effect": "Allow", "Principal": "*", "Action": ["s3:GetObject"], "Resource": [f"arn:aws:s3:::{name}/.well-known/acme-challenge/*"], }], } client.put_bucket_policy(Bucket=name, Policy=json.dumps(policy)) return S3Storage.Bucket(name, client)
def bucket(s3, minio_settings): name = minio_settings["BUCKET"] s3.create_bucket( Bucket=name, CreateBucketConfiguration={"LocationConstraint": "ap-southeast-2"}) policy = { "Version": "2012-10-17", "Statement": [{ "Sid": "AddPerm", "Effect": "Allow", "Principal": "*", "Action": ["s3:GetObject"], "Resource": [f"arn:aws:s3:::{name}/.well-known/acme-challenge/*"], }], } s3.put_bucket_policy(Bucket=name, Policy=json.dumps(policy)) return S3Storage.Bucket(name, s3)
def test_s3_mixin_ops(bucket): storage = S3Storage(bucket=bucket) key = "keys/example.com" assert storage._get(key) is None storage._set(key, b"mybytes") assert storage._get(key) == b"mybytes"