Beispiel #1
0
def add_aws_destination_to_sources(dst):
    """
    Given a destination check, if it can be added as sources, and included it if not already a source
    We identify qualified destinations based on the sync_as_source attributed of the plugin.
    The destination sync_as_source_name reveals the name of the suitable source-plugin.
    We rely on account numbers to avoid duplicates.
    :return: true for success and false for not adding the destination as source
    """
    # a set of all accounts numbers available as sources
    src_accounts = set()
    sources = get_all()
    for src in sources:
        src_accounts.add(get_plugin_option('accountNumber', src.options))

    # check
    destination_plugin = plugins.get(dst.plugin_name)
    account_number = get_plugin_option('accountNumber', dst.options)
    if account_number is not None and \
            destination_plugin.sync_as_source is not None and \
            destination_plugin.sync_as_source and \
            (account_number not in src_accounts):
        src_options = copy.deepcopy(plugins.get(destination_plugin.sync_as_source_name).options)
        set_plugin_option('accountNumber', account_number, src_options)
        create(label=dst.label,
               plugin_name=destination_plugin.sync_as_source_name,
               options=src_options,
               description=dst.description)
        return True

    return False
Beispiel #2
0
def add_aws_destination_to_sources(dst):
    """
    Given a destination, check if it can be added as sources, and include it if not already a source
    We identify qualified destinations based on the sync_as_source attributed of the plugin.
    The destination sync_as_source_name reveals the name of the suitable source-plugin.
    We rely on account numbers to avoid duplicates.
    :return: true for success and false for not adding the destination as source
    """
    # a set of all accounts numbers available as sources
    src_accounts = set()
    sources = get_all()
    for src in sources:
        src_accounts.add(get_plugin_option("accountNumber", src.options))

    # check
    destination_plugin = plugins.get(dst.plugin_name)
    account_number = get_plugin_option("accountNumber", dst.options)
    if (account_number is not None
            and destination_plugin.sync_as_source is not None
            and destination_plugin.sync_as_source
            and (account_number not in src_accounts)):
        src_options = copy.deepcopy(
            plugins.get(destination_plugin.sync_as_source_name).options)
        set_plugin_option("accountNumber", account_number, src_options)
        create(
            label=dst.label,
            plugin_name=destination_plugin.sync_as_source_name,
            options=src_options,
            description=dst.description,
        )
        return True

    return False
Beispiel #3
0
def needs_notification(certificate):
    """
    Determine if notifications for a given certificate should
    currently be sent

    :param certificate:
    :return:
    """
    now = arrow.utcnow()
    days = (certificate.not_after - now).days

    for notification in certificate.notifications:
        if not notification.options:
            return

        interval = get_plugin_option('interval', notification.options)
        unit = get_plugin_option('unit', notification.options)

        if unit == 'weeks':
            interval *= 7

        elif unit == 'months':
            interval *= 30

        elif unit == 'days':  # it's nice to be explicit about the base unit
            pass

        else:
            raise Exception(
                "Invalid base unit for expiration interval: {0}".format(unit))

        if days == interval:
            return notification
Beispiel #4
0
def needs_notification(certificate):
    """
    Determine if notifications for a given certificate should
    currently be sent

    :param certificate:
    :return:
    """
    now = arrow.utcnow()
    days = (certificate.not_after - now).days

    notifications = []

    for notification in certificate.notifications:
        if not notification.active or not notification.options:
            return

        interval = get_plugin_option('interval', notification.options)
        unit = get_plugin_option('unit', notification.options)

        if unit == 'weeks':
            interval *= 7

        elif unit == 'months':
            interval *= 30

        elif unit == 'days':  # it's nice to be explicit about the base unit
            pass

        else:
            raise Exception("Invalid base unit for expiration interval: {0}".format(unit))

        if days == interval:
            notifications.append(notification)
    return notifications
Beispiel #5
0
def calculate_expiration_days(options):
    unit = get_plugin_option("unit", options)
    interval = get_plugin_option("interval", options)
    if unit == "weeks":
        return interval * 7

    elif unit == "months":
        return interval * 30

    elif unit == "days":
        return interval
