From 44e3b33aaa9a58ece57fbb605c943fc43066cda6 Mon Sep 17 00:00:00 2001 From: Curtis Castrapel Date: Fri, 20 Apr 2018 14:49:54 -0700 Subject: [PATCH] More stuff. Will prioritize this more next week --- lemur/certificates/schemas.py | 4 +- lemur/defaults/schemas.py | 2 - lemur/defaults/views.py | 3 - lemur/dns_providers/models.py | 3 +- lemur/dns_providers/schemas.py | 6 +- lemur/dns_providers/service.py | 37 +++++-- lemur/dns_providers/views.py | 23 ++++- lemur/plugins/lemur_acme/plugin.py | 60 ++++++------ .../lemur_digicert/tests/test_digicert.py | 12 +-- lemur/schemas.py | 10 ++ lemur/static/app/angular/app.js | 10 +- .../certificates/certificate/certificate.js | 5 + .../certificates/certificate/options.tpl.html | 14 +-- .../certificate/tracking.tpl.html | 11 +++ .../app/angular/certificates/services.js | 7 +- .../dns_provider/dns_provider.js | 98 +++++++++++++++++++ .../dns_provider/dns_provider.tpl.html | 93 ++++++++++++++++++ .../app/angular/dns_providers/services.js | 38 +++++++ .../app/angular/dns_providers/view/view.js | 84 ++++++++++++++++ .../angular/dns_providers/view/view.tpl.html | 54 ++++++++++ .../angular/pending_certificates/services.js | 1 - lemur/static/app/index.html | 1 + 22 files changed, 496 insertions(+), 80 deletions(-) create mode 100644 lemur/static/app/angular/dns_providers/dns_provider/dns_provider.js create mode 100644 lemur/static/app/angular/dns_providers/dns_provider/dns_provider.tpl.html create mode 100644 lemur/static/app/angular/dns_providers/services.js create mode 100644 lemur/static/app/angular/dns_providers/view/view.js create mode 100644 lemur/static/app/angular/dns_providers/view/view.tpl.html diff --git a/lemur/certificates/schemas.py b/lemur/certificates/schemas.py index 651aa647..f98f6a67 100644 --- a/lemur/certificates/schemas.py +++ b/lemur/certificates/schemas.py @@ -18,7 +18,8 @@ from lemur.schemas import ( ExtensionSchema, AssociatedRoleSchema, EndpointNestedOutputSchema, - AssociatedRotationPolicySchema + AssociatedRotationPolicySchema, + DnsProviderSchema ) from lemur.authorities.schemas import AuthorityNestedOutputSchema @@ -70,6 +71,7 @@ class CertificateInputSchema(CertificateCreationSchema): replaces = fields.Nested(AssociatedCertificateSchema, missing=[], many=True) replacements = fields.Nested(AssociatedCertificateSchema, missing=[], many=True) # deprecated roles = fields.Nested(AssociatedRoleSchema, missing=[], many=True) + dns_provider = fields.Nested(DnsProviderSchema, missing={}, required=False) csr = fields.String(validate=validators.csr) key_type = fields.String(validate=validate.OneOf(['RSA2048', 'RSA4096']), missing='RSA2048') diff --git a/lemur/defaults/schemas.py b/lemur/defaults/schemas.py index 58ba31af..c03d6d85 100644 --- a/lemur/defaults/schemas.py +++ b/lemur/defaults/schemas.py @@ -8,7 +8,6 @@ from marshmallow import fields from lemur.common.schema import LemurOutputSchema from lemur.authorities.schemas import AuthorityNestedOutputSchema -from lemur.dns_providers.schemas import DnsProvidersNestedOutputSchema class DefaultOutputSchema(LemurOutputSchema): @@ -19,7 +18,6 @@ class DefaultOutputSchema(LemurOutputSchema): organization = fields.String() organizational_unit = fields.String() issuer_plugin = fields.String() - dns_providers = fields.List(fields.Nested(DnsProvidersNestedOutputSchema)) default_output_schema = DefaultOutputSchema() diff --git a/lemur/defaults/views.py b/lemur/defaults/views.py index 7ba38fa0..5fe3cf41 100644 --- a/lemur/defaults/views.py +++ b/lemur/defaults/views.py @@ -9,7 +9,6 @@ from flask_restful import Api from lemur.common.schema import validate_schema from lemur.authorities.service import get_by_name from lemur.auth.service import AuthenticatedResource -from lemur.dns_providers.service import get_all_dns_providers from lemur.defaults.schemas import default_output_schema @@ -61,7 +60,6 @@ class LemurDefaults(AuthenticatedResource): """ default_authority = get_by_name(current_app.config.get('LEMUR_DEFAULT_AUTHORITY')) - dns_providers = get_all_dns_providers() return dict( country=current_app.config.get('LEMUR_DEFAULT_COUNTRY'), @@ -71,7 +69,6 @@ class LemurDefaults(AuthenticatedResource): organizational_unit=current_app.config.get('LEMUR_DEFAULT_ORGANIZATIONAL_UNIT'), issuer_plugin=current_app.config.get('LEMUR_DEFAULT_ISSUER_PLUGIN'), authority=default_authority, - dns_providers=dns_providers, ) diff --git a/lemur/dns_providers/models.py b/lemur/dns_providers/models.py index f43aac0b..e0b50134 100644 --- a/lemur/dns_providers/models.py +++ b/lemur/dns_providers/models.py @@ -15,4 +15,5 @@ class DnsProviders(db.Model): api_endpoint = Column(String(length=256), nullable=True) date_created = Column(ArrowType(), server_default=text('now()'), nullable=False) status = Column(String(length=128), nullable=True) - options = Column(JSON) + options = Column(JSON, nullable=True) + domains = Column(JSON, nullable=True) diff --git a/lemur/dns_providers/schemas.py b/lemur/dns_providers/schemas.py index bae9570c..df2042c6 100644 --- a/lemur/dns_providers/schemas.py +++ b/lemur/dns_providers/schemas.py @@ -8,13 +8,11 @@ class DnsProvidersNestedOutputSchema(LemurOutputSchema): __envelope__ = False id = fields.Integer() name = fields.String() - description = fields.String() provider_type = fields.String() + description = fields.String() credentials = fields.String() api_endpoint = fields.String() date_created = ArrowDateTime() - status = fields.String() - options = fields.String() -default_output_schema = DnsProvidersNestedOutputSchema() +dns_provider_schema = DnsProvidersNestedOutputSchema() diff --git a/lemur/dns_providers/service.py b/lemur/dns_providers/service.py index fa752d09..03314513 100644 --- a/lemur/dns_providers/service.py +++ b/lemur/dns_providers/service.py @@ -1,16 +1,33 @@ +from lemur import database from lemur.dns_providers.models import DnsProviders -def get_all_dns_providers(status="active"): +def render(args): """ - Retrieves all certificates within Lemur. - + Helper that helps us render the REST Api responses. + :param args: :return: """ - all_dns_providers = DnsProviders.query.all() - dns_provider_result = [] - for provider in all_dns_providers: - print(provider) - if provider.status == status: - dns_provider_result.append(provider.__dict__) - return dns_provider_result + query = database.session_query(DnsProviders) + + return database.sort_and_page(query, DnsProviders, args) + + +def get(dns_provider_id): + """ + Retrieves a dns provider by its lemur assigned ID. + + :param dns_provider_id: Lemur assigned ID + :rtype : DnsProvider + :return: + """ + return database.get(DnsProviders, dns_provider_id) + + +def delete(dns_provider_id): + """ + Deletes a DNS provider. + + :param dns_provider_id: Lemur assigned ID + """ + database.delete(get(dns_provider_id)) \ No newline at end of file diff --git a/lemur/dns_providers/views.py b/lemur/dns_providers/views.py index faa76aac..0f324bdf 100644 --- a/lemur/dns_providers/views.py +++ b/lemur/dns_providers/views.py @@ -5,13 +5,15 @@ :license: Apache, see LICENSE for more details. .. moduleauthor:: Curtis Castrapel """ -from flask import Blueprint +from flask import Blueprint, g from flask_restful import reqparse, Api - +from lemur.auth.permissions import admin_permission from lemur.auth.service import AuthenticatedResource - +from lemur.common.schema import validate_schema +from lemur.common.utils import paginated_parser from lemur.dns_providers import service +from lemur.dns_providers.schemas import dns_provider_schema mod = Blueprint('dns_providers', __name__) api = Api(mod) @@ -23,6 +25,7 @@ class DnsProvidersList(AuthenticatedResource): self.reqparse = reqparse.RequestParser() super(DnsProvidersList, self).__init__() + @validate_schema(None, dns_provider_schema) def get(self): """ .. http:get:: /dns_providers @@ -66,7 +69,19 @@ class DnsProvidersList(AuthenticatedResource): :statuscode 403: unauthenticated """ - return service.get_all_dns_providers() + parser = paginated_parser.copy() + parser.add_argument('id', type=int, location='args') + parser.add_argument('name', type=str, location='args') + parser.add_argument('type', type=str, location='args') + + args = parser.parse_args() + args['user'] = g.user + return service.render(args) + + @admin_permission.require(http_exception=403) + def delete(self, dns_provider_id): + service.delete(dns_provider_id) + return {'result': True} api.add_resource(DnsProvidersList, '/dns_providers', endpoint='dns_providers') diff --git a/lemur/plugins/lemur_acme/plugin.py b/lemur/plugins/lemur_acme/plugin.py index e5fd1730..4f64d521 100644 --- a/lemur/plugins/lemur_acme/plugin.py +++ b/lemur/plugins/lemur_acme/plugin.py @@ -9,6 +9,7 @@ .. moduleauthor:: Kevin Glisson .. moduleauthor:: Mikhail Khodorovskiy +.. moduleauthor:: Curtis Castrapel """ import josepy as jose import json @@ -23,7 +24,6 @@ from lemur.common.utils import generate_private_key import OpenSSL.crypto -from lemur.common.utils import validate_conf from lemur.plugins.bases import IssuerPlugin from lemur.plugins import lemur_acme as acme @@ -97,20 +97,26 @@ def request_certificate(acme_client, authorizations, csr): OpenSSL.crypto.FILETYPE_PEM, cert_response.body ).decode('utf-8') - pem_certificate_chain = "\n".join( - OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, cert.decode("utf-8")) - for cert in acme_client.fetch_chain(cert_response) - ).decode('utf-8') + full_chain = [] + for cert in acme_client.fetch_chain(cert_response): + chain = OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, cert) + full_chain.append(chain.decode("utf-8")) + pem_certificate_chain = "\n".join(full_chain) - current_app.logger.debug("{0} {1}".format(type(pem_certificate). type(pem_certificate_chain))) + current_app.logger.debug("{0} {1}".format(type(pem_certificate), type(pem_certificate_chain))) return pem_certificate, pem_certificate_chain def setup_acme_client(authority): - options = json.loads(authority.get('options', '[]')) - email = options.getcurrent_app.config.get('ACME_EMAIL') - tel = current_app.config.get('ACME_TEL') - directory_url = current_app.config.get('ACME_DIRECTORY_URL') + if not authority.options: + raise Exception("Invalid authority. Options not set") + options = {} + for o in json.loads(authority.options): + print(o) + options[o.get("name")] = o.get("value") + email = options.get('email', current_app.config.get('ACME_EMAIL')) + tel = options.get('telephone', current_app.config.get('ACME_TEL')) + directory_url = options.get('acme_url', current_app.config.get('ACME_DIRECTORY_URL')) contact = ('mailto:{}'.format(email), 'tel:{}'.format(tel)) key = jose.JWKRSA(key=generate_private_key('RSA2048')) @@ -173,7 +179,7 @@ class ACMEIssuerPlugin(IssuerPlugin): description = 'Enables the creation of certificates via ACME CAs (including Let\'s Encrypt)' version = acme.VERSION - author = 'Kevin Glisson' + author = 'Netflix' author_url = 'https://github.com/netflix/lemur.git' options = [ @@ -207,18 +213,6 @@ class ACMEIssuerPlugin(IssuerPlugin): ] def __init__(self, *args, **kwargs): - required_vars = [ - 'ACME_DIRECTORY_URL', - 'ACME_TEL', - 'ACME_EMAIL', - 'ACME_AWS_ACCOUNT_NUMBER', - 'ACME_ROOT' - ] - - validate_conf(current_app, required_vars) - self.dns_provider_name = current_app.config.get('ACME_DNS_PROVIDER', 'route53') - current_app.logger.debug("Using DNS provider: {0}".format(self.dns_provider_name)) - self.dns_provider = __import__(self.dns_provider_name, globals(), locals(), [], 1) super(ACMEIssuerPlugin, self).__init__(*args, **kwargs) def create_certificate(self, csr, issuer_options): @@ -229,12 +223,22 @@ class ACMEIssuerPlugin(IssuerPlugin): :param issuer_options: :return: :raise Exception: """ - current_app.logger.debug("Requesting a new acme certificate: {0}".format(issuer_options)) - acme_client, registration = setup_acme_client(issuer_options.get(issuer_options.get('authority'))) - # Deal with account number per certificate - account_number = current_app.config.get('ACME_AWS_ACCOUNT_NUMBER') + authority = issuer_options.get('authority') + acme_client, registration = setup_acme_client(authority) + dns_provider = issuer_options.get('dns_provider') + if not dns_provider: + raise Exception("DNS Provider setting is required for ACME certificates.") + credentials = json.loads(dns_provider.credentials) + + current_app.logger.debug("Using DNS provider: {0}".format(dns_provider.provider_type)) + dns_provider_type = __import__(dns_provider.provider_type, globals(), locals(), [], 1) + account_number = credentials.get("account_number") + if dns_provider.provider_type == 'route53' and not account_number: + error = "DNS Provider {} does not have an account number configured.".format(dns_provider.name) + current_app.logger.error(error) + raise Exception(error) domains = get_domains(issuer_options) - authorizations = get_authorizations(acme_client, account_number, domains, self.dns_provider) + authorizations = get_authorizations(acme_client, account_number, domains, dns_provider_type) pem_certificate, pem_certificate_chain = request_certificate(acme_client, authorizations, csr) # TODO add external ID (if possible) return pem_certificate, pem_certificate_chain, None diff --git a/lemur/plugins/lemur_digicert/tests/test_digicert.py b/lemur/plugins/lemur_digicert/tests/test_digicert.py index 91f27ad4..53536fce 100644 --- a/lemur/plugins/lemur_digicert/tests/test_digicert.py +++ b/lemur/plugins/lemur_digicert/tests/test_digicert.py @@ -150,11 +150,7 @@ def test_signature_hash(app): signature_hash('sdfdsf') -def test_issuer_plugin_create_certificate(): - import requests_mock - from lemur.plugins.lemur_digicert.plugin import DigiCertIssuerPlugin - - pem_fixture = """\ +def test_issuer_plugin_create_certificate(certificate_="""\ -----BEGIN CERTIFICATE----- abc -----END CERTIFICATE----- @@ -164,7 +160,11 @@ def -----BEGIN CERTIFICATE----- ghi -----END CERTIFICATE----- -""" +"""): + import requests_mock + from lemur.plugins.lemur_digicert.plugin import DigiCertIssuerPlugin + + pem_fixture = certificate_ subject = DigiCertIssuerPlugin() adapter = requests_mock.Adapter() diff --git a/lemur/schemas.py b/lemur/schemas.py index 9d1836cd..03bffc11 100644 --- a/lemur/schemas.py +++ b/lemur/schemas.py @@ -21,6 +21,7 @@ from lemur.plugins.utils import get_plugin_option from lemur.roles.models import Role from lemur.users.models import User from lemur.authorities.models import Authority +from lemur.dns_providers.models import DnsProviders from lemur.policies.models import RotationPolicy from lemur.certificates.models import Certificate from lemur.destinations.models import Destination @@ -159,6 +160,15 @@ class AssociatedRotationPolicySchema(LemurInputSchema): return fetch_objects(RotationPolicy, data, many=many) +class DnsProviderSchema(LemurInputSchema): + id = fields.Integer() + name = fields.String() + + @post_load + def get_object(self, data, many=False): + return fetch_objects(DnsProviders, data, many=many) + + class PluginInputSchema(LemurInputSchema): plugin_options = fields.List(fields.Dict(), validate=validate_options) slug = fields.String(required=True) diff --git a/lemur/static/app/angular/app.js b/lemur/static/app/angular/app.js index 68509fa5..b71518b4 100644 --- a/lemur/static/app/angular/app.js +++ b/lemur/static/app/angular/app.js @@ -109,11 +109,11 @@ }; }); - lemur.service('DnsService', function (LemurRestangular) { - var DnsService = this; - DnsService.get = function () { - return LemurRestangular.all('dns_service').customGET().then(function (dns_service) { - return dns_service; + lemur.service('DnsProviders', function (LemurRestangular) { + var DnsProviders = this; + DnsProviders.get = function () { + return LemurRestangular.all('dns_providers').customGET().then(function (dnsProviders) { + return dnsProviders; }); }; }); diff --git a/lemur/static/app/angular/certificates/certificate/certificate.js b/lemur/static/app/angular/certificates/certificate/certificate.js index fdd773fd..1294dd32 100644 --- a/lemur/static/app/angular/certificates/certificate/certificate.js +++ b/lemur/static/app/angular/certificates/certificate/certificate.js @@ -134,6 +134,11 @@ angular.module('lemur') $scope.certificate.validityYears = null; }; + CertificateService.getDnsProviders().then(function (providers) { + $scope.dnsProviders = providers; + } + ); + $scope.create = function (certificate) { WizardHandler.wizard().context.loading = true; CertificateService.create(certificate).then( diff --git a/lemur/static/app/angular/certificates/certificate/options.tpl.html b/lemur/static/app/angular/certificates/certificate/options.tpl.html index a2e440fd..7e47cf18 100644 --- a/lemur/static/app/angular/certificates/certificate/options.tpl.html +++ b/lemur/static/app/angular/certificates/certificate/options.tpl.html @@ -235,19 +235,7 @@
- -
- -
- -
-
- -
+ diff --git a/lemur/static/app/angular/certificates/certificate/tracking.tpl.html b/lemur/static/app/angular/certificates/certificate/tracking.tpl.html index 21277106..fb74d208 100644 --- a/lemur/static/app/angular/certificates/certificate/tracking.tpl.html +++ b/lemur/static/app/angular/certificates/certificate/tracking.tpl.html @@ -107,6 +107,17 @@ +
+ + +
+ +
+