Adding ACME Support (#178)
This commit is contained in:
parent
f846d78778
commit
5021e8ba91
|
@ -58,8 +58,8 @@ class InstanceManager(object):
|
||||||
results.append(cls())
|
results.append(cls())
|
||||||
else:
|
else:
|
||||||
results.append(cls)
|
results.append(cls)
|
||||||
except Exception:
|
except Exception as e:
|
||||||
current_app.logger.exception('Unable to import %s', cls_path)
|
current_app.logger.exception('Unable to import %s. Reason: %s', cls_path, e)
|
||||||
continue
|
continue
|
||||||
self.cache = results
|
self.cache = results
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
try:
|
||||||
|
VERSION = __import__('pkg_resources') \
|
||||||
|
.get_distribution(__name__).version
|
||||||
|
except Exception as e:
|
||||||
|
VERSION = 'unknown'
|
|
@ -0,0 +1,210 @@
|
||||||
|
"""
|
||||||
|
.. module: lemur.plugins.lemur_acme.acme
|
||||||
|
:platform: Unix
|
||||||
|
:synopsis: This module is responsible for communicating with a ACME CA.
|
||||||
|
:copyright: (c) 2015 by Netflix Inc., see AUTHORS for more
|
||||||
|
:license: Apache, see LICENSE for more details.
|
||||||
|
|
||||||
|
Snippets from https://raw.githubusercontent.com/alex/letsencrypt-aws/master/letsencrypt-aws.py
|
||||||
|
|
||||||
|
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
|
||||||
|
.. moduleauthor:: Mikhail Khodorovskiy <mikhail.khodorovskiy@jivesoftware.com>
|
||||||
|
"""
|
||||||
|
from flask import current_app
|
||||||
|
|
||||||
|
from acme.client import Client
|
||||||
|
from acme import jose
|
||||||
|
from acme import messages
|
||||||
|
|
||||||
|
from cryptography.hazmat.backends import default_backend
|
||||||
|
from cryptography.hazmat.primitives import serialization
|
||||||
|
from cryptography.hazmat.primitives.asymmetric import rsa
|
||||||
|
|
||||||
|
import OpenSSL.crypto
|
||||||
|
|
||||||
|
from lemur.plugins.bases import IssuerPlugin
|
||||||
|
from lemur.plugins import lemur_acme as acme
|
||||||
|
|
||||||
|
from .route53 import delete_txt_record, create_txt_record, wait_for_change
|
||||||
|
|
||||||
|
|
||||||
|
def find_dns_challenge(authz):
|
||||||
|
for combo in authz.body.resolved_combinations:
|
||||||
|
if (
|
||||||
|
len(combo) == 1 and
|
||||||
|
isinstance(combo[0].chall, acme.challenges.DNS01)
|
||||||
|
):
|
||||||
|
yield combo[0]
|
||||||
|
|
||||||
|
|
||||||
|
class AuthorizationRecord(object):
|
||||||
|
def __init__(self, host, authz, dns_challenge, change_id):
|
||||||
|
self.host = host
|
||||||
|
self.authz = authz
|
||||||
|
self.dns_challenge = dns_challenge
|
||||||
|
self.change_id = change_id
|
||||||
|
|
||||||
|
|
||||||
|
def start_dns_challenge(acme_client, host):
|
||||||
|
authz = acme_client.request_domain_challenges(
|
||||||
|
host, acme_client.directory.new_authz
|
||||||
|
)
|
||||||
|
|
||||||
|
[dns_challenge] = find_dns_challenge(authz)
|
||||||
|
|
||||||
|
change_id = create_txt_record(
|
||||||
|
dns_challenge.validation_domain_name(host),
|
||||||
|
dns_challenge.validation(acme_client.key),
|
||||||
|
|
||||||
|
)
|
||||||
|
return AuthorizationRecord(
|
||||||
|
host,
|
||||||
|
authz,
|
||||||
|
dns_challenge,
|
||||||
|
change_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def complete_dns_challenge(acme_client, authz_record):
|
||||||
|
wait_for_change(authz_record.change_id)
|
||||||
|
|
||||||
|
response = authz_record.dns_challenge.response(acme_client.key)
|
||||||
|
|
||||||
|
verified = response.simple_verify(
|
||||||
|
authz_record.dns_challenge.chall,
|
||||||
|
authz_record.host,
|
||||||
|
acme_client.key.public_key()
|
||||||
|
)
|
||||||
|
if not verified:
|
||||||
|
raise ValueError("Failed verification")
|
||||||
|
|
||||||
|
acme_client.answer_challenge(authz_record.dns_challenge, response)
|
||||||
|
|
||||||
|
|
||||||
|
def request_certificate(acme_client, authorizations, csr):
|
||||||
|
cert_response, _ = acme_client.poll_and_request_issuance(
|
||||||
|
jose.util.ComparableX509(
|
||||||
|
OpenSSL.crypto.load_certificate_request(
|
||||||
|
OpenSSL.crypto.FILETYPE_ASN1,
|
||||||
|
csr.public_bytes(serialization.Encoding.DER),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
authzrs=[authz_record.authz for authz_record in authorizations],
|
||||||
|
)
|
||||||
|
pem_certificate = OpenSSL.crypto.dump_certificate(
|
||||||
|
OpenSSL.crypto.FILETYPE_PEM, cert_response.body
|
||||||
|
)
|
||||||
|
pem_certificate_chain = "\n".join(
|
||||||
|
OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, cert)
|
||||||
|
for cert in acme_client.fetch_chain(cert_response)
|
||||||
|
)
|
||||||
|
return pem_certificate, pem_certificate_chain
|
||||||
|
|
||||||
|
|
||||||
|
def generate_rsa_private_key():
|
||||||
|
return rsa.generate_private_key(
|
||||||
|
public_exponent=65537, key_size=2048, backend=default_backend()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def setup_acme_client():
|
||||||
|
key = current_app.config.get('ACME_PRIVATE_KEY').strip()
|
||||||
|
acme_email = current_app.config.get('ACME_EMAIL')
|
||||||
|
acme_tel = current_app.config.get('ACME_TEL')
|
||||||
|
acme_directory_url = current_app.config('ACME_DIRECTORY_URL'),
|
||||||
|
contact = ('mailto:{}'.format(acme_email), 'tel:{}'.format(acme_tel))
|
||||||
|
|
||||||
|
key = serialization.load_pem_private_key(
|
||||||
|
key, password=None, backend=default_backend()
|
||||||
|
)
|
||||||
|
return acme_client_for_private_key(acme_directory_url, key)
|
||||||
|
|
||||||
|
|
||||||
|
def acme_client_for_private_key(acme_directory_url, private_key):
|
||||||
|
return Client(
|
||||||
|
acme_directory_url, key=acme.jose.JWKRSA(key=private_key)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def register(email):
|
||||||
|
private_key = generate_rsa_private_key()
|
||||||
|
acme_client = acme_client_for_private_key(current_app.config('ACME_DIRECTORY_URL'), private_key)
|
||||||
|
|
||||||
|
registration = acme_client.register(
|
||||||
|
messages.NewRegistration.from_data(email=email)
|
||||||
|
)
|
||||||
|
acme_client.agree_to_tos(registration)
|
||||||
|
return private_key
|
||||||
|
|
||||||
|
|
||||||
|
def get_domains(options):
|
||||||
|
"""
|
||||||
|
Fetches all domains currently requested
|
||||||
|
:param options:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
domains = [options['common_name']]
|
||||||
|
for name in options['extensions']['sub_alt_name']['names']:
|
||||||
|
domains.append(name)
|
||||||
|
return domains
|
||||||
|
|
||||||
|
|
||||||
|
def get_authorizations(acme_client, domains):
|
||||||
|
authorizations = []
|
||||||
|
try:
|
||||||
|
for domain in domains:
|
||||||
|
authz_record = start_dns_challenge(acme_client, domain)
|
||||||
|
authorizations.append(authz_record)
|
||||||
|
|
||||||
|
for authz_record in authorizations:
|
||||||
|
complete_dns_challenge(acme_client, authz_record)
|
||||||
|
finally:
|
||||||
|
for authz_record in authorizations:
|
||||||
|
dns_challenge = authz_record.dns_challenge
|
||||||
|
delete_txt_record(
|
||||||
|
authz_record.change_id,
|
||||||
|
dns_challenge.validation_domain_name(authz_record.host),
|
||||||
|
dns_challenge.validation(acme_client.key),
|
||||||
|
)
|
||||||
|
|
||||||
|
return authorizations
|
||||||
|
|
||||||
|
|
||||||
|
class ACMEIssuerPlugin(IssuerPlugin):
|
||||||
|
title = 'Acme'
|
||||||
|
slug = 'acme-issuer'
|
||||||
|
description = 'Enables the creation of certificates via ACME CAs (including Let\'s Encrypt)'
|
||||||
|
version = acme.VERSION
|
||||||
|
|
||||||
|
author = 'Kevin Glisson'
|
||||||
|
author_url = 'https://github.com/netflix/lemur.git'
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(ACMEIssuerPlugin, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
def create_certificate(self, csr, issuer_options):
|
||||||
|
"""
|
||||||
|
Creates a ACME certificate.
|
||||||
|
|
||||||
|
:param csr:
|
||||||
|
:param issuer_options:
|
||||||
|
:return: :raise Exception:
|
||||||
|
"""
|
||||||
|
current_app.logger.debug("Requesting a new acme certificate: {0}".format(issuer_options))
|
||||||
|
acme_client = setup_acme_client()
|
||||||
|
domains = get_domains(issuer_options)
|
||||||
|
authorizations = get_authorizations(acme_client, domains)
|
||||||
|
pem_certificate, pem_certificate_chain = request_certificate(acme_client, authorizations, csr)
|
||||||
|
return pem_certificate, pem_certificate_chain
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def create_authority(options):
|
||||||
|
"""
|
||||||
|
Creates an authority, this authority is then used by Lemur to allow a user
|
||||||
|
to specify which Certificate Authority they want to sign their certificate.
|
||||||
|
|
||||||
|
:param options:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
role = {'username': '', 'password': '', 'name': 'acme'}
|
||||||
|
return current_app.config.get('ACME_ROOT'), "", [role]
|
|
@ -0,0 +1,86 @@
|
||||||
|
import time
|
||||||
|
from lemur.plugins.lemur_aws.sts import sts_client
|
||||||
|
|
||||||
|
|
||||||
|
@sts_client('route53')
|
||||||
|
def wait_for_r53_change(change_id, client=None):
|
||||||
|
_, change_id = change_id
|
||||||
|
|
||||||
|
while True:
|
||||||
|
response = client.get_change(Id=change_id)
|
||||||
|
if response["ChangeInfo"]["Status"] == "INSYNC":
|
||||||
|
return
|
||||||
|
time.sleep(5)
|
||||||
|
|
||||||
|
|
||||||
|
@sts_client('route53')
|
||||||
|
def find_zone_id(domain, client=None):
|
||||||
|
paginator = client.get_paginator("list_hosted_zones")
|
||||||
|
zones = []
|
||||||
|
for page in paginator.paginate():
|
||||||
|
for zone in page["HostedZones"]:
|
||||||
|
if domain.endswith(zone["Name"]) or (domain + ".").endswith(zone["Name"]):
|
||||||
|
if not zone["Config"]["PrivateZone"]:
|
||||||
|
zones.append((zone["Name"], zone["Id"]))
|
||||||
|
|
||||||
|
if not zones:
|
||||||
|
raise ValueError(
|
||||||
|
"Unable to find a Route53 hosted zone for {}".format(domain)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@sts_client('route53')
|
||||||
|
def change_txt_record(action, zone_id, domain, value, client=None):
|
||||||
|
response = client.change_resource_record_sets(
|
||||||
|
HostedZoneId=zone_id,
|
||||||
|
ChangeBatch={
|
||||||
|
"Changes": [
|
||||||
|
{
|
||||||
|
"Action": action,
|
||||||
|
"ResourceRecordSet": {
|
||||||
|
"Name": domain,
|
||||||
|
"Type": "TXT",
|
||||||
|
"TTL": 300,
|
||||||
|
"ResourceRecords": [
|
||||||
|
# For some reason TXT records need to be
|
||||||
|
# manually quoted.
|
||||||
|
{"Value": '"{}"'.format(value)}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return response["ChangeInfo"]["Id"]
|
||||||
|
|
||||||
|
|
||||||
|
def create_txt_record(host, value):
|
||||||
|
zone_id = find_zone_id(host)
|
||||||
|
change_id = change_txt_record(
|
||||||
|
"CREATE",
|
||||||
|
zone_id,
|
||||||
|
host,
|
||||||
|
value,
|
||||||
|
)
|
||||||
|
return zone_id, change_id
|
||||||
|
|
||||||
|
|
||||||
|
def delete_txt_record(change_id, host, value):
|
||||||
|
zone_id, _ = change_id
|
||||||
|
change_txt_record(
|
||||||
|
"DELETE",
|
||||||
|
zone_id,
|
||||||
|
host,
|
||||||
|
value
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@sts_client('route53')
|
||||||
|
def wait_for_change(change_id, client=None):
|
||||||
|
_, change_id = change_id
|
||||||
|
|
||||||
|
while True:
|
||||||
|
response = client.get_change(Id=change_id)
|
||||||
|
if response["ChangeInfo"]["Status"] == "INSYNC":
|
||||||
|
return
|
||||||
|
time.sleep(5)
|
|
@ -0,0 +1 @@
|
||||||
|
from lemur.tests.conftest import * # noqa
|
|
@ -0,0 +1,5 @@
|
||||||
|
|
||||||
|
def test_get_certificates(app):
|
||||||
|
from lemur.plugins.base import plugins
|
||||||
|
p = plugins.get('acme-issuer')
|
||||||
|
p.create_certificate('', {})
|
|
@ -4,11 +4,11 @@
|
||||||
:copyright: (c) 2015 by Netflix Inc., see AUTHORS for more
|
:copyright: (c) 2015 by Netflix Inc., see AUTHORS for more
|
||||||
:license: Apache, see LICENSE for more details.
|
:license: Apache, see LICENSE for more details.
|
||||||
|
|
||||||
.. moduleauthor:: Mikhail Khodorovskiy <mikhail.khodorovskiy@jivesoftware.com>
|
|
||||||
|
|
||||||
The plugin inserts certificates and the private key as Kubernetes secret that
|
The plugin inserts certificates and the private key as Kubernetes secret that
|
||||||
can later be used to secure service endpoints running in Kubernetes pods
|
can later be used to secure service endpoints running in Kubernetes pods
|
||||||
|
|
||||||
|
.. moduleauthor:: Mikhail Khodorovskiy <mikhail.khodorovskiy@jivesoftware.com>
|
||||||
"""
|
"""
|
||||||
from cryptography import x509
|
from cryptography import x509
|
||||||
from cryptography.hazmat.backends import default_backend
|
from cryptography.hazmat.backends import default_backend
|
||||||
|
|
|
@ -72,3 +72,61 @@ VERISIGN_PEM_PATH = '~/'
|
||||||
VERISIGN_FIRST_NAME = 'Jim'
|
VERISIGN_FIRST_NAME = 'Jim'
|
||||||
VERISIGN_LAST_NAME = 'Bob'
|
VERISIGN_LAST_NAME = 'Bob'
|
||||||
VERSIGN_EMAIL = 'jim@example.com'
|
VERSIGN_EMAIL = 'jim@example.com'
|
||||||
|
|
||||||
|
|
||||||
|
ACME_PRIVATE_KEY = '''
|
||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
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
|
||||||
|
-----END RSA PRIVATE KEY-----
|
||||||
|
'''
|
||||||
|
ACME_URL = 'https://acme-v01.api.letsencrypt.org'
|
||||||
|
ACME_EMAIL = 'jim@example.com'
|
||||||
|
ACME_TEL = '4088675309'
|
||||||
|
|
1
setup.py
1
setup.py
|
@ -168,6 +168,7 @@ setup(
|
||||||
],
|
],
|
||||||
'lemur.plugins': [
|
'lemur.plugins': [
|
||||||
'verisign_issuer = lemur.plugins.lemur_verisign.plugin:VerisignIssuerPlugin',
|
'verisign_issuer = lemur.plugins.lemur_verisign.plugin:VerisignIssuerPlugin',
|
||||||
|
'acme_issuer = lemur.plugins.lemur_acme.plugin:ACMEIssuerPlugin',
|
||||||
'aws_destination = lemur.plugins.lemur_aws.plugin:AWSDestinationPlugin',
|
'aws_destination = lemur.plugins.lemur_aws.plugin:AWSDestinationPlugin',
|
||||||
'aws_source = lemur.plugins.lemur_aws.plugin:AWSSourcePlugin',
|
'aws_source = lemur.plugins.lemur_aws.plugin:AWSSourcePlugin',
|
||||||
'email_notification = lemur.plugins.lemur_email.plugin:EmailNotificationPlugin',
|
'email_notification = lemur.plugins.lemur_email.plugin:EmailNotificationPlugin',
|
||||||
|
|
Loading…
Reference in New Issue