Beispiel #6
0
def send_expiration_notifications(exclude):
    """
    This function will check for upcoming certificate expiration,
    and send out notification emails at given intervals.
    """
    success = failure = 0

    # security team gets all
    security_email = current_app.config.get("LEMUR_SECURITY_TEAM_EMAIL")

    security_data = []
    for owner, notification_group in get_eligible_certificates(
            exclude=exclude).items():

        for notification_label, certificates in notification_group.items():
            notification_data = []

            notification = certificates[0][0]

            for data in certificates:
                n, certificate = data
                cert_data = certificate_notification_output_schema.dump(
                    certificate).data
                notification_data.append(cert_data)
                security_data.append(cert_data)

            if send_notification("expiration", notification_data, [owner],
                                 notification):
                success += 1
            else:
                failure += 1

            notification_recipient = get_plugin_option("recipients",
                                                       notification.options)
            if notification_recipient:
                notification_recipient = notification_recipient.split(",")
                # removing owner and security_email from notification_recipient
                notification_recipient = [
                    i for i in notification_recipient
                    if i not in security_email and i != owner
                ]

            if (notification_recipient):
                if send_notification(
                        "expiration",
                        notification_data,
                        notification_recipient,
                        notification,
                ):
                    success += 1
                else:
                    failure += 1

            if send_notification("expiration", security_data, security_email,
                                 notification):
                success += 1
            else:
                failure += 1

    return success, failure
Beispiel #7
0
    def filter_recipients(options, excluded_recipients, **kwargs):
        notification_recipients = get_plugin_option("recipients", options)
        if notification_recipients:
            notification_recipients = notification_recipients.split(",")
            # removing owner and security_email from notification_recipient
            notification_recipients = [i for i in notification_recipients if i not in excluded_recipients]

        return notification_recipients
Beispiel #8
0
def add_aws_destination_to_sources(dst):
    """
    Given a destination, check if it can be added as sources, and include it if not already a source
    We identify qualified destinations based on the sync_as_source attributed of the plugin.
    The destination sync_as_source_name reveals the name of the suitable source-plugin.
    We rely on account numbers to avoid duplicates.
    :return: true for success and false for not adding the destination as source
    """
    # check that destination can be synced to a source
    destination_plugin = plugins.get(dst.plugin_name)
    if destination_plugin.sync_as_source is None or not destination_plugin.sync_as_source:
        return False
    account_number = get_plugin_option("accountNumber", dst.options)
    if account_number is None:
        return False
    path = get_plugin_option("path", dst.options)
    if path is None:
        return False

    # a set of all (account number, path) available as sources
    src_account_paths = set()
    sources = get_all()
    for src in sources:
        src_account_paths.add((get_plugin_option("accountNumber", src.options),
                               get_plugin_option("path", src.options)))

    if (account_number, path) not in src_account_paths:
        src_options = copy.deepcopy(
            plugins.get(destination_plugin.sync_as_source_name).options)
        set_plugin_option("accountNumber", account_number, src_options)
        set_plugin_option("path", path, src_options)
        # Set the right endpointType for cloudfront sources.
        if get_plugin_option(
                "endpointType",
                src_options) is not None and path == "/cloudfront/":
            set_plugin_option("endpointType", "cloudfront", src_options)
        create(
            label=dst.label,
            plugin_name=destination_plugin.sync_as_source_name,
            options=src_options,
            description=dst.description,
        )
        return True

    return False
Beispiel #9
0
def validate_options(options):
    """
    Ensures that the plugin options are valid.
    :param options:
    :return:
    """
    interval = get_plugin_option('interval', options)
    unit = get_plugin_option('unit', options)

    if not interval and not unit:
        return

    if unit == 'month':
        interval *= 30

    elif unit == 'week':
        interval *= 7

    if interval > 90:
        raise ValidationError('Notification cannot be more than 90 days into the future.')
Beispiel #10
0
def validate_options(options):
    """
    Ensures that the plugin options are valid.
    :param options:
    :return:
    """
    interval = get_plugin_option('interval', options)
    unit = get_plugin_option('unit', options)

    if not interval and not unit:
        return

    if unit == 'month':
        interval *= 30

    elif unit == 'week':
        interval *= 7

    if interval > 90:
        raise ValidationError('Notification cannot be more than 90 days into the future.')
