Merge branch 'castrapel-hackday'

This commit is contained in:
Curtis Castrapel 2018-04-24 09:41:26 -07:00
commit 38c33395c8
38 changed files with 902 additions and 58 deletions

View File

@ -123,5 +123,4 @@ endif
@echo "--> Done installing new dependencies" @echo "--> Done installing new dependencies"
@echo "" @echo ""
.PHONY: develop dev-postgres dev-docs setup-git build clean update-submodules test testloop test-cli test-js test-python lint lint-python lint-js coverage publish release .PHONY: develop dev-postgres dev-docs setup-git build clean update-submodules test testloop test-cli test-js test-python lint lint-python lint-js coverage publish release

View File

@ -29,6 +29,7 @@ from lemur.endpoints.views import mod as endpoints_bp
from lemur.logs.views import mod as logs_bp from lemur.logs.views import mod as logs_bp
from lemur.api_keys.views import mod as api_key_bp from lemur.api_keys.views import mod as api_key_bp
from lemur.pending_certificates.views import mod as pending_certificates_bp from lemur.pending_certificates.views import mod as pending_certificates_bp
from lemur.dns_providers.views import mod as dns_providers_bp
from lemur.__about__ import ( from lemur.__about__ import (
__author__, __copyright__, __email__, __license__, __summary__, __title__, __author__, __copyright__, __email__, __license__, __summary__, __title__,
@ -57,6 +58,7 @@ LEMUR_BLUEPRINTS = (
logs_bp, logs_bp,
api_key_bp, api_key_bp,
pending_certificates_bp, pending_certificates_bp,
dns_providers_bp,
) )

View File

@ -42,6 +42,7 @@ class Authority(db.Model):
self.description = kwargs.get('description') self.description = kwargs.get('description')
self.authority_certificate = kwargs['authority_certificate'] self.authority_certificate = kwargs['authority_certificate']
self.plugin_name = kwargs['plugin']['slug'] self.plugin_name = kwargs['plugin']['slug']
self.options = kwargs.get('options')
@property @property
def plugin(self): def plugin(self):

View File

@ -8,6 +8,9 @@
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com> .. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
""" """
import json
from lemur import database from lemur import database
from lemur.common.utils import truthiness from lemur.common.utils import truthiness
from lemur.extensions import metrics from lemur.extensions import metrics
@ -107,6 +110,8 @@ def create(**kwargs):
cert = upload(**kwargs) cert = upload(**kwargs)
kwargs['authority_certificate'] = cert kwargs['authority_certificate'] = cert
if kwargs.get('plugin', {}).get('plugin_options', []):
kwargs['options'] = json.dumps(kwargs.get('plugin', {}).get('plugin_options', []))
authority = Authority(**kwargs) authority = Authority(**kwargs)
authority = database.create(authority) authority = database.create(authority)

View File

View File

@ -0,0 +1,34 @@
"""
.. module: lemur.authorizations.models
:platform: unix
:copyright: (c) 2015 by Netflix Inc., see AUTHORS for more
:license: Apache, see LICENSE for more details.
.. moduleauthor:: Netflix Secops <secops@netflix.com>
"""
from sqlalchemy import Column, Integer, String
from sqlalchemy_utils import JSONType
from lemur.database import db
from lemur.plugins.base import plugins
class Authorizations(db.Model):
__tablename__ = 'pending_dns_authorizations'
id = Column(Integer, primary_key=True, autoincrement=True)
account_number = Column(String(128))
domains = Column(JSONType)
dns_provider_type = Column(String(128))
options = Column(JSONType)
@property
def plugin(self):
return plugins.get(self.plugin_name)
def __repr__(self):
return "Authorizations(id={id})".format(label=self.id)
def __init__(self, account_number, domains, dns_provider_type, options=None):
self.account_number = account_number
self.domains = domains
self.dns_provider_type = dns_provider_type
self.options = options

View File

@ -0,0 +1,24 @@
"""
.. module: lemur.pending_certificates.service
Copyright (c) 2017 and onwards Instart Logic, Inc. All rights reserved.
.. moduleauthor:: Secops <secops@netflix.com>
"""
from lemur import database
from lemur.authorizations.models import Authorizations
def get(authorization_id):
"""
Retrieve dns authorization by ID
"""
return database.get(Authorizations, authorization_id)
def create(account_number, domains, dns_provider_type, options=None):
"""
Creates a new dns authorization.
"""
authorization = Authorizations(account_number, domains, dns_provider_type, options)
return database.create(authorization)

View File

@ -102,6 +102,7 @@ class Certificate(db.Model):
serial = Column(String(128)) serial = Column(String(128))
cn = Column(String(128)) cn = Column(String(128))
deleted = Column(Boolean, index=True) deleted = Column(Boolean, index=True)
dns_provider_id = Column(Integer(), nullable=True)
not_before = Column(ArrowType) not_before = Column(ArrowType)
not_after = Column(ArrowType) not_after = Column(ArrowType)

View File

@ -18,7 +18,8 @@ from lemur.schemas import (
ExtensionSchema, ExtensionSchema,
AssociatedRoleSchema, AssociatedRoleSchema,
EndpointNestedOutputSchema, EndpointNestedOutputSchema,
AssociatedRotationPolicySchema AssociatedRotationPolicySchema,
DnsProviderSchema
) )
from lemur.authorities.schemas import AuthorityNestedOutputSchema from lemur.authorities.schemas import AuthorityNestedOutputSchema
@ -70,6 +71,7 @@ class CertificateInputSchema(CertificateCreationSchema):
replaces = fields.Nested(AssociatedCertificateSchema, missing=[], many=True) replaces = fields.Nested(AssociatedCertificateSchema, missing=[], many=True)
replacements = fields.Nested(AssociatedCertificateSchema, missing=[], many=True) # deprecated replacements = fields.Nested(AssociatedCertificateSchema, missing=[], many=True) # deprecated
roles = fields.Nested(AssociatedRoleSchema, missing=[], many=True) roles = fields.Nested(AssociatedRoleSchema, missing=[], many=True)
dns_provider = fields.Nested(DnsProviderSchema, missing={}, required=False, allow_none=True)
csr = fields.String(validate=validators.csr) csr = fields.String(validate=validators.csr)
key_type = fields.String(validate=validate.OneOf(['RSA2048', 'RSA4096']), missing='RSA2048') key_type = fields.String(validate=validate.OneOf(['RSA2048', 'RSA4096']), missing='RSA2048')

