Merge branch 'master' into get_by_attributes
This commit is contained in:
@ -265,30 +265,31 @@ def query(fqdns, issuer, owner, expired):
|
||||
table = []
|
||||
|
||||
q = database.session_query(Certificate)
|
||||
if issuer:
|
||||
sub_query = database.session_query(Authority.id) \
|
||||
.filter(Authority.name.ilike('%{0}%'.format(issuer))) \
|
||||
.subquery()
|
||||
|
||||
sub_query = database.session_query(Authority.id) \
|
||||
.filter(Authority.name.ilike('%{0}%'.format(issuer))) \
|
||||
.subquery()
|
||||
|
||||
q = q.filter(
|
||||
or_(
|
||||
Certificate.issuer.ilike('%{0}%'.format(issuer)),
|
||||
Certificate.authority_id.in_(sub_query)
|
||||
q = q.filter(
|
||||
or_(
|
||||
Certificate.issuer.ilike('%{0}%'.format(issuer)),
|
||||
Certificate.authority_id.in_(sub_query)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
q = q.filter(Certificate.owner.ilike('%{0}%'.format(owner)))
|
||||
if owner:
|
||||
q = q.filter(Certificate.owner.ilike('%{0}%'.format(owner)))
|
||||
|
||||
if not expired:
|
||||
q = q.filter(Certificate.expired == False) # noqa
|
||||
|
||||
for f in fqdns.split(','):
|
||||
q = q.filter(
|
||||
or_(
|
||||
Certificate.cn.ilike('%{0}%'.format(f)),
|
||||
Certificate.domains.any(Domain.name.ilike('%{0}%'.format(f)))
|
||||
if fqdns:
|
||||
for f in fqdns.split(','):
|
||||
q = q.filter(
|
||||
or_(
|
||||
Certificate.cn.ilike('%{0}%'.format(f)),
|
||||
Certificate.domains.any(Domain.name.ilike('%{0}%'.format(f)))
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
for c in q.all():
|
||||
table.append([c.id, c.name, c.owner, c.issuer])
|
||||
@ -363,10 +364,7 @@ def check_revoked():
|
||||
else:
|
||||
status = verify_string(cert.body, "")
|
||||
|
||||
if status is None:
|
||||
cert.status = 'unknown'
|
||||
else:
|
||||
cert.status = 'valid' if status else 'revoked'
|
||||
cert.status = 'valid' if status else 'revoked'
|
||||
|
||||
except Exception as e:
|
||||
sentry.captureException()
|
||||
|
@ -19,7 +19,7 @@ from sqlalchemy.sql.expression import case, extract
|
||||
from sqlalchemy_utils.types.arrow import ArrowType
|
||||
from werkzeug.utils import cached_property
|
||||
|
||||
from lemur.common import defaults, utils
|
||||
from lemur.common import defaults, utils, validators
|
||||
from lemur.constants import SUCCESS_METRIC_STATUS, FAILURE_METRIC_STATUS
|
||||
from lemur.database import db
|
||||
from lemur.domains.models import Domain
|
||||
@ -77,6 +77,14 @@ def get_or_increase_name(name, serial):
|
||||
|
||||
class Certificate(db.Model):
|
||||
__tablename__ = 'certificates'
|
||||
__table_args__ = (
|
||||
Index('ix_certificates_cn', "cn",
|
||||
postgresql_ops={"cn": "gin_trgm_ops"},
|
||||
postgresql_using='gin'),
|
||||
Index('ix_certificates_name', "name",
|
||||
postgresql_ops={"name": "gin_trgm_ops"},
|
||||
postgresql_using='gin'),
|
||||
)
|
||||
id = Column(Integer, primary_key=True)
|
||||
ix = Index('ix_certificates_id_desc', id.desc(), postgresql_using='btree', unique=True)
|
||||
external_id = Column(String(128))
|
||||
@ -130,7 +138,6 @@ class Certificate(db.Model):
|
||||
logs = relationship('Log', backref='certificate')
|
||||
endpoints = relationship('Endpoint', backref='certificate')
|
||||
rotation_policy = relationship("RotationPolicy")
|
||||
|
||||
sensitive_fields = ('private_key',)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
@ -179,6 +186,18 @@ class Certificate(db.Model):
|
||||
for domain in defaults.domains(cert):
|
||||
self.domains.append(Domain(name=domain))
|
||||
|
||||
# Check integrity before saving anything into the database.
|
||||
# For user-facing API calls, validation should also be done in schema validators.
|
||||
self.check_integrity()
|
||||
|
||||
def check_integrity(self):
|
||||
"""
|
||||
Integrity checks: Does the cert have a matching private key?
|
||||
"""
|
||||
if self.private_key:
|
||||
validators.verify_private_key_match(utils.parse_private_key(self.private_key), self.parsed_cert,
|
||||
error_class=AssertionError)
|
||||
|
||||
@cached_property
|
||||
def parsed_cert(self):
|
||||
assert self.body, "Certificate body not set"
|
||||
@ -208,6 +227,10 @@ class Certificate(db.Model):
|
||||
def location(self):
|
||||
return defaults.location(self.parsed_cert)
|
||||
|
||||
@property
|
||||
def distinguished_name(self):
|
||||
return self.parsed_cert.subject.rfc4514_string()
|
||||
|
||||
@property
|
||||
def key_type(self):
|
||||
if isinstance(self.parsed_cert.public_key(), rsa.RSAPublicKey):
|
||||
@ -359,7 +382,7 @@ def update_destinations(target, value, initiator):
|
||||
destination_plugin = plugins.get(value.plugin_name)
|
||||
status = FAILURE_METRIC_STATUS
|
||||
try:
|
||||
if target.private_key:
|
||||
if target.private_key or not destination_plugin.requires_key:
|
||||
destination_plugin.upload(target.name, target.body, target.private_key, target.chain, value.options)
|
||||
status = SUCCESS_METRIC_STATUS
|
||||
except Exception as e:
|
||||
|
@ -10,7 +10,7 @@ from marshmallow import fields, validate, validates_schema, post_load, pre_load
|
||||
from marshmallow.exceptions import ValidationError
|
||||
|
||||
from lemur.authorities.schemas import AuthorityNestedOutputSchema
|
||||
from lemur.common import validators, missing
|
||||
from lemur.common import missing, utils, validators
|
||||
from lemur.common.fields import ArrowDateTime, Hex
|
||||
from lemur.common.schema import LemurInputSchema, LemurOutputSchema
|
||||
from lemur.constants import CERTIFICATE_KEY_TYPES
|
||||
@ -206,6 +206,7 @@ class CertificateOutputSchema(LemurOutputSchema):
|
||||
|
||||
cn = fields.String()
|
||||
common_name = fields.String(attribute='cn')
|
||||
distinguished_name = fields.String()
|
||||
|
||||
not_after = fields.DateTime()
|
||||
validity_end = ArrowDateTime(attribute='not_after')
|
||||
@ -242,8 +243,8 @@ class CertificateUploadInputSchema(CertificateCreationSchema):
|
||||
authority = fields.Nested(AssociatedAuthoritySchema, required=False)
|
||||
notify = fields.Boolean(missing=True)
|
||||
external_id = fields.String(missing=None, allow_none=True)
|
||||
private_key = fields.String(validate=validators.private_key)
|
||||
body = fields.String(required=True, validate=validators.public_certificate)
|
||||
private_key = fields.String()
|
||||
body = fields.String(required=True)
|
||||
chain = fields.String(validate=validators.public_certificate, missing=None,
|
||||
allow_none=True) # TODO this could be multiple certificates
|
||||
|
||||
@ -258,6 +259,26 @@ class CertificateUploadInputSchema(CertificateCreationSchema):
|
||||
if not data.get('private_key'):
|
||||
raise ValidationError('Destinations require private key.')
|
||||
|
||||
@validates_schema
|
||||
def validate_cert_private_key(self, data):
|
||||
cert = None
|
||||
key = None
|
||||
if data.get('body'):
|
||||
try:
|
||||
cert = utils.parse_certificate(data['body'])
|
||||
except ValueError:
|
||||
raise ValidationError("Public certificate presented is not valid.", field_names=['body'])
|
||||
|
||||
if data.get('private_key'):
|
||||
try:
|
||||
key = utils.parse_private_key(data['private_key'])
|
||||
except ValueError:
|
||||
raise ValidationError("Private key presented is not valid.", field_names=['private_key'])
|
||||
|
||||
if cert and key:
|
||||
# Throws ValidationError
|
||||
validators.verify_private_key_match(key, cert)
|
||||
|
||||
|
||||
class CertificateExportInputSchema(LemurInputSchema):
|
||||
plugin = fields.Nested(PluginInputSchema)
|
||||
|
@ -237,11 +237,6 @@ def upload(**kwargs):
|
||||
else:
|
||||
kwargs['roles'] = roles
|
||||
|
||||
if kwargs.get('private_key'):
|
||||
private_key = kwargs['private_key']
|
||||
if not isinstance(private_key, bytes):
|
||||
kwargs['private_key'] = private_key.encode('utf-8')
|
||||
|
||||
cert = Certificate(**kwargs)
|
||||
cert.authority = kwargs.get('authority')
|
||||
cert = database.create(cert)
|
||||
@ -291,6 +286,14 @@ def create(**kwargs):
|
||||
certificate_issued.send(certificate=cert, authority=cert.authority)
|
||||
metrics.send('certificate_issued', 'counter', 1, metric_tags=dict(owner=cert.owner, issuer=cert.issuer))
|
||||
|
||||
if isinstance(cert, PendingCertificate):
|
||||
# We need to refresh the pending certificate to avoid "Instance is not bound to a Session; "
|
||||
# "attribute refresh operation cannot proceed"
|
||||
pending_cert = database.session_query(PendingCertificate).get(cert.id)
|
||||
from lemur.common.celery import fetch_acme_cert
|
||||
if not current_app.config.get("ACME_DISABLE_AUTORESOLVE", False):
|
||||
fetch_acme_cert.apply_async((pending_cert.id,), countdown=5)
|
||||
|
||||
return cert
|
||||
|
||||
|
||||
@ -314,7 +317,7 @@ def render(args):
|
||||
|
||||
if filt:
|
||||
terms = filt.split(';')
|
||||
term = '%{0}%'.format(terms[1])
|
||||
term = '{0}%'.format(terms[1])
|
||||
# Exact matches for quotes. Only applies to name, issuer, and cn
|
||||
if terms[1].startswith('"') and terms[1].endswith('"'):
|
||||
term = terms[1][1:-1]
|
||||
@ -378,7 +381,8 @@ def render(args):
|
||||
now = arrow.now().format('YYYY-MM-DD')
|
||||
query = query.filter(Certificate.not_after <= to).filter(Certificate.not_after >= now)
|
||||
|
||||
return database.sort_and_page(query, Certificate, args)
|
||||
result = database.sort_and_page(query, Certificate, args)
|
||||
return result
|
||||
|
||||
|
||||
def create_csr(**csr_config):
|
||||
@ -439,10 +443,7 @@ def create_csr(**csr_config):
|
||||
encoding=serialization.Encoding.PEM,
|
||||
format=serialization.PrivateFormat.TraditionalOpenSSL, # would like to use PKCS8 but AWS ELBs don't like it
|
||||
encryption_algorithm=serialization.NoEncryption()
|
||||
)
|
||||
|
||||
if isinstance(private_key, bytes):
|
||||
private_key = private_key.decode('utf-8')
|
||||
).decode('utf-8')
|
||||
|
||||
csr = request.public_bytes(
|
||||
encoding=serialization.Encoding.PEM
|
||||
@ -554,6 +555,9 @@ def reissue_certificate(certificate, replace=None, user=None):
|
||||
"""
|
||||
primitives = get_certificate_primitives(certificate)
|
||||
|
||||
if primitives.get("csr"):
|
||||
# We do not want to re-use the CSR when creating a certificate because this defeats the purpose of rotation.
|
||||
del primitives["csr"]
|
||||
if not user:
|
||||
primitives['creator'] = certificate.user
|
||||
|
||||
|
Reference in New Issue
Block a user