Beispiel #11
0
def validate_options(options):
    """
    Ensures that the plugin options are valid.
    :param options:
    :return:
    """
    interval = get_plugin_option("interval", options)
    unit = get_plugin_option("unit", options)

    if not interval and not unit:
        return

    if unit == "month":
        interval *= 30

    elif unit == "week":
        interval *= 7

    if interval > 90:
        raise ValidationError(
            "Notification cannot be more than 90 days into the future.")
Beispiel #12
0
def needs_notification(certificate):
    """
    Determine if notifications for a given certificate should currently be sent.
    For each notification configured for the cert, verifies it is active, properly configured,
    and that the configured expiration period is currently met.

    :param certificate:
    :return:
    """
    now = arrow.utcnow()
    days = (certificate.not_after - now).days

    notifications = []

    for notification in certificate.notifications:
        if not notification.active or not notification.options:
            continue

        interval = get_plugin_option("interval", notification.options)
        unit = get_plugin_option("unit", notification.options)

        if unit == "weeks":
            interval *= 7

        elif unit == "months":
            interval *= 30

        elif unit == "days":  # it's nice to be explicit about the base unit
            pass

        else:
            raise Exception(
                f"Invalid base unit for expiration interval: {unit}"
            )
        if days == interval:
            notifications.append(notification)
    return notifications
Beispiel #13
0
def needs_notification(certificate):
    """
    Determine if notifications for a given certificate should
    currently be sent

    :param certificate:
    :return:
    """
    now = arrow.utcnow()
    days = (certificate.not_after - now).days

    notifications = []

    for notification in certificate.notifications:
        if not notification.active or not notification.options:
            return

        interval = get_plugin_option("interval", notification.options)
        unit = get_plugin_option("unit", notification.options)

        if unit == "weeks":
            interval *= 7

        elif unit == "months":
            interval *= 30

        elif unit == "days":  # it's nice to be explicit about the base unit
            pass

        else:
            raise Exception(
                "Invalid base unit for expiration interval: {0}".format(unit))

        if days == interval:
            notifications.append(notification)
    return notifications
Beispiel #14
0
def send_expiration_notifications(exclude):
    """
    This function will check for upcoming certificate expiration,
    and send out notification emails at given intervals.
    """
    success = failure = 0

    # security team gets all
    security_email = current_app.config.get('LEMUR_SECURITY_TEAM_EMAIL')

    security_data = []
    for owner, notification_group in get_eligible_certificates(exclude=exclude).items():

        for notification_label, certificates in notification_group.items():
            notification_data = []

            notification = certificates[0][0]

            for data in certificates:
                n, certificate = data
                cert_data = certificate_notification_output_schema.dump(certificate).data
                notification_data.append(cert_data)
                security_data.append(cert_data)

            notification_recipient = get_plugin_option('recipients', notification.options)
            if notification_recipient:
                notification_recipient = notification_recipient.split(",")

            if send_notification('expiration', notification_data, [owner], notification):
                success += 1
            else:
                failure += 1

            if notification_recipient and owner != notification_recipient and security_email != notification_recipient:
                if send_notification('expiration', notification_data, notification_recipient, notification):
                    success += 1
                else:
                    failure += 1

            if send_notification('expiration', security_data, security_email, notification):
                success += 1
            else:
                failure += 1

    return success, failure
Beispiel #15
0
    def get_recipients(options, additional_recipients, **kwargs):
        notification_recipients = get_plugin_option("recipients", options)
        if notification_recipients:
            notification_recipients = notification_recipients.split(",")

        return list(set(notification_recipients + additional_recipients))
Beispiel #16
0
def unit(options):
    return get_plugin_option('unit', options)