View File

@ -50,7 +50,8 @@ class LemurDefaults(AuthenticatedResource):
"state": "CA", "state": "CA",
"location": "Los Gatos", "location": "Los Gatos",
"organization": "Netflix", "organization": "Netflix",
"organizationalUnit": "Operations" "organizationalUnit": "Operations",
"dnsProviders": [{"name": "test", ...}, {...}],
} }
:reqheader Authorization: OAuth token to authenticate :reqheader Authorization: OAuth token to authenticate
@ -67,7 +68,7 @@ class LemurDefaults(AuthenticatedResource):
organization=current_app.config.get('LEMUR_DEFAULT_ORGANIZATION'), organization=current_app.config.get('LEMUR_DEFAULT_ORGANIZATION'),
organizational_unit=current_app.config.get('LEMUR_DEFAULT_ORGANIZATIONAL_UNIT'), organizational_unit=current_app.config.get('LEMUR_DEFAULT_ORGANIZATIONAL_UNIT'),
issuer_plugin=current_app.config.get('LEMUR_DEFAULT_ISSUER_PLUGIN'), issuer_plugin=current_app.config.get('LEMUR_DEFAULT_ISSUER_PLUGIN'),
authority=default_authority authority=default_authority,
) )

View File

@ -0,0 +1,19 @@
from sqlalchemy import Column, Integer, String, text
from sqlalchemy.dialects.postgresql import JSON
from sqlalchemy_utils import ArrowType
from lemur.database import db
class DnsProviders(db.Model):
__tablename__ = 'dns_providers'
id = Column(Integer(), primary_key=True)
name = Column(String(length=256), unique=True, nullable=True)
description = Column(String(length=1024), nullable=True)
provider_type = Column(String(length=256), nullable=True)
credentials = Column(String(length=256), nullable=True)
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, nullable=True)
domains = Column(JSON, nullable=True)

View File

@ -0,0 +1,18 @@
from lemur.common.fields import ArrowDateTime
from lemur.common.schema import LemurOutputSchema
from marshmallow import fields
class DnsProvidersNestedOutputSchema(LemurOutputSchema):
__envelope__ = False
id = fields.Integer()
name = fields.String()
provider_type = fields.String()
description = fields.String()
credentials = fields.String()
api_endpoint = fields.String()
date_created = ArrowDateTime()
dns_provider_schema = DnsProvidersNestedOutputSchema()

View File

@ -0,0 +1,33 @@
from lemur import database
from lemur.dns_providers.models import DnsProviders
def render(args):
"""
Helper that helps us render the REST Api responses.
:param args:
:return:
"""
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))

View File

@ -0,0 +1,87 @@
"""
.. module: lemur.dns)providers.views
:platform: Unix
:copyright: (c) 2015 by Netflix Inc., see AUTHORS for more
:license: Apache, see LICENSE for more details.
.. moduleauthor:: Curtis Castrapel <ccastrapel@netflix.com>
"""
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)
class DnsProvidersList(AuthenticatedResource):
""" Defines the 'dns_providers' endpoint """
def __init__(self):
self.reqparse = reqparse.RequestParser()
super(DnsProvidersList, self).__init__()
@validate_schema(None, dns_provider_schema)
def get(self):
"""
.. http:get:: /dns_providers
The current list of DNS Providers
**Example request**:
.. sourcecode:: http
GET /dns_providers HTTP/1.1
Host: example.com
Accept: application/json, text/javascript
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: text/javascript
{
"items": [{
"id": 1,
"name": "test",
"description": "test",
"provider_type": "dyn",
"status": "active",
}],
"total": 1
}
:query sortBy: field to sort on
:query sortDir: asc or desc
:query page: int. default is 1
:query filter: key value pair format is k;v
:query count: count number. default is 10
:reqheader Authorization: OAuth token to authenticate
:statuscode 200: no error
:statuscode 403: unauthenticated
"""
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')

View File

@ -0,0 +1,39 @@
"""Create dns_providers table
Revision ID: 3adfdd6598df
Revises: 556ceb3e3c3e
Create Date: 2018-04-10 13:25:47.007556
"""
# revision identifiers, used by Alembic.
revision = '3adfdd6598df'
down_revision = '556ceb3e3c3e'
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects.postgresql import JSON
from sqlalchemy_utils import ArrowType
def upgrade():
# create provider table
op.create_table(
'dns_providers',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sa.String(length=256), nullable=True),
sa.Column('description', sa.String(length=1024), nullable=True),
sa.Column('provider_type', sa.String(length=256), nullable=True),
sa.Column('credentials', sa.String(length=256), nullable=True),
sa.Column('api_endpoint', sa.String(length=256), nullable=True),
sa.Column('date_created', ArrowType(), server_default=sa.text('now()'), nullable=False),
sa.Column('status', sa.String(length=128), nullable=True),
sa.Column('options', JSON),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('name')
)
def downgrade():
op.drop_table('dns_providers')

View File

@ -0,0 +1,22 @@
"""Add dns_provider id column to certificates table
Revision ID: 4e78b9e4e1dd
Revises: 3adfdd6598df
Create Date: 2018-04-10 14:00:30.701669
"""
# revision identifiers, used by Alembic.
revision = '4e78b9e4e1dd'
down_revision = '3adfdd6598df'
from alembic import op
import sqlalchemy as sa
def upgrade():
op.add_column('certificates', sa.Column('dns_provider_id', sa.Integer(), nullable=True))
def downgrade():
op.drop_column('certificates', 'dns_provider_id')

View File

@ -29,7 +29,7 @@ def fetch(ids):
for cert in pending_certs: for cert in pending_certs:
authority = plugins.get(cert.authority.plugin_name) authority = plugins.get(cert.authority.plugin_name)
real_cert = authority.get_ordered_certificate(cert.external_id) real_cert = authority.get_ordered_certificate(cert)
if real_cert: if real_cert:
# If a real certificate was returned from issuer, then create it in Lemur and delete # If a real certificate was returned from issuer, then create it in Lemur and delete
# the pending certificate # the pending certificate

View File

