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
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
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
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
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
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
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
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
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.')
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.')
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.")
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
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
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
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))
def unit(options): return get_plugin_option('unit', options)
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
def unit(options): return get_plugin_option('unit', options)
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)
def interval(options): return get_plugin_option('interval', options)
def interval(options): return get_plugin_option("interval", options)
def unit(options): return get_plugin_option("unit", options)
def interval(options): return get_plugin_option('interval', options)