Beispiel #17
0
def create(**kwargs):
    """
    Creates a new certificate.
    """
    # Validate destinations do not overlap accounts
    if "destinations" in kwargs:
        dest_accounts = {}
        for dest in kwargs["destinations"]:
            account = get_plugin_option("accountNumber", dest.options)
            if account in dest_accounts:
                raise Exception(f"Only one destination allowed per account: {account}")
            dest_accounts[account] = True

    try:
        cert_body, private_key, cert_chain, external_id, csr = mint(**kwargs)
    except Exception:
        log_data = {
            "message": "Exception minting certificate",
            "issuer": kwargs["authority"].name,
            "cn": kwargs.get("common_name"),
        }
        current_app.logger.error(log_data, exc_info=True)
        capture_exception()
        raise
    kwargs["body"] = cert_body
    kwargs["private_key"] = private_key
    kwargs["chain"] = cert_chain
    kwargs["external_id"] = external_id
    kwargs["csr"] = csr

    roles = create_certificate_roles(**kwargs)

    if kwargs.get("roles"):
        kwargs["roles"] += roles
    else:
        kwargs["roles"] = roles

    if cert_body:
        cert = Certificate(**kwargs)
        kwargs["creator"].certificates.append(cert)
    else:
        # ACME path
        cert = PendingCertificate(**kwargs)
        kwargs["creator"].pending_certificates.append(cert)

    cert.authority = kwargs["authority"]

    database.commit()

    if isinstance(cert, Certificate):
        certificate_issued.send(certificate=cert, authority=cert.authority)
        metrics.send(
            "certificate_issued",
            "counter",
            1,
            metric_tags=dict(owner=cert.owner, issuer=cert.issuer),
        )
        log_data = {
            "function": "lemur.certificates.service.create",
            "owner": cert.owner,
            "name": cert.name,
            "serial": cert.serial,
            "issuer": cert.issuer,
            "not_after": cert.not_after.format('YYYY-MM-DD HH:mm:ss'),
            "not_before": cert.not_before.format('YYYY-MM-DD HH:mm:ss'),
            "sans": str(', '.join([domain.name for domain in cert.domains])),
        }
        current_app.logger.info(log_data)

    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, kwargs.get("async_reissue_notification_cert_id", None)), countdown=5)

    return cert
Beispiel #18
0
def unit(options):
    return get_plugin_option('unit', options)
Beispiel #19
0
def enable_cloudfront(source_label):
    """
    Given the label of a legacy AWS source (without path or endpointType options), set up the source for CloudFront:

    #. Update the source options to the newest template, inheriting the existing values.
    #. Set ``path`` to "/" and ``endpointType`` to "elb" to restrict the source to discovering ELBs and related certs only.
    #. Create a new source (and destination) for the same accountNumber with ``path`` as "/cloudfront/" and ``endpointType`` as "cloudfront"

    :param source_strings:
    :return:
    """
    class ValidationError(Exception):
        pass

    try:
        source = source_service.get_by_label(source_label)
        if not source:
            raise ValidationError(
                f"Unable to find source with label: {source_label}")
        if source.plugin_name != "aws-source":
            raise ValidationError(
                f"Source '{source_label}' is not an AWS source")
        for opt_name in ["endpointType", "path"]:
            if get_plugin_option(opt_name, source.options) is not None:
                raise ValidationError(
                    f"Source '{source_label}' already sets option '{opt_name}'"
                )
        cloudfront_label = f"{source_label}-cloudfront"
        cloudfront_source = source_service.get_by_label(cloudfront_label)
        if cloudfront_source:
            raise ValidationError(
                f"A source named '{cloudfront_label}' already exists")

        p = plugins.get(source.plugin_name)
        new_options = deepcopy(p.options)
        for old_opt in source.options:
            name = old_opt["name"]
            value = get_plugin_option(name, source.options)
            set_plugin_option(name, value, new_options)
        set_plugin_option("path", "/", new_options)
        set_plugin_option("endpointType", "elb", new_options)
        source_service.update(source.id, source.label, source.plugin_name,
                              new_options, source.description)

        cloudfront_options = deepcopy(new_options)
        set_plugin_option("path", "/cloudfront/", cloudfront_options)
        set_plugin_option("endpointType", "cloudfront", cloudfront_options)
        source_service.create(
            cloudfront_label, source.plugin_name, cloudfront_options,
            f"CloudFront certificates and distributions for {source_label}")

        print(
            f"[+] Limited source {source_label} to discover ELBs and ELB certificates.\n"
        )
        print(
            f"[+] Created source {cloudfront_label} to discover CloudFront distributions and certificates.\n"
        )

    except ValidationError as e:
        print(f"[+] Error: {str(e)}")
        sys.exit(1)
Beispiel #20
0
def interval(options):
    return get_plugin_option('interval', options)
Beispiel #21
0
def interval(options):
    return get_plugin_option("interval", options)
Beispiel #22
0
def unit(options):
    return get_plugin_option("unit", options)
Beispiel #23
0
def interval(options):
    return get_plugin_option('interval', options)