@ -8,6 +8,7 @@ from datetime import datetime as dt
from sqlalchemy.orm import relationship from sqlalchemy.orm import relationship
from sqlalchemy import Integer, ForeignKey, String, PassiveDefault, func, Column, Text, Boolean from sqlalchemy import Integer, ForeignKey, String, PassiveDefault, func, Column, Text, Boolean
from sqlalchemy_utils.types.arrow import ArrowType from sqlalchemy_utils.types.arrow import ArrowType
from sqlalchemy_utils import JSONType
import lemur.common.utils import lemur.common.utils
from lemur.certificates.models import get_or_increase_name from lemur.certificates.models import get_or_increase_name
@ -37,6 +38,7 @@ class PendingCertificate(db.Model):
private_key = Column(Vault, nullable=True) private_key = Column(Vault, nullable=True)
date_created = Column(ArrowType, PassiveDefault(func.now()), nullable=False) date_created = Column(ArrowType, PassiveDefault(func.now()), nullable=False)
dns_provider_id = Column(Integer(), nullable=True)
status = Column(String(128)) status = Column(String(128))
@ -54,6 +56,7 @@ class PendingCertificate(db.Model):
secondary=pending_cert_replacement_associations, secondary=pending_cert_replacement_associations,
backref='pending_cert', backref='pending_cert',
passive_deletes=True) passive_deletes=True)
options = Column(JSONType)
rotation_policy = relationship("RotationPolicy") rotation_policy = relationship("RotationPolicy")
@ -93,3 +96,7 @@ class PendingCertificate(db.Model):
self.replaces = kwargs.get('replaces', []) self.replaces = kwargs.get('replaces', [])
self.rotation = kwargs.get('rotation') self.rotation = kwargs.get('rotation')
self.rotation_policy = kwargs.get('rotation_policy') self.rotation_policy = kwargs.get('rotation_policy')
try:
self.dns_provider_id = kwargs.get('dns_provider', {}).get("id")
except AttributeError:
self.dns_provider_id = None

View File

@ -25,7 +25,7 @@ class IssuerPlugin(Plugin):
def revoke_certificate(self, certificate, comments): def revoke_certificate(self, certificate, comments):
raise NotImplementedError raise NotImplementedError
def get_ordered_certificate(self, order_id): def get_ordered_certificate(self, certificate):
raise NotImplementedError raise NotImplementedError
def cancel_ordered_certificate(self, pending_cert, **kwargs): def cancel_ordered_certificate(self, pending_cert, **kwargs):

View File

@ -9,8 +9,10 @@
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com> .. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
.. moduleauthor:: Mikhail Khodorovskiy <mikhail.khodorovskiy@jivesoftware.com> .. moduleauthor:: Mikhail Khodorovskiy <mikhail.khodorovskiy@jivesoftware.com>
.. moduleauthor:: Curtis Castrapel <ccastrapel@netflix.com>
""" """
import josepy as jose import josepy as jose
import json
from flask import current_app from flask import current_app
@ -22,7 +24,8 @@ from lemur.common.utils import generate_private_key
import OpenSSL.crypto import OpenSSL.crypto
from lemur.common.utils import validate_conf from lemur.authorizations import service as authorization_service
from lemur.dns_providers import service as dns_provider_service
from lemur.plugins.bases import IssuerPlugin from lemur.plugins.bases import IssuerPlugin
from lemur.plugins import lemur_acme as acme from lemur.plugins import lemur_acme as acme
@ -96,19 +99,26 @@ def request_certificate(acme_client, authorizations, csr):
OpenSSL.crypto.FILETYPE_PEM, cert_response.body OpenSSL.crypto.FILETYPE_PEM, cert_response.body
).decode('utf-8') ).decode('utf-8')
pem_certificate_chain = "\n".join( full_chain = []
OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, cert.decode("utf-8")) for cert in acme_client.fetch_chain(cert_response):
for cert in acme_client.fetch_chain(cert_response) chain = OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, cert)
).decode('utf-8') 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 return pem_certificate, pem_certificate_chain
def setup_acme_client(): def setup_acme_client(authority):
email = current_app.config.get('ACME_EMAIL') if not authority.options:
tel = current_app.config.get('ACME_TEL') raise Exception("Invalid authority. Options not set")
directory_url = current_app.config.get('ACME_DIRECTORY_URL') 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)) contact = ('mailto:{}'.format(email), 'tel:{}'.format(tel))
key = jose.JWKRSA(key=generate_private_key('RSA2048')) key = jose.JWKRSA(key=generate_private_key('RSA2048'))
@ -145,11 +155,14 @@ def get_domains(options):
def get_authorizations(acme_client, account_number, domains, dns_provider): def get_authorizations(acme_client, account_number, domains, dns_provider):
authorizations = [] authorizations = []
try: for domain in domains:
for domain in domains: authz_record = start_dns_challenge(acme_client, account_number, domain, dns_provider)
authz_record = start_dns_challenge(acme_client, account_number, domain, dns_provider) authorizations.append(authz_record)
authorizations.append(authz_record) return authorizations
def finalize_authorizations(acme_client, account_number, dns_provider, authorizations):
try:
for authz_record in authorizations: for authz_record in authorizations:
complete_dns_challenge(acme_client, account_number, authz_record, dns_provider) complete_dns_challenge(acme_client, account_number, authz_record, dns_provider)
finally: finally:
@ -171,24 +184,59 @@ class ACMEIssuerPlugin(IssuerPlugin):
description = 'Enables the creation of certificates via ACME CAs (including Let\'s Encrypt)' description = 'Enables the creation of certificates via ACME CAs (including Let\'s Encrypt)'
version = acme.VERSION version = acme.VERSION
author = 'Kevin Glisson' author = 'Netflix'
author_url = 'https://github.com/netflix/lemur.git' author_url = 'https://github.com/netflix/lemur.git'
def __init__(self, *args, **kwargs): options = [
required_vars = [ {
'ACME_DIRECTORY_URL', 'name': 'acme_url',
'ACME_TEL', 'type': 'str',
'ACME_EMAIL', 'required': True,
'ACME_AWS_ACCOUNT_NUMBER', 'validation': '/^http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+$/',
'ACME_ROOT' 'helpMessage': 'Must be a valid web url starting with http[s]://',
] },
{
'name': 'telephone',
'type': 'str',
'default': '',
'helpMessage': 'Telephone to use'
},
{
'name': 'email',
'type': 'str',
'default': '',
'validation': '/^?([-a-zA-Z0-9.`?{}]+@\w+\.\w+)$/',
'helpMessage': 'Email to use'
},
{
'name': 'certificate',
'type': 'textarea',
'default': '',
'validation': '/^-----BEGIN CERTIFICATE-----/',
'helpMessage': 'Certificate to use'
},
]
validate_conf(current_app, required_vars) def __init__(self, *args, **kwargs):
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) super(ACMEIssuerPlugin, self).__init__(*args, **kwargs)
def get_ordered_certificate(self, pending_cert):
acme_client, registration = setup_acme_client(pending_cert.authority)
order_info = authorization_service.get(pending_cert.external_id)
dns_provider = dns_provider_service.get(pending_cert.dns_provider_id)
dns_provider_type = __import__(dns_provider.provider_type, globals(), locals(), [], 1)
authorizations = get_authorizations(
acme_client, order_info.account_number, order_info.domains, dns_provider_type)
finalize_authorizations(acme_client, order_info.account_number, dns_provider_type, authorizations)
pem_certificate, pem_certificate_chain = request_certificate(acme_client, authorizations, pending_cert.csr)
cert = {
'body': "\n".join(str(pem_certificate).splitlines()),
'chain': "\n".join(str(pem_certificate_chain).splitlines()),
'external_id': str(pending_cert.external_id)
}
return cert
def create_certificate(self, csr, issuer_options): def create_certificate(self, csr, issuer_options):
""" """
Creates an ACME certificate. Creates an ACME certificate.
@ -197,11 +245,38 @@ class ACMEIssuerPlugin(IssuerPlugin):
:param issuer_options: :param issuer_options:
:return: :raise Exception: :return: :raise Exception:
""" """
current_app.logger.debug("Requesting a new acme certificate: {0}".format(issuer_options)) authority = issuer_options.get('authority')
acme_client, registration = setup_acme_client() create_immediately = issuer_options.get('create_immediately', False)
account_number = current_app.config.get('ACME_AWS_ACCOUNT_NUMBER') acme_client, registration = setup_acme_client(authority)
dns_provider_d = issuer_options.get('dns_provider')
if not dns_provider_d:
raise Exception("DNS Provider setting is required for ACME certificates.")
dns_provider = dns_provider_service.get(dns_provider_d.get("id"))
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) domains = get_domains(issuer_options)
authorizations = get_authorizations(acme_client, account_number, domains, self.dns_provider) if not create_immediately:
# Create pending authorizations that we'll need to do the creation
authz_domains = []
for d in domains:
if type(d) == str:
authz_domains.append(d)
else:
authz_domains.append(d.value)
dns_authorization = authorization_service.create(account_number, authz_domains, dns_provider.provider_type)
# Return id of the DNS Authorization
return None, None, dns_authorization.id
authorizations = get_authorizations(acme_client, account_number, domains, dns_provider_type)
finalize_authorizations(acme_client, account_number, dns_provider_type, authorizations)
pem_certificate, pem_certificate_chain = request_certificate(acme_client, authorizations, csr) pem_certificate, pem_certificate_chain = request_certificate(acme_client, authorizations, csr)
# TODO add external ID (if possible) # TODO add external ID (if possible)
return pem_certificate, pem_certificate_chain, None return pem_certificate, pem_certificate_chain, None
@ -216,4 +291,11 @@ class ACMEIssuerPlugin(IssuerPlugin):
:return: :return:
""" """
role = {'username': '', 'password': '', 'name': 'acme'} role = {'username': '', 'password': '', 'name': 'acme'}
return current_app.config.get('ACME_ROOT'), "", [role] plugin_options = options.get('plugin').get('plugin_options')
# Define static acme_root based off configuration variable by default. However, if user has passed a
# certificate, use this certificate as the root.
acme_root = current_app.config.get('ACME_ROOT')
for option in plugin_options:
if option.get('name') == 'certificate':
acme_root = option.get('value')
return acme_root, "", [role]

