Merge branch 'master' into hosseinsh-celeryjob-sync-src-dst
This commit is contained in:
@ -50,6 +50,19 @@ def make_celery(app):
|
||||
celery = make_celery(flask_app)
|
||||
|
||||
|
||||
def is_task_active(fun, task_id, args):
|
||||
from celery.task.control import inspect
|
||||
i = inspect()
|
||||
active_tasks = i.active()
|
||||
for _, tasks in active_tasks.items():
|
||||
for task in tasks:
|
||||
if task.get("id") == task_id:
|
||||
continue
|
||||
if task.get("name") == fun and task.get("args") == str(args):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
@celery.task()
|
||||
def fetch_acme_cert(id):
|
||||
"""
|
||||
@ -227,8 +240,24 @@ def sync_source(source):
|
||||
:param source:
|
||||
:return:
|
||||
"""
|
||||
current_app.logger.debug("Syncing source {}".format(source))
|
||||
|
||||
function = "{}.{}".format(__name__, sys._getframe().f_code.co_name)
|
||||
task_id = celery.current_task.request.id
|
||||
log_data = {
|
||||
"function": function,
|
||||
"message": "Syncing source",
|
||||
"source": source,
|
||||
"task_id": task_id,
|
||||
}
|
||||
current_app.logger.debug(log_data)
|
||||
|
||||
if is_task_active(function, task_id, (source,)):
|
||||
log_data["message"] = "Skipping task: Task is already active"
|
||||
current_app.logger.debug(log_data)
|
||||
return
|
||||
sync([source])
|
||||
log_data["message"] = "Done syncing source"
|
||||
current_app.logger.debug(log_data)
|
||||
|
||||
|
||||
@celery.task()
|
||||
|
@ -3,6 +3,8 @@ import unicodedata
|
||||
|
||||
from cryptography import x509
|
||||
from flask import current_app
|
||||
|
||||
from lemur.common.utils import is_selfsigned
|
||||
from lemur.extensions import sentry
|
||||
from lemur.constants import SAN_NAMING_TEMPLATE, DEFAULT_NAMING_TEMPLATE
|
||||
|
||||
@ -229,15 +231,22 @@ def issuer(cert):
|
||||
"""
|
||||
Gets a sane issuer slug from a given certificate, stripping non-alphanumeric characters.
|
||||
|
||||
:param cert:
|
||||
For self-signed certificates, the special value '<selfsigned>' is returned.
|
||||
If issuer cannot be determined, '<unknown>' is returned.
|
||||
|
||||
:param cert: Parsed certificate object
|
||||
:return: Issuer slug
|
||||
"""
|
||||
# If certificate is self-signed, we return a special value -- there really is no distinct "issuer" for it
|
||||
if is_selfsigned(cert):
|
||||
return '<selfsigned>'
|
||||
|
||||
# Try Common Name or fall back to Organization name
|
||||
attrs = (cert.issuer.get_attributes_for_oid(x509.OID_COMMON_NAME) or
|
||||
cert.issuer.get_attributes_for_oid(x509.OID_ORGANIZATION_NAME))
|
||||
if not attrs:
|
||||
current_app.logger.error("Unable to get issuer! Cert serial {:x}".format(cert.serial_number))
|
||||
return "Unknown"
|
||||
return '<unknown>'
|
||||
|
||||
return text_to_slug(attrs[0].value, '')
|
||||
|
||||
|
@ -7,13 +7,15 @@
|
||||
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
|
||||
"""
|
||||
import random
|
||||
import re
|
||||
import string
|
||||
|
||||
import sqlalchemy
|
||||
from cryptography import x509
|
||||
from cryptography.exceptions import InvalidSignature, UnsupportedAlgorithm
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.hazmat.primitives import hashes
|
||||
from cryptography.hazmat.primitives.asymmetric import rsa, ec
|
||||
from cryptography.hazmat.primitives.asymmetric import rsa, ec, padding
|
||||
from cryptography.hazmat.primitives.serialization import load_pem_private_key
|
||||
from flask_restful.reqparse import RequestParser
|
||||
from sqlalchemy import and_, func
|
||||
@ -66,6 +68,26 @@ def parse_private_key(private_key):
|
||||
return load_pem_private_key(private_key.encode('utf8'), password=None, backend=default_backend())
|
||||
|
||||
|
||||
def split_pem(data):
|
||||
"""
|
||||
Split a string of several PEM payloads to a list of strings.
|
||||
|
||||
:param data: String
|
||||
:return: List of strings
|
||||
"""
|
||||
return re.split("\n(?=-----BEGIN )", data)
|
||||
|
||||
|
||||
def parse_cert_chain(pem_chain):
|
||||
"""
|
||||
Helper function to split and parse a series of PEM certificates.
|
||||
|
||||
:param pem_chain: string
|
||||
:return: List of parsed certificates
|
||||
"""
|
||||
return [parse_certificate(cert) for cert in split_pem(pem_chain) if pem_chain]
|
||||
|
||||
|
||||
def parse_csr(csr):
|
||||
"""
|
||||
Helper function that parses a CSR.
|
||||
@ -143,6 +165,42 @@ def generate_private_key(key_type):
|
||||
)
|
||||
|
||||
|
||||
def check_cert_signature(cert, issuer_public_key):
|
||||
"""
|
||||
Check a certificate's signature against an issuer public key.
|
||||
Before EC validation, make sure we support the algorithm, otherwise raise UnsupportedAlgorithm
|
||||
On success, returns None; on failure, raises UnsupportedAlgorithm or InvalidSignature.
|
||||
"""
|
||||
if isinstance(issuer_public_key, rsa.RSAPublicKey):
|
||||
# RSA requires padding, just to make life difficult for us poor developers :(
|
||||
if cert.signature_algorithm_oid == x509.SignatureAlgorithmOID.RSASSA_PSS:
|
||||
# In 2005, IETF devised a more secure padding scheme to replace PKCS #1 v1.5. To make sure that
|
||||
# nobody can easily support or use it, they mandated lots of complicated parameters, unlike any
|
||||
# other X.509 signature scheme.
|
||||
# https://tools.ietf.org/html/rfc4056
|
||||
raise UnsupportedAlgorithm("RSASSA-PSS not supported")
|
||||
else:
|
||||
padder = padding.PKCS1v15()
|
||||
issuer_public_key.verify(cert.signature, cert.tbs_certificate_bytes, padder, cert.signature_hash_algorithm)
|
||||
elif isinstance(issuer_public_key, ec.EllipticCurvePublicKey) and isinstance(ec.ECDSA(cert.signature_hash_algorithm), ec.ECDSA):
|
||||
issuer_public_key.verify(cert.signature, cert.tbs_certificate_bytes, ec.ECDSA(cert.signature_hash_algorithm))
|
||||
else:
|
||||
raise UnsupportedAlgorithm("Unsupported Algorithm '{var}'.".format(var=cert.signature_algorithm_oid._name))
|
||||
|
||||
|
||||
def is_selfsigned(cert):
|
||||
"""
|
||||
Returns True if the certificate is self-signed.
|
||||
Returns False for failed verification or unsupported signing algorithm.
|
||||
"""
|
||||
try:
|
||||
check_cert_signature(cert, cert.public_key())
|
||||
# If verification was successful, it's self-signed.
|
||||
return True
|
||||
except InvalidSignature:
|
||||
return False
|
||||
|
||||
|
||||
def is_weekend(date):
|
||||
"""
|
||||
Determines if a given date is on a weekend.
|
||||
|
@ -1,27 +1,14 @@
|
||||
import re
|
||||
|
||||
from cryptography import x509
|
||||
from cryptography.exceptions import UnsupportedAlgorithm, InvalidSignature
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.x509 import NameOID
|
||||
from flask import current_app
|
||||
from marshmallow.exceptions import ValidationError
|
||||
|
||||
from lemur.auth.permissions import SensitiveDomainPermission
|
||||
from lemur.common.utils import parse_certificate, is_weekend
|
||||
|
||||
|
||||
def public_certificate(body):
|
||||
"""
|
||||
Determines if specified string is valid public certificate.
|
||||
|
||||
:param body:
|
||||
:return:
|
||||
"""
|
||||
try:
|
||||
parse_certificate(body)
|
||||
except Exception as e:
|
||||
current_app.logger.exception(e)
|
||||
raise ValidationError('Public certificate presented is not valid.')
|
||||
from lemur.common.utils import check_cert_signature, is_weekend
|
||||
|
||||
|
||||
def common_name(value):
|
||||
@ -138,3 +125,34 @@ def verify_private_key_match(key, cert, error_class=ValidationError):
|
||||
"""
|
||||
if key.public_key().public_numbers() != cert.public_key().public_numbers():
|
||||
raise error_class("Private key does not match certificate.")
|
||||
|
||||
|
||||
def verify_cert_chain(certs, error_class=ValidationError):
|
||||
"""
|
||||
Verifies that the certificates in the chain are correct.
|
||||
|
||||
We don't bother with full cert validation but just check that certs in the chain are signed by the next, to avoid
|
||||
basic human errors -- such as pasting the wrong certificate.
|
||||
|
||||
:param certs: List of parsed certificates, use parse_cert_chain()
|
||||
:param error_class: Exception class to raise on error
|
||||
"""
|
||||
cert = certs[0]
|
||||
for issuer in certs[1:]:
|
||||
# Use the current cert's public key to verify the previous signature.
|
||||
# "certificate validation is a complex problem that involves much more than just signature checks"
|
||||
try:
|
||||
check_cert_signature(cert, issuer.public_key())
|
||||
|
||||
except InvalidSignature:
|
||||
# Avoid circular import.
|
||||
from lemur.common import defaults
|
||||
|
||||
raise error_class("Incorrect chain certificate(s) provided: '%s' is not signed by '%s'"
|
||||
% (defaults.common_name(cert) or 'Unknown', defaults.common_name(issuer)))
|
||||
|
||||
except UnsupportedAlgorithm as err:
|
||||
current_app.logger.warning("Skipping chain validation: %s", err)
|
||||
|
||||
# Next loop will validate that *this issuer* cert is signed by the next chain cert.
|
||||
cert = issuer
|
||||
|
Reference in New Issue
Block a user