View File

@ -58,7 +58,7 @@ def change_txt_record(action, zone_id, domain, value, client=None):
def create_txt_record(host, value, account_number): def create_txt_record(host, value, account_number):
zone_id = find_zone_id(host, account_number=account_number) zone_id = find_zone_id(host, account_number=account_number)
change_id = change_txt_record( change_id = change_txt_record(
"CREATE", "UPSERT",
zone_id, zone_id,
host, host,
value, value,

View File

@ -325,8 +325,9 @@ class DigiCertIssuerPlugin(IssuerPlugin):
response = self.session.put(create_url, data=json.dumps({'comments': comments})) response = self.session.put(create_url, data=json.dumps({'comments': comments}))
return handle_response(response) return handle_response(response)
def get_ordered_certificate(self, order_id): def get_ordered_certificate(self, pending_cert):
""" Retrieve a certificate via order id """ """ Retrieve a certificate via order id """
order_id = pending_cert.external_id
base_url = current_app.config.get('DIGICERT_URL') base_url = current_app.config.get('DIGICERT_URL')
try: try:
certificate_id = get_certificate_id(self.session, base_url, order_id) certificate_id = get_certificate_id(self.session, base_url, order_id)

View File

@ -150,11 +150,7 @@ def test_signature_hash(app):
signature_hash('sdfdsf') signature_hash('sdfdsf')
def test_issuer_plugin_create_certificate(): def test_issuer_plugin_create_certificate(certificate_="""\
import requests_mock
from lemur.plugins.lemur_digicert.plugin import DigiCertIssuerPlugin
pem_fixture = """\
-----BEGIN CERTIFICATE----- -----BEGIN CERTIFICATE-----
abc abc
-----END CERTIFICATE----- -----END CERTIFICATE-----
@ -164,7 +160,11 @@ def
-----BEGIN CERTIFICATE----- -----BEGIN CERTIFICATE-----
ghi ghi
-----END CERTIFICATE----- -----END CERTIFICATE-----
""" """):
import requests_mock
from lemur.plugins.lemur_digicert.plugin import DigiCertIssuerPlugin
pem_fixture = certificate_
subject = DigiCertIssuerPlugin() subject = DigiCertIssuerPlugin()
adapter = requests_mock.Adapter() adapter = requests_mock.Adapter()

View File

@ -21,6 +21,7 @@ from lemur.plugins.utils import get_plugin_option
from lemur.roles.models import Role from lemur.roles.models import Role
from lemur.users.models import User from lemur.users.models import User
from lemur.authorities.models import Authority from lemur.authorities.models import Authority
from lemur.dns_providers.models import DnsProviders
from lemur.policies.models import RotationPolicy from lemur.policies.models import RotationPolicy
from lemur.certificates.models import Certificate from lemur.certificates.models import Certificate
from lemur.destinations.models import Destination from lemur.destinations.models import Destination
@ -159,6 +160,11 @@ class AssociatedRotationPolicySchema(LemurInputSchema):
return fetch_objects(RotationPolicy, data, many=many) return fetch_objects(RotationPolicy, data, many=many)
class DnsProviderSchema(LemurInputSchema):
id = fields.Integer()
name = fields.String()
class PluginInputSchema(LemurInputSchema): class PluginInputSchema(LemurInputSchema):
plugin_options = fields.List(fields.Dict(), validate=validate_options) plugin_options = fields.List(fields.Dict(), validate=validate_options)
slug = fields.String(required=True) slug = fields.String(required=True)

View File

@ -109,6 +109,15 @@
}; };
}); });
lemur.service('DnsProviders', function (LemurRestangular) {
var DnsProviders = this;
DnsProviders.get = function () {
return LemurRestangular.all('dns_providers').customGET().then(function (dnsProviders) {
return dnsProviders;
});
};
});
lemur.directive('lemurBadRequest', [function () { lemur.directive('lemurBadRequest', [function () {
return { return {
template: '<h4>{{ directiveData.message }}</h4>' + template: '<h4>{{ directiveData.message }}</h4>' +

View File

@ -52,8 +52,58 @@
<label class="control-label col-sm-2"> <label class="control-label col-sm-2">
Plugin Plugin
</label> </label>
<div class="col-sm-10"> <div class="form-group col-sm-10">
<select class="form-control" ng-model="authority.plugin" ng-options="plugin as plugin.title for plugin in plugins" required></select> <select class="form-control" ng-model="authority.plugin" ng-options="plugin as plugin.title for plugin in plugins" required></select>
</div> </div>
<div class="form-group" ng-repeat="item in authority.plugin.pluginOptions">
<ng-form name="subForm" class="form-horizontal" role="form" novalidate>
<div ng-class="{'has-error': subForm.sub.$invalid, 'has-success': !subForm.sub.$invalid&&subForm.sub.$dirty}">
<label class="control-label col-sm-2">
{{ item.name | titleCase }}
</label>
<div class="col-sm-10">
<input name="sub" ng-if="item.type == 'int'" type="number" ng-pattern="item.validation?item.validation:'^[0-9]+$'"
class="form-control" ng-model="item.value"/>
<select name="sub" ng-if="item.type == 'select'" class="form-control" ng-options="i for i in item.available"
ng-model="item.value"></select>
<input name="sub" ng-if="item.type == 'bool'" class="form-control" type="checkbox" ng-model="item.value">
<input name="sub" ng-if="item.type == 'str'" type="text" class="form-control" ng-model="item.value"/>
<textarea name="sub" ng-if="item.type == 'textarea'" class="form-control" ng-model="item.value"></textarea>
<div ng-if="item.type == 'export-plugin'">
<form name="exportForm" class="form-horizontal" role="form" novalidate>
<select class="form-control" ng-model="item.value"
ng-options="plugin.title for plugin in exportPlugins" required></select>
<div class="col-sm-10">
<div class="form-group" ng-repeat="item in item.value.pluginOptions">
<ng-form name="subForm" class="form-horizontal" role="form" novalidate>
ng-class="{'has-error': subForm.sub.$invalid, 'has-success': !subForm.sub.$invalid&&subForm.sub.$dirty}">
<label class="control-label col-sm-2">
{{ item.name | titleCase }}
</label>
<div class="col-sm-10">
<input name="sub" ng-if="item.type == 'int'" type="number" ng-pattern="item.validation?item.validation:'^[0-9]+$'"
class="form-control" ng-model="item.value"/>
<select name="sub" ng-if="item.type == 'select'" class="form-control"
ng-options="i for i in item.available" ng-model="item.value"></select>
<input name="sub" ng-if="item.type == 'bool'" class="form-control" type="checkbox"
ng-model="item.value">
<input name="sub" ng-if="item.type == 'str'" type="text" class="form-control"
ng-model="item.value" ng-pattern="item.validation"/>
<textarea name="sub" ng-if="item.type == 'textarea'" class="form-control"
ng-model="item.value" ng-pattern="item.validation"></textarea>
<p ng-show="subForm.sub.$invalid && !subForm.sub.$pristine"
class="help-block">{{ item.helpMessage }}</p>
</div>
</div>
</ng-form>
</div>
</div>
</form>
</div>
<p ng-show="subForm.sub.$invalid && !subForm.sub.$pristine" class="help-block">{{ item.helpMessage }}</p>
</div>
</div>
</ng-form>
</div>
</div> </div>
</div> </div>

View File

@ -134,6 +134,11 @@ angular.module('lemur')
$scope.certificate.validityYears = null; $scope.certificate.validityYears = null;
}; };
CertificateService.getDnsProviders().then(function (providers) {
$scope.dnsProviders = providers;
}
);
$scope.create = function (certificate) { $scope.create = function (certificate) {
WizardHandler.wizard().context.loading = true; WizardHandler.wizard().context.loading = true;
CertificateService.create(certificate).then( CertificateService.create(certificate).then(

View File

@ -234,6 +234,9 @@
</label> </label>
</div> </div>
</div> </div>
<div class="col-sm-10">
</div></div>
</div>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">

View File

@ -107,6 +107,17 @@
</ui-select> </ui-select>
</div> </div>
</div> </div>
<div class="form-group" ng-show="certificate.authority.plugin.slug == 'acme-issuer'">
<label class="control-label col-sm-2">
DNS Provider:
</label>
<div class="col-sm-10">
<select class="form-control" ng-model="certificate.dnsProvider" ng-options="item as item.name for item in dnsProviders.items track by item.id">
<option value="">- choose an entry. Neded for ACME Providers -</option>
</select>
</div>
</div>
<div class="form-group"> <div class="form-group">
<label class="control-label col-sm-2" <label class="control-label col-sm-2"
uib-tooltip="If no date is selected Lemur attempts to issue a 2 year certificate"> uib-tooltip="If no date is selected Lemur attempts to issue a 2 year certificate">

View File

@ -149,7 +149,7 @@ angular.module('lemur')
}); });
return LemurRestangular.all('certificates'); return LemurRestangular.all('certificates');
}) })
.service('CertificateService', function ($location, CertificateApi, AuthorityService, AuthorityApi, LemurRestangular, DefaultService) { .service('CertificateService', function ($location, CertificateApi, AuthorityService, AuthorityApi, LemurRestangular, DefaultService, DnsProviders) {
var CertificateService = this; var CertificateService = this;
CertificateService.findCertificatesByName = function (filterValue) { CertificateService.findCertificatesByName = function (filterValue) {
return CertificateApi.getList({'filter[name]': filterValue}) return CertificateApi.getList({'filter[name]': filterValue})
@ -246,6 +246,10 @@ angular.module('lemur')
}); });
}; };
CertificateService.getDnsProviders = function () {
return DnsProviders.get();
};
CertificateService.loadPrivateKey = function (certificate) { CertificateService.loadPrivateKey = function (certificate) {
return certificate.customGET('key'); return certificate.customGET('key');
}; };

View File

@ -0,0 +1,98 @@
'use strict';
angular.module('lemur')
.controller('DnsProviderCreateController', function ($scope, $uibModalInstance, PluginService, DnsProviderService, LemurRestangular, toaster) {
$scope.dns_provider = LemurRestangular.restangularizeElement(null, {}, 'dns_providers');
PluginService.getByType('dns_provider').then(function (plugins) {
$scope.plugins = plugins;
});
PluginService.getByType('export').then(function (plugins) {
$scope.exportPlugins = plugins;
});
$scope.save = function (dns_provider) {
DnsProviderService.create(dns_provider.then(
function () {
toaster.pop({
type: 'success',
title: dns_provider.label,
body: 'Successfully Created!'
});
$uibModalInstance.close();
}, function (response) {
toaster.pop({
type: 'error',
title: dns_provider.label,
body: 'lemur-bad-request',
bodyOutputType: 'directive',
directiveData: response.data,
timeout: 100000
});
}));
};
$scope.cancel = function () {
$uibModalInstance.dismiss('cancel');
};
})
.controller('DnsProviderEditController', function ($scope, $uibModalInstance, DnsProviderService, DnsProviderApi, PluginService, toaster, editId) {
DnsProviderApi.get(editId).then(function (dns_provider) {
$scope.dns_provider = dns_provider;
PluginService.getByType('dns_provider').then(function (plugins) {
$scope.plugins = plugins;
_.each($scope.plugins, function (plugin) {
if (plugin.slug === $scope.dns_provider.plugin.slug) {
plugin.pluginOptions = $scope.dns_provider.plugin.pluginOptions;
$scope.dns_provider.plugin = plugin;
_.each($scope.dns_provider.plugin.pluginOptions, function (option) {
if (option.type === 'export-plugin') {
PluginService.getByType('export').then(function (plugins) {
$scope.exportPlugins = plugins;
_.each($scope.exportPlugins, function (plugin) {
if (plugin.slug === option.value.slug) {
plugin.pluginOptions = option.value.pluginOptions;
option.value = plugin;
}
});
});
}
});
}
});
});
});
$scope.save = function (dns_provider) {
DnsProviderService.update(dns_provider).then(
function () {
toaster.pop({
type: 'success',
title: dns_provider.label,
body: 'Successfully Updated!'
});
$uibModalInstance.close();
}, function (response) {
toaster.pop({
type: 'error',
title: dns_provider.label,
body: 'lemur-bad-request',
bodyOutputType: 'directive',
directiveData: response.data,
timeout: 100000
});
});
};
$scope.cancel = function () {
$uibModalInstance.dismiss('cancel');
};
});

View File

@ -0,0 +1,93 @@
<div class="modal-header">
<button type="button" class="close" ng-click="cancel()" aria-label="Close"><span aria-hidden="true">&times;</span>
</button>
<h3><span ng-show="!dns_provider.fromServer">Create</span><span ng-show="dns_provider.fromServer">Edit</span>
DnsProvider <span class="text-muted"><small>oh the places you will go!</small></span></h3>
</div>
<div class="modal-body">
<form name="createForm" class="form-horizontal" role="form" novalidate>
<div class="form-group"
ng-class="{'has-error': createForm.label.$invalid, 'has-success': !createForm.label.$invalid&&createForm.label.$dirty}">
<label class="control-label col-sm-2">
Label
</label>
<div class="col-sm-10">
<input name="label" ng-model="dns_provider.label" placeholder="Label" class="form-control" required/>
<p ng-show="createForm.label.$invalid && !createForm.label.$pristine" class="help-block">You must enter an
dns_provider label</p>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2">
Description
</label>
<div class="col-sm-10">
<textarea name="comments" ng-model="dns_provider.description" placeholder="Something elegant"
class="form-control"></textarea>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2">
Plugin
</label>
<div class="col-sm-10">
<select class="form-control" ng-model="dns_provider.plugin" ng-options="plugin.title for plugin in plugins"
required></select>
</div>
</div>
<div class="form-group" ng-repeat="item in dns_provider.plugin.pluginOptions">
<ng-form name="subForm" class="form-horizontal" role="form" novalidate>
<div ng-class="{'has-error': subForm.sub.$invalid, 'has-success': !subForm.sub.$invalid&&subForm.sub.$dirty}">
<label class="control-label col-sm-2">
{{ item.name | titleCase }}
</label>
<div class="col-sm-10">
<input name="sub" ng-if="item.type == 'int'" type="number" ng-pattern="item.validation?item.validation:'^[0-9]+$'"
class="form-control" ng-model="item.value"/>
<select name="sub" ng-if="item.type == 'select'" class="form-control" ng-options="i for i in item.available"
ng-model="item.value"></select>
<input name="sub" ng-if="item.type == 'bool'" class="form-control" type="checkbox" ng-model="item.value">
<input name="sub" ng-if="item.type == 'str'" type="text" class="form-control" ng-model="item.value"/>
<div ng-if="item.type == 'export-plugin'">
<form name="exportForm" class="form-horizontal" role="form" novalidate>
<select class="form-control" ng-model="item.value"
ng-options="plugin.title for plugin in exportPlugins" required></select>
<div class="col-sm-10">
<div class="form-group" ng-repeat="item in item.value.pluginOptions">
<ng-form name="subForm" class="form-horizontal" role="form" novalidate>
<div
ng-class="{'has-error': subForm.sub.$invalid, 'has-success': !subForm.sub.$invalid&&subForm.sub.$dirty}">
<label class="control-label col-sm-2">
{{ item.name | titleCase }}
</label>
<div class="col-sm-10">
<input name="sub" ng-if="item.type == 'int'" type="number" ng-pattern="item.validation?item.validation:'^[0-9]+$'"
class="form-control" ng-model="item.value"/>
<select name="sub" ng-if="item.type == 'select'" class="form-control"
ng-options="i for i in item.available" ng-model="item.value"></select>
<input name="sub" ng-if="item.type == 'bool'" class="form-control" type="checkbox"
ng-model="item.value">
<input name="sub" ng-if="item.type == 'str'" type="text" class="form-control"
ng-model="item.value" ng-pattern="item.validation"/>
<p ng-show="subForm.sub.$invalid && !subForm.sub.$pristine"
class="help-block">{{ item.helpMessage }}</p>
</div>
</div>
</ng-form>
</div>
</div>
</form>
</div>
<p ng-show="subForm.sub.$invalid && !subForm.sub.$pristine" class="help-block">{{ item.helpMessage }}</p>
</div>
</div>
</ng-form>
</div>
</form>
</div>
<div class="modal-footer">
<button ng-click="save(dns_provider)" type="submit" ng-disabled="createForm.$invalid" class="btn btn-primary">Save
</button>
<button ng-click="cancel()" class="btn btn-danger">Cancel</button>
</div>

View File

@ -0,0 +1,38 @@
'use strict';
angular.module('lemur')
.service('DnsProviderApi', function (LemurRestangular) {
return LemurRestangular.all('dns_providers');
})
.service('DnsProviderService', function ($location, DnsProviderApi, PluginService, DnsProviders) {
var DnsProviderService = this;
DnsProviderService.findDnsProvidersByName = function (filterValue) {
return DnsProviderApi.getList({'filter[label]': filterValue})
.then(function (dns_providers) {
return dns_providers;
});
};
DnsProviderService.getDnsProviders = function () {
return DnsProviders.get();
};
DnsProviderService.create = function (dns_provider) {
return DnsProviderApi.post(dns_provider);
};
DnsProviderService.get = function () {
return DnsProviderApi.get();
};
DnsProviderService.update = function (dns_provider) {
return dns_provider.put();
};
DnsProviderService.getPlugin = function (dns_provider) {
return PluginService.getByName(dns_provider.pluginName).then(function (plugin) {
dns_provider.plugin = plugin;
});
};
return DnsProviderService;
});

View File

@ -0,0 +1,84 @@
'use strict';
angular.module('lemur')
.config(function config($stateProvider) {
$stateProvider.state('dns_providers', {
url: '/dns_providers',
templateUrl: '/angular/dns_providers/view/view.tpl.html',
controller: 'DnsProvidersViewController'
});
})
.controller('DnsProvidersViewController', function ($scope, $uibModal, DnsProviderApi, DnsProviderService, ngTableParams, toaster) {
$scope.filter = {};
$scope.dnsProvidersTable = new ngTableParams({
page: 1, // show first page
count: 10, // count per page
sorting: {
id: 'desc' // initial sorting
},
filter: $scope.filter
}, {
total: 0, // length of data
getData: function ($defer, params) {
DnsProviderApi.getList(params.url()).then(
function (data) {
params.total(data.total);
$defer.resolve(data);
}
);
}
});
$scope.remove = function (dns_provider) {
dns_provider.remove().then(
function () {
$scope.dnsProvidersTable.reload();
},
function (response) {
toaster.pop({
type: 'error',
title: 'Opps',
body: 'I see what you did there: ' + response.data.message
});
}
);
};
$scope.edit = function (dns_providerId) {
var uibModalInstance = $uibModal.open({
animation: true,
templateUrl: '/angular/dns_providers/dns_provider/dns_provider.tpl.html',
controller: 'DnsProvidersEditController',
size: 'lg',
backdrop: 'static',
resolve: {
editId: function () {
return dns_providerId;
}
}
});
uibModalInstance.result.then(function () {
$scope.dnsProvidersTable.reload();
});
};
$scope.create = function () {
var uibModalInstance = $uibModal.open({
animation: true,
controller: 'DnsProvidersCreateController',
templateUrl: '/angular/dns_providers/dns_provider/dns_provider.tpl.html',
size: 'lg',
backdrop: 'static'
});
uibModalInstance.result.then(function () {
$scope.dnsProvidersTable.reload();
});
};
});

View File

@ -0,0 +1,54 @@
<div class="row">
<div class="col-md-12">
<h2 class="featurette-heading">DNS Providers
<span class="text-muted"><small>the root of all problems</small></span></h2>
<div class="panel panel-default">
<div class="panel-heading">
<div class="btn-group pull-right">
<button ng-click="create()" class="btn btn-primary">Create</button>
</div>
<div class="btn-group">
<button ng-model="showFilter" class="btn btn-default" uib-btn-checkbox
btn-checkbox-true="1"
btn-checkbox-false="0">Filter</button>
</div>
<div class="clearfix"></div>
</div>
<div class="table-responsive">
<table ng-table="dnsProvidersTable" class="table table-striped" show-filter="showFilter" template-pagination="angular/pager.html" >
<tbody>
<tr ng-repeat="dns_provider in $data track by $index">
<td data-title="'Name'" sortable="'name'" filter="{ 'name': 'text' }">
<ul class="list-unstyled">
<li>{{ dns_provider.name }}</li>
<li><span class="text-muted">{{ dns_provider.description }}</span></li>
</ul>
</td>
<td data-title="'Type'" sortable="'type'" filter="{ 'type': 'text' }">
<ul class="list-unstyled">
<li>{{ dns_provider.providerType }}</li>
</ul>
</td>
<td data-title="'Domains'" sortable="'domains'" filter="{ 'domains': 'text' }">
<ul class="list-unstyled">
<li>{{ dns_provider.domains }}</li>
<li><span class="text-muted">{{ dns_provider.description }}</span></li>
</ul>
</td>
<td data-title="''">
<div class="btn-group-vertical pull-right">
<button uib-tooltip="Edit DNS Provider" ng-click="edit(dns_provider.id)" class="btn btn-sm btn-info">
Edit
</button>
<button uib-tooltip="Delete DNS Provider" ng-click="remove(dns_provider)" type="button" class="btn btn-sm btn-danger pull-left">
Remove
</button>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>

View File

@ -65,6 +65,7 @@
<li><a ui-sref="domains">Domains</a></li> <li><a ui-sref="domains">Domains</a></li>
<li><a ui-sref="logs">Logs</a></li> <li><a ui-sref="logs">Logs</a></li>
<li><a ui-sref="keys">Api Keys</a></li> <li><a ui-sref="keys">Api Keys</a></li>
<li><a ui-sref="dns_providers">DNS Providers</a></li>
</ul> </ul>
</li> </li>
</ul> </ul>

View File

@ -37,7 +37,8 @@ class TestAsyncIssuerPlugin(IssuerPlugin):
def create_certificate(self, csr, issuer_options): def create_certificate(self, csr, issuer_options):
return "", "", 12345 return "", "", 12345
def get_ordered_certificate(self, order_id): def get_ordered_certificate(self, pending_cert):
order_id = pending_cert.external_id
return INTERNAL_VALID_LONG_STR, INTERNAL_VALID_SAN_STR, 54321 return INTERNAL_VALID_LONG_STR, INTERNAL_VALID_SAN_STR, 54321
@staticmethod @staticmethod

View File

@ -63,7 +63,7 @@ def test_get_certificate_primitives(certificate):
with freeze_time(datetime.date(year=2016, month=10, day=30)): with freeze_time(datetime.date(year=2016, month=10, day=30)):
primitives = get_certificate_primitives(certificate) primitives = get_certificate_primitives(certificate)
assert len(primitives) == 23 assert len(primitives) == 24
def test_certificate_edit_schema(session): def test_certificate_edit_schema(session):
@ -152,7 +152,8 @@ def test_certificate_input_schema(client, authority):
'authority': {'id': authority.id}, 'authority': {'id': authority.id},
'description': 'testtestest', 'description': 'testtestest',
'validityEnd': arrow.get(2016, 11, 9).isoformat(), 'validityEnd': arrow.get(2016, 11, 9).isoformat(),
'validityStart': arrow.get(2015, 11, 9).isoformat() 'validityStart': arrow.get(2015, 11, 9).isoformat(),
'dns_provider': None,
} }
data, errors = CertificateInputSchema().load(input_data) data, errors = CertificateInputSchema().load(input_data)
@ -165,7 +166,7 @@ def test_certificate_input_schema(client, authority):
assert data['country'] == 'US' assert data['country'] == 'US'
assert data['location'] == 'Los Gatos' assert data['location'] == 'Los Gatos'
assert len(data.keys()) == 18 assert len(data.keys()) == 19
def test_certificate_input_with_extensions(client, authority): def test_certificate_input_with_extensions(client, authority):
@ -192,7 +193,8 @@ def test_certificate_input_with_extensions(client, authority):
{'nameType': 'DNSName', 'value': 'test.example.com'} {'nameType': 'DNSName', 'value': 'test.example.com'}
] ]
} }
} },
'dns_provider': None,
} }
data, errors = CertificateInputSchema().load(input_data) data, errors = CertificateInputSchema().load(input_data)
@ -206,7 +208,8 @@ def test_certificate_out_of_range_date(client, authority):
'owner': 'jim@example.com', 'owner': 'jim@example.com',
'authority': {'id': authority.id}, 'authority': {'id': authority.id},
'description': 'testtestest', 'description': 'testtestest',
'validityYears': 100 'validityYears': 100,
'dns_provider': None,
} }
data, errors = CertificateInputSchema().load(input_data) data, errors = CertificateInputSchema().load(input_data)
@ -230,7 +233,8 @@ def test_certificate_valid_years(client, authority):
'owner': 'jim@example.com', 'owner': 'jim@example.com',
'authority': {'id': authority.id}, 'authority': {'id': authority.id},
'description': 'testtestest', 'description': 'testtestest',
'validityYears': 1 'validityYears': 1,
'dns_provider': None,
} }
data, errors = CertificateInputSchema().load(input_data) data, errors = CertificateInputSchema().load(input_data)
@ -245,7 +249,8 @@ def test_certificate_valid_dates(client, authority):
'authority': {'id': authority.id}, 'authority': {'id': authority.id},
'description': 'testtestest', 'description': 'testtestest',
'validityStart': '2020-01-01T00:00:00', 'validityStart': '2020-01-01T00:00:00',
'validityEnd': '2020-01-01T00:00:01' 'validityEnd': '2020-01-01T00:00:01',
'dns_provider': None,
} }
data, errors = CertificateInputSchema().load(input_data) data, errors = CertificateInputSchema().load(input_data)
@ -262,6 +267,7 @@ def test_certificate_cn_admin(client, authority, logged_in_admin):
'description': 'testtestest', 'description': 'testtestest',
'validityStart': '2020-01-01T00:00:00', 'validityStart': '2020-01-01T00:00:00',
'validityEnd': '2020-01-01T00:00:01', 'validityEnd': '2020-01-01T00:00:01',
'dns_provider': None,
} }
data, errors = CertificateInputSchema().load(input_data) data, errors = CertificateInputSchema().load(input_data)
@ -285,7 +291,8 @@ def test_certificate_allowed_names(client, authority, session, logged_in_user):
{'nameType': 'IPAddress', 'value': '127.0.0.1'}, {'nameType': 'IPAddress', 'value': '127.0.0.1'},
] ]
} }
} },
'dns_provider': None,
} }
data, errors = CertificateInputSchema().load(input_data) data, errors = CertificateInputSchema().load(input_data)
@ -306,6 +313,7 @@ def test_certificate_incative_authority(client, authority, session, logged_in_us
'description': 'testtestest', 'description': 'testtestest',
'validityStart': '2020-01-01T00:00:00', 'validityStart': '2020-01-01T00:00:00',
'validityEnd': '2020-01-01T00:00:01', 'validityEnd': '2020-01-01T00:00:01',
'dns_provider': None,
} }
data, errors = CertificateInputSchema().load(input_data) data, errors = CertificateInputSchema().load(input_data)
@ -329,7 +337,8 @@ def test_certificate_disallowed_names(client, authority, session, logged_in_user
{'nameType': 'DNSName', 'value': 'evilhacker.org'}, {'nameType': 'DNSName', 'value': 'evilhacker.org'},
] ]
} }
} },
'dns_provider': None,
} }
data, errors = CertificateInputSchema().load(input_data) data, errors = CertificateInputSchema().load(input_data)
@ -348,6 +357,7 @@ def test_certificate_sensitive_name(client, authority, session, logged_in_user):
'description': 'testtestest', 'description': 'testtestest',
'validityStart': '2020-01-01T00:00:00', 'validityStart': '2020-01-01T00:00:00',
'validityEnd': '2020-01-01T00:00:01', 'validityEnd': '2020-01-01T00:00:01',
'dns_provider': None,
} }
session.add(Domain(name='sensitive.example.com', sensitive=True)) session.add(Domain(name='sensitive.example.com', sensitive=True))