Beispiel #1
0
def reissue(old_certificate_name, commit):
    """
    Reissues certificate with the same parameters as it was originally issued with.
    If not time period is provided, reissues certificate as valid from today to
    today + length of original.
    """
    if commit:
        print("[!] Running in COMMIT mode.")

    print("[+] Starting certificate re-issuance.")

    status = FAILURE_METRIC_STATUS

    try:
        old_cert = validate_certificate(old_certificate_name)

        if not old_cert:
            for certificate in get_all_pending_reissue():
                request_reissue(certificate, commit)
        else:
            request_reissue(old_cert, commit)

        status = SUCCESS_METRIC_STATUS
        print("[+] Done!")
    except Exception as e:
        sentry.captureException()
        current_app.logger.exception("Error reissuing certificate.", exc_info=True)
        print(
            "[!] Failed to reissue certificates. Reason: {}".format(
                e
            )
        )

    metrics.send('certificate_reissue_job', 'counter', 1, metric_tags={'status': status})
Beispiel #2
0
    def post(self):
        access_token_url = 'https://accounts.google.com/o/oauth2/token'
        people_api_url = 'https://www.googleapis.com/plus/v1/people/me/openIdConnect'

        self.reqparse.add_argument('clientId', type=str, required=True, location='json')
        self.reqparse.add_argument('redirectUri', type=str, required=True, location='json')
        self.reqparse.add_argument('code', type=str, required=True, location='json')

        args = self.reqparse.parse_args()

        # Step 1. Exchange authorization code for access token
        payload = {
            'client_id': args['clientId'],
            'grant_type': 'authorization_code',
            'redirect_uri': args['redirectUri'],
            'code': args['code'],
            'client_secret': current_app.config.get('GOOGLE_SECRET')
        }

        r = requests.post(access_token_url, data=payload)
        token = r.json()

        # Step 2. Retrieve information about the current user
        headers = {'Authorization': 'Bearer {0}'.format(token['access_token'])}

        r = requests.get(people_api_url, headers=headers)
        profile = r.json()

        user = user_service.get_by_email(profile['email'])

        if user:
            metrics.send('successful_login', 'counter', 1)
            return dict(token=create_token(user))
Beispiel #3
0
    def post(self):
        access_token_url = "https://accounts.google.com/o/oauth2/token"
        people_api_url = "https://www.googleapis.com/plus/v1/people/me/openIdConnect"

        self.reqparse.add_argument("clientId", type=str, required=True, location="json")
        self.reqparse.add_argument("redirectUri", type=str, required=True, location="json")
        self.reqparse.add_argument("code", type=str, required=True, location="json")

        args = self.reqparse.parse_args()

        # Step 1. Exchange authorization code for access token
        payload = {
            "client_id": args["clientId"],
            "grant_type": "authorization_code",
            "redirect_uri": args["redirectUri"],
            "code": args["code"],
            "client_secret": current_app.config.get("GOOGLE_SECRET"),
        }

        r = requests.post(access_token_url, data=payload)
        token = r.json()

        # Step 2. Retrieve information about the current user
        headers = {"Authorization": "Bearer {0}".format(token["access_token"])}

        r = requests.get(people_api_url, headers=headers)
        profile = r.json()

        user = user_service.get_by_email(profile["email"])

        if user:
            metrics.send("successful_login", "counter", 1)
            return dict(token=create_token(user))
Beispiel #4
0
def create(**kwargs):
    """
    Creates a new certificate.
    """
    kwargs['creator'] = g.user.email
    cert_body, private_key, cert_chain = mint(**kwargs)
    kwargs['body'] = cert_body
    kwargs['private_key'] = private_key
    kwargs['chain'] = cert_chain

    roles = create_certificate_roles(**kwargs)

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

    cert = Certificate(**kwargs)

    g.user.certificates.append(cert)
    cert.authority = kwargs['authority']
    database.commit()

    metrics.send('certificate_issued', 'counter', 1, metric_tags=dict(owner=cert.owner, issuer=cert.issuer))
    return cert
Beispiel #5
0
def create(**kwargs):
    """
    Creates a new authority.
    """
    body, private_key, chain, roles = mint(**kwargs)

    kwargs['creator'].roles = list(set(list(kwargs['creator'].roles) + roles))

    kwargs['body'] = body
    kwargs['private_key'] = private_key
    kwargs['chain'] = chain

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

    cert = upload(**kwargs)
    kwargs['authority_certificate'] = cert
    if kwargs.get('plugin', {}).get('plugin_options', []):
        kwargs['options'] = json.dumps(kwargs['plugin']['plugin_options'])

    authority = Authority(**kwargs)
    authority = database.create(authority)
    kwargs['creator'].authorities.append(authority)

    metrics.send('authority_created', 'counter', 1, metric_tags=dict(owner=authority.owner))
    return authority
Beispiel #6
0
def create(**kwargs):
    """
    Creates a new authority.
    """
    kwargs['creator'] = g.user.email
    body, chain, roles = mint(**kwargs)

    kwargs['body'] = body
    kwargs['chain'] = chain

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

    if kwargs['type'] == 'subca':
        description = "This is the ROOT certificate for the {0} sub certificate authority the parent \
                                authority is {1}.".format(kwargs.get('name'), kwargs.get('parent'))
    else:
        description = "This is the ROOT certificate for the {0} certificate authority.".format(
            kwargs.get('name')
        )

    kwargs['description'] = description

    cert = upload(**kwargs)
    kwargs['authority_certificate'] = cert

    authority = Authority(**kwargs)
    authority = database.create(authority)
    g.user.authorities.append(authority)

    metrics.send('authority_created', 'counter', 1, metric_tags=dict(owner=authority.owner))
    return authority
Beispiel #7
0
def reissue(old_certificate_name, commit):
    """
    Reissues certificate with the same parameters as it was originally issued with.
    If not time period is provided, reissues certificate as valid from today to
    today + length of original.
    """
    if commit:
        print("[!] Running in COMMIT mode.")

    print("[+] Starting certificate re-issuance.")

    try:
        old_cert = validate_certificate(old_certificate_name)

        if not old_cert:
            for certificate in get_all_pending_reissue():
                print("[+] {0} is eligible for re-issuance".format(certificate.name))
                request_reissue(certificate, commit)
        else:
            request_reissue(old_cert, commit)

        print("[+] Done!")
    except Exception as e:
        sentry.captureException()
        metrics.send('certificate_reissue_failure', 'counter', 1)
        print(
            "[!] Failed to reissue certificates. Reason: {}".format(
                e
            )
        )
Beispiel #8
0
def request_rotation(endpoint, certificate, message, commit):
    """
    Rotates a certificate and handles any exceptions during
    execution.
    :param endpoint:
    :param certificate:
    :param message:
    :param commit:
    :return:
    """
    if commit:
        try:
            deployment_service.rotate_certificate(endpoint, certificate)
            metrics.send('endpoint_rotation_success', 'counter', 1)

            if message:
                send_rotation_notification(certificate)

        except Exception as e:
            metrics.send('endpoint_rotation_failure', 'counter', 1)
            print(
                "[!] Failed to rotate endpoint {0} to certificate {1} reason: {2}".format(
                    endpoint.name,
                    certificate.name,
                    e
                )
            )
Beispiel #9
0
def request_rotation(endpoint, certificate, message, commit):
    """
    Rotates a certificate and handles any exceptions during
    execution.
    :param endpoint:
    :param certificate:
    :param message:
    :param commit:
    :return:
    """
    status = FAILURE_METRIC_STATUS
    if commit:
        try:
            deployment_service.rotate_certificate(endpoint, certificate)

            if message:
                send_rotation_notification(certificate)

            status = SUCCESS_METRIC_STATUS

        except Exception as e:
            print(
                "[!] Failed to rotate endpoint {0} to certificate {1} reason: {2}".format(
                    endpoint.name,
                    certificate.name,
                    e
                )
            )

    metrics.send('endpoint_rotation', 'counter', 1, metric_tags={'status': status})
Beispiel #10
0
def request_reissue(certificate, commit):
    """
    Reissuing certificate and handles any exceptions.
    :param certificate:
    :param commit:
    :return:
    """
    status = FAILURE_METRIC_STATUS
    try:
        print("[+] {0} is eligible for re-issuance".format(certificate.name))

        # set the lemur identity for all cli commands
        identity_changed.send(current_app._get_current_object(), identity=Identity(1))

        details = get_certificate_primitives(certificate)
        print_certificate_details(details)

        if commit:
            new_cert = reissue_certificate(certificate, replace=True)
            print("[+] New certificate named: {0}".format(new_cert.name))

        status = SUCCESS_METRIC_STATUS

    except Exception as e:
        sentry.captureException()
        current_app.logger.exception("Error reissuing certificate.", exc_info=True)
        print(
            "[!] Failed to reissue certificates. Reason: {}".format(
                e
            )
        )

    metrics.send('certificate_reissue', 'counter', 1, metric_tags={'status': status})
Beispiel #11
0
def expirations(exclude):
    """
    Runs Lemur's notification engine, that looks for expired certificates and sends
    notifications out to those that have subscribed to them.

    Every certificate receives notifications by default. When expiration notifications are handled outside of Lemur
    we exclude their names (or matching) from expiration notifications.

    It performs simple subset matching and is case insensitive.

    :return:
    """
    status = FAILURE_METRIC_STATUS
    try:
        print("Starting to notify subscribers about expiring certificates!")
        success, failed = send_expiration_notifications(exclude)
        print(
            "Finished notifying subscribers about expiring certificates! Sent: {success} Failed: {failed}".format(
                success=success,
                failed=failed
            )
        )
        status = SUCCESS_METRIC_STATUS
    except Exception as e:
        sentry.captureException()

    metrics.send('expiration_notification_job', 'counter', 1, metric_tags={'status': status})
Beispiel #12
0
def send_rotation_notification(certificate, notification_plugin=None):
    """
    Sends a report to certificate owners when their certificate has been
    rotated.

    :param certificate:
    :param notification_plugin:
    :return:
    """
    status = FAILURE_METRIC_STATUS
    if not notification_plugin:
        notification_plugin = plugins.get(current_app.config.get('LEMUR_DEFAULT_NOTIFICATION_PLUGIN'))

    data = certificate_notification_output_schema.dump(certificate).data

    try:
        notification_plugin.send('rotation', data, [data['owner']])
        status = SUCCESS_METRIC_STATUS
    except Exception as e:
        current_app.logger.error('Unable to send notification to {}.'.format(data['owner']), exc_info=True)
        sentry.captureException()

    metrics.send('notification', 'counter', 1, metric_tags={'status': status, 'event_type': 'rotation'})

    if status == SUCCESS_METRIC_STATUS:
        return True
Beispiel #13
0
    def create_certificate(self, csr, issuer_options):
        """
        Creates a CFSSL certificate.

        :param csr:
        :param issuer_options:
        :return:
        """
        current_app.logger.info("Requesting a new cfssl certificate with csr: {0}".format(csr))

        url = "{0}{1}".format(current_app.config.get('CFSSL_URL'), '/api/v1/cfssl/sign')

        data = {'certificate_request': csr}
        data = json.dumps(data)

        response = self.session.post(url, data=data.encode(encoding='utf_8', errors='strict'))
        if response.status_code > 399:
            metrics.send('cfssl_create_certificate_failure', 'counter', 1)
            raise Exception(
                "Error creating cert. Please check your CFSSL API server")
        response_json = json.loads(response.content.decode('utf_8'))
        cert = response_json['result']['certificate']
        parsed_cert = parse_certificate(cert)
        metrics.send('cfssl_create_certificate_success', 'counter', 1)
        return cert, current_app.config.get('CFSSL_INTERMEDIATE'), parsed_cert.serial_number
Beispiel #14
0
def create(**kwargs):
    """
    Creates a new authority.
    """
    kwargs['creator'] = g.user.email
    body, private_key, chain, roles = mint(**kwargs)

    g.user.roles = list(set(list(g.user.roles) + roles))

    kwargs['body'] = body
    kwargs['private_key'] = private_key
    kwargs['chain'] = chain

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

    cert = upload(**kwargs)
    kwargs['authority_certificate'] = cert

    authority = Authority(**kwargs)
    authority = database.create(authority)
    g.user.authorities.append(authority)

    metrics.send('authority_created', 'counter', 1, metric_tags=dict(owner=authority.owner))
    return authority
Beispiel #15
0
    def revoke_certificate(self, certificate, comments):
        """Revoke a Digicert certificate."""
        base_url = current_app.config.get('DIGICERT_URL')

        # make certificate revoke request
        create_url = '{0}/services/v2/certificate/{1}/revoke'.format(base_url, certificate.external_id)
        metrics.send('digicert_revoke_certificate', 'counter', 1)
        response = self.session.put(create_url, data=json.dumps({'comments': comments}))
        return handle_response(response)
Beispiel #16
0
    def post(self):
        """
        .. http:post:: /auth/login

           Login with username:password

           **Example request**:

           .. sourcecode:: http

              POST /auth/login HTTP/1.1
              Host: example.com
              Accept: application/json, text/javascript

              {
                "username": "******",
                "password": "******"
              }

           **Example response**:

           .. sourcecode:: http

              HTTP/1.1 200 OK
              Vary: Accept
              Content-Type: text/javascript

              {
                "token": "12343243243"
              }

           :arg username: username
           :arg password: password
           :statuscode 401: invalid credentials
           :statuscode 200: no error
        """
        self.reqparse.add_argument('username', type=str, required=True, location='json')
        self.reqparse.add_argument('password', type=str, required=True, location='json')

        args = self.reqparse.parse_args()

        if '@' in args['username']:
            user = user_service.get_by_email(args['username'])
        else:
            user = user_service.get_by_username(args['username'])

        if user and user.check_password(args['password']):
            # Tell Flask-Principal the identity changed
            identity_changed.send(current_app._get_current_object(),
                                  identity=Identity(user.id))

            metrics.send('successful_login', 'counter', 1)
            return dict(token=create_token(user))

        metrics.send('invalid_login', 'counter', 1)
        return dict(message='The supplied credentials are invalid'), 401
Beispiel #17
0
def create(**kwargs):
    """
    Creates a new endpoint.
    :param kwargs:
    :return:
    """
    endpoint = Endpoint(**kwargs)
    database.create(endpoint)
    metrics.send('endpoint_added', 'counter', 1)
    return endpoint
Beispiel #18
0
def create(**kwargs):
    """
    Creates a new endpoint.
    :param kwargs:
    :return:
    """
    endpoint = Endpoint(**kwargs)
    database.create(endpoint)
    metrics.send('endpoint_added', 'counter', 1, metric_tags={'source': endpoint.source.label})
    return endpoint
Beispiel #19
0
def publish_unapproved_verisign_certificates():
    """
    Query the Verisign for any certificates that need to be approved.
    :return:
    """
    from lemur.plugins import plugins
    from lemur.extensions import metrics
    v = plugins.get('verisign-issuer')
    certs = v.get_pending_certificates()
    metrics.send('pending_certificates', 'gauge', certs)
Beispiel #20
0
def log_status_code(r, *args, **kwargs):
    """
    Is a request hook that logs all status codes to the digicert api.

    :param r:
    :param args:
    :param kwargs:
    :return:
    """
    metrics.send('digicert_status_code_{}'.format(r.status_code), 'counter', 1)
Beispiel #21
0
def update(endpoint_id, **kwargs):
    endpoint = database.get(Endpoint, endpoint_id)

    endpoint.policy = kwargs['policy']
    endpoint.certificate = kwargs['certificate']
    endpoint.source = kwargs['source']
    endpoint.last_updated = arrow.utcnow()
    metrics.send('endpoint_updated', 'counter', 1, metric_tags={'source': endpoint.source.label})
    database.update(endpoint)
    return endpoint
Beispiel #22
0
def retry_throttled(exception):
    """
    Determines if this exception is due to throttling
    :param exception:
    :return:
    """
    if isinstance(exception, botocore.exceptions.ClientError):
        if exception.response['Error']['Code'] == 'NoSuchEntity':
            return False

    metrics.send('iam_retry', 'counter', 1)
    return True
Beispiel #23
0
def get_elbs(**kwargs):
    """
    Fetches one page elb objects for a given account and region.
    """
    try:
        client = kwargs.pop('client')
        return client.describe_load_balancers(**kwargs)
    except Exception as e:  # noqa
        metrics.send('get_elbs_error', 'counter', 1,
                     metric_tags={"error": e})
        sentry.captureException()
        raise
Beispiel #24
0
def request_reissue(certificate, commit):
    """
    Reissuing certificate and handles any exceptions.
    :param certificate:
    :param commit:
    :return:
    """
    details = get_certificate_primitives(certificate)
    print_certificate_details(details)

    if commit:
        new_cert = reissue_certificate(certificate, replace=True)
        metrics.send('certificate_reissue_success', 'counter', 1)
        print("[+] New certificate named: {0}".format(new_cert.name))
Beispiel #25
0
def describe_ssl_policies_v2(policy_names, **kwargs):
    """
    Fetching all policies currently associated with an ELB.

    :param policy_names:
    :return:
    """
    try:
        return kwargs['client'].describe_ssl_policies(Names=policy_names)
    except Exception as e:  # noqa
        metrics.send('describe_ssl_policies_v2_error', 'counter', 1,
                     metric_tags={"policy_names": policy_names, "error": e})
        sentry.captureException(extra={"policy_names": str(policy_names)})
        raise
Beispiel #26
0
def _disassociate_endpoints_from_source(found_endpoints, source_label):
    current_endpoints = endpoint_service.get_by_source(source_label=source_label)

    for ce in current_endpoints:
        for fe in found_endpoints:
            if ce.dnsname == fe['dnsname']:
                break
        else:
            current_app.logger.info(
                "Endpoint {dnsname} was not found during sync, removing from inventory.".format(
                    dnsname=ce.dnsname
                )
            )
            metrics.send('endpoint_removed', 'counter', 1)
            database.delete(ce)
Beispiel #27
0
def retry_throttled(exception):
    """
    Determines if this exception is due to throttling
    :param exception:
    :return:
    """
    if isinstance(exception, botocore.exceptions.ClientError):
        if exception.response['Error']['Code'] == 'LoadBalancerNotFound':
            return False

        if exception.response['Error']['Code'] == 'CertificateNotFound':
            return False

    metrics.send('elb_retry', 'counter', 1)
    return True
Beispiel #28
0
def describe_listeners_v2(**kwargs):
    """
    Fetches one page of listener objects for a given elb arn.

    :param kwargs:
    :return:
    """
    try:
        client = kwargs.pop('client')
        return client.describe_listeners(**kwargs)
    except Exception as e:  # noqa
        metrics.send('describe_listeners_v2_error', 'counter', 1,
                     metric_tags={"error": e})
        sentry.captureException()
        raise
Beispiel #29
0
 def revoke_certificate(self, certificate, comments):
     """Revoke a CFSSL certificate."""
     base_url = current_app.config.get('CFSSL_URL')
     create_url = '{0}/api/v1/cfssl/revoke'.format(base_url)
     data = '{"serial": "' + certificate.external_id + '","authority_key_id": "' + \
         get_authority_key(certificate.body) + \
         '", "reason": "superseded"}'
     current_app.logger.debug("Revoking cert: {0}".format(data))
     response = self.session.post(
         create_url, data=data.encode(encoding='utf_8', errors='strict'))
     if response.status_code > 399:
         metrics.send('cfssl_revoke_certificate_failure', 'counter', 1)
         raise Exception(
             "Error revoking cert. Please check your CFSSL API server")
     metrics.send('cfssl_revoke_certificate_success', 'counter', 1)
     return response.json()
Beispiel #30
0
def create(**kwargs):
    """
    Creates a new certificate.
    """
    try:
        cert_body, private_key, cert_chain, external_id, csr = mint(**kwargs)
    except Exception:
        current_app.logger.error("Exception minting certificate", exc_info=True)
        sentry.captureException()
        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:
        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))

    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
Beispiel #31
0
 def revoke_certificate(self, certificate, comments):
     """Revoke a CFSSL certificate."""
     base_url = current_app.config.get('CFSSL_URL')
     create_url = '{0}/api/v1/cfssl/revoke'.format(base_url)
     data = '{"serial": "' + certificate.external_id + '","authority_key_id": "' + \
         get_authority_key(certificate.body) + \
         '", "reason": "superseded"}'
     current_app.logger.debug("Revoking cert: {0}".format(data))
     response = self.session.post(create_url,
                                  data=data.encode(encoding='utf_8',
                                                   errors='strict'))
     if response.status_code > 399:
         metrics.send('cfssl_revoke_certificate_failure', 'counter', 1)
         raise Exception(
             "Error revoking cert. Please check your CFSSL API server")
     metrics.send('cfssl_revoke_certificate_success', 'counter', 1)
     return response.json()
Beispiel #32
0
    def cleanup_dns_challenges(self, acme_client, authorizations):
        """
        Best effort attempt to delete DNS challenges that may not have been deleted previously. This is usually called
        on an exception

        :param acme_client:
        :param account_number:
        :param dns_provider:
        :param authorizations:
        :param dns_provider_options:
        :return:
        """
        for authz_record in authorizations:
            dns_providers = self.dns_providers_for_domain.get(
                authz_record.target_domain)
            for dns_provider in dns_providers:
                # Grab account number (For Route53)
                dns_provider_options = json.loads(dns_provider.credentials)
                account_number = dns_provider_options.get("account_id")
                dns_challenges = authz_record.dns_challenge
                host_to_validate, _ = self.strip_wildcard(
                    authz_record.target_domain)
                host_to_validate = self.maybe_add_extension(
                    host_to_validate, dns_provider_options)

                dns_provider_plugin = self.get_dns_provider(
                    dns_provider.provider_type)
                for dns_challenge in dns_challenges:
                    if not authz_record.cname_delegation:
                        host_to_validate = dns_challenge.validation_domain_name(
                            host_to_validate)
                    try:
                        dns_provider_plugin.delete_txt_record(
                            authz_record.change_id,
                            account_number,
                            host_to_validate,
                            dns_challenge.validation(
                                acme_client.client.net.key),
                        )
                    except Exception as e:
                        # If this fails, it's most likely because the record doesn't exist (It was already cleaned up)
                        # or we're not authorized to modify it.
                        metrics.send("cleanup_dns_challenges_error", "counter",
                                     1)
                        capture_exception()
                        pass
Beispiel #33
0
def create(**kwargs):
    """
    Creates a new endpoint.
    :param kwargs:
    :return:
    """
    aliases = []
    if "aliases" in kwargs:
        aliases = [EndpointDnsAlias(alias=name) for name in kwargs.pop("aliases")]
    endpoint = Endpoint(**kwargs)
    endpoint.aliases = aliases
    database.create(endpoint)
    metrics.send(
        "endpoint_added", "counter", 1,
        metric_tags={"source": endpoint.source.label, "type": endpoint.type}
    )
    return endpoint
Beispiel #34
0
def request_reissue(certificate, commit):
    """
    Reissuing certificate and handles any exceptions.
    :param certificate:
    :param commit:
    :return:
    """
    # set the lemur identity for all cli commands
    identity_changed.send(current_app._get_current_object(), identity=Identity(1))

    details = get_certificate_primitives(certificate)
    print_certificate_details(details)

    if commit:
        new_cert = reissue_certificate(certificate, replace=True)
        metrics.send('certificate_reissue_success', 'counter', 1)
        print("[+] New certificate named: {0}".format(new_cert.name))
Beispiel #35
0
def wait_for_dns_change(change_id, account_number=None):
    """
    Checks the authoritative DNS Server to see if changes have propagated to DNS
    Retries and waits until successful.
    """
    _check_conf()
    domain, token = change_id
    number_of_attempts = current_app.config.get("ACME_POWERDNS_RETRIES", 3)
    zone_name = _get_zone_name(domain, account_number)
    nameserver = dnsutil.get_authoritative_nameserver(zone_name)
    record_found = False
    for attempts in range(0, number_of_attempts):
        txt_records = dnsutil.get_dns_records(domain, "TXT", nameserver)
        for txt_record in txt_records:
            if txt_record == token:
                record_found = True
                break
        if record_found:
            break
        time.sleep(10)

    function = sys._getframe().f_code.co_name
    log_data = {
        "function": function,
        "fqdn": domain,
        "status": record_found,
        "message": "Record status on PowerDNS authoritative server"
    }
    current_app.logger.debug(log_data)

    if record_found:
        metrics.send(f"{function}.success",
                     "counter",
                     1,
                     metric_tags={
                         "fqdn": domain,
                         "txt_record": token
                     })
    else:
        metrics.send(f"{function}.fail",
                     "counter",
                     1,
                     metric_tags={
                         "fqdn": domain,
                         "txt_record": token
                     })
Beispiel #36
0
def _get_zone_name(domain, account_number):
    """Get most specific matching zone for the given domain and return as a String"""
    zones = get_zones(account_number)
    zone_name = ""
    for z in zones:
        if domain.endswith(z):
            if z.count(".") > zone_name.count("."):
                zone_name = z
    if not zone_name:
        function = sys._getframe().f_code.co_name
        log_data = {
            "function": function,
            "fqdn": domain,
            "message": "No PowerDNS zone name found.",
        }
        metrics.send(f"{function}.fail", "counter", 1)
    return zone_name
Beispiel #37
0
def log_status_code(r, *args, **kwargs):
    """
    Is a request hook that logs all status codes to the ENTRUST api.

    :param r:
    :param args:
    :param kwargs:
    :return:
    """
    if r.status_code != 200:
        log_data = {
            "reason": (r.reason if r.reason else ""),
            "status_code": r.status_code,
            "url": (r.url if r.url else ""),
        }
        metrics.send(f"entrust_status_code_{r.status_code}", "counter", 1)
        current_app.logger.info(log_data)
Beispiel #38
0
def delete_cert(cert_name, **kwargs):
    """
    Delete a certificate from AWS

    :param cert_name:
    :return:
    """
    client = kwargs.pop("client")
    metrics.send("delete_cert",
                 "counter",
                 1,
                 metric_tags={"cert_name": cert_name})
    try:
        client.delete_server_certificate(ServerCertificateName=cert_name)
    except botocore.exceptions.ClientError as e:
        if e.response["Error"]["Code"] != "NoSuchEntity":
            raise e
Beispiel #39
0
    def get_authorizations(self, acme_client, order, order_info):
        """ The list can be empty if all hostname validations are still valid"""
        authorizations = []

        for domain in order_info.domains:

            # If CNAME exists, set host to the target address
            target_domain = domain
            if current_app.config.get("ACME_ENABLE_DELEGATED_CNAME", False):
                cname_result, _ = self.strip_wildcard(domain)
                cname_result = challenges.DNS01().validation_domain_name(
                    cname_result)
                cname_result = self.get_cname(cname_result)
                if cname_result:
                    target_domain = cname_result
                    self.autodetect_dns_providers(target_domain)
                    metrics.send(
                        "get_authorizations_cname_delegation_for_domain",
                        "counter",
                        1,
                        metric_tags={"domain": domain})

            if not self.dns_providers_for_domain.get(target_domain):
                metrics.send("get_authorizations_no_dns_provider_for_domain",
                             "counter", 1)
                raise Exception("No DNS providers found for domain: {}".format(
                    target_domain))

            for dns_provider in self.dns_providers_for_domain[target_domain]:
                dns_provider_plugin = self.get_dns_provider(
                    dns_provider.provider_type)
                dns_provider_options = json.loads(dns_provider.credentials)
                account_number = dns_provider_options.get("account_id")
                authz_record = self.start_dns_challenge(
                    acme_client,
                    account_number,
                    domain,
                    target_domain,
                    dns_provider_plugin,
                    order,
                    dns_provider.options,
                )
                # it can be null, if hostname is still valid
                if authz_record:
                    authorizations.append(authz_record)
        return authorizations
Beispiel #40
0
    def post(self):
        self.reqparse.add_argument("clientId", type=str, required=True, location="json")
        self.reqparse.add_argument(
            "redirectUri", type=str, required=True, location="json"
        )
        self.reqparse.add_argument("code", type=str, required=True, location="json")

        args = self.reqparse.parse_args()

        # you can either discover these dynamically or simply configure them
        access_token_url = current_app.config.get("PING_ACCESS_TOKEN_URL")
        user_api_url = current_app.config.get("PING_USER_API_URL")

        secret = current_app.config.get("PING_SECRET")

        id_token, access_token = exchange_for_access_token(
            args["code"],
            args["redirectUri"],
            args["clientId"],
            secret,
            access_token_url=access_token_url,
        )

        jwks_url = current_app.config.get("PING_JWKS_URL")
        error_code = validate_id_token(id_token, args["clientId"], jwks_url)
        if error_code:
            return error_code
        user, profile = retrieve_user(user_api_url, access_token)
        roles = create_user_roles(profile)
        update_user(user, profile, roles)

        if not user or not user.active:
            metrics.send(
                "login", "counter", 1, metric_tags={"status": FAILURE_METRIC_STATUS}
            )
            return dict(message="The supplied credentials are invalid"), 403

        # Tell Flask-Principal the identity changed
        identity_changed.send(
            current_app._get_current_object(), identity=Identity(user.id)
        )

        metrics.send(
            "login", "counter", 1, metric_tags={"status": SUCCESS_METRIC_STATUS}
        )
        return dict(token=create_token(user))
Beispiel #41
0
def send_plugin_notification(event_type, data, recipients, notification):
    """
    Executes the plugin and handles failure.

    :param event_type:
    :param data:
    :param recipients:
    :param notification:
    :return:
    """
    function = f"{__name__}.{sys._getframe().f_code.co_name}"
    log_data = {
        "function": function,
        "message":
        f"Sending {event_type} notification for to recipients {recipients}",
        "notification_type": event_type,
        "notification_plugin": notification.plugin.slug,
        "certificate_targets": recipients,
        "plugin": notification.plugin.slug,
        "notification_id": notification.id,
    }
    status = FAILURE_METRIC_STATUS
    try:
        current_app.logger.debug(log_data)
        notification.plugin.send(event_type, data, recipients,
                                 notification.options)
        status = SUCCESS_METRIC_STATUS
    except Exception as e:
        log_data[
            "message"] = f"Unable to send {event_type} notification to recipients {recipients}"
        current_app.logger.error(log_data, exc_info=True)
        capture_exception()

    metrics.send(
        "notification",
        "counter",
        1,
        metric_tags={
            "status": status,
            "event_type": event_type,
            "plugin": notification.plugin.slug
        },
    )

    if status == SUCCESS_METRIC_STATUS:
        return True
Beispiel #42
0
def report_revoked_task(**kwargs):
    """
    Report a generic failure metric as tasks to our metrics broker every time a task is revoked.
    This metric can be used for alerting.
    https://docs.celeryproject.org/en/latest/userguide/signals.html#task-revoked
    """
    with flask_app.app_context():
        log_data = {
            "function": f"{__name__}.{sys._getframe().f_code.co_name}",
            "Message": "Celery Task Revoked",
        }

        error_tags = get_celery_request_tags(**kwargs)

        log_data.update(error_tags)
        current_app.logger.error(log_data)
        metrics.send("celery.revoked_task", "TIMER", 1, metric_tags=error_tags)
Beispiel #43
0
def sync_all_sources():
    """
    This function will sync certificates from all sources. This function triggers one celery task per source.
    """
    function = f"{__name__}.{sys._getframe().f_code.co_name}"
    log_data = {
        "function": function,
        "message": "creating celery task to sync source",
    }
    sources = validate_sources("all")
    for source in sources:
        log_data["source"] = source.label
        current_app.logger.debug(log_data)
        sync_source.delay(source.label)

    red.set(f'{function}.last_success', int(time.time()))
    metrics.send(f"{function}.success", 'counter', 1)
Beispiel #44
0
def describe_load_balancer_policies(load_balancer_name, policy_names, **kwargs):
    """
    Fetching all policies currently associated with an ELB.

    :param load_balancer_name:
    :return:
    """

    try:
        return kwargs['client'].describe_load_balancer_policies(LoadBalancerName=load_balancer_name,
                                                                PolicyNames=policy_names)
    except Exception as e:  # noqa
        metrics.send('describe_load_balancer_policies_error', 'counter', 1,
                     metric_tags={"load_balancer_name": load_balancer_name, "policy_names": policy_names, "error": e})
        sentry.captureException(extra={"load_balancer_name": str(load_balancer_name),
                                       "policy_names": str(policy_names)})
        raise
Beispiel #45
0
def _get_elbs_v2(**kwargs):
    """
    Fetches one page of elb objects for a given account and region.

    :param kwargs:
    :return:
    """
    try:
        client = kwargs.pop("client")
        return client.describe_load_balancers(**kwargs)
    except Exception as e:  # noqa
        metrics.send("get_elbs_v2_error",
                     "counter",
                     1,
                     metric_tags={"error": str(e)})
        capture_exception()
        raise
Beispiel #46
0
def update_destinations(target, value, initiator):
    """
    Attempt to upload certificate to the new destination

    :param target:
    :param value:
    :param initiator:
    :return:
    """
    destination_plugin = plugins.get(value.plugin_name)

    try:
        if target.private_key:
            destination_plugin.upload(target.name, target.body, target.private_key, target.chain, value.options)
    except Exception as e:
        current_app.logger.exception(e)
        metrics.send('destination_upload_failure', 'counter', 1, metric_tags={'certificate': target.name, 'destination': value.label})
Beispiel #47
0
def describe_listeners_v2(**kwargs):
    """
    Fetches one page of listener objects for a given elb arn.

    :param kwargs:
    :return:
    """
    try:
        client = kwargs.pop("client")
        return client.describe_listeners(**kwargs)
    except Exception as e:  # noqa
        metrics.send("describe_listeners_v2_error",
                     "counter",
                     1,
                     metric_tags={"error": str(e)})
        capture_exception()
        raise
Beispiel #48
0
    def get_ordered_certificate(self, pending_cert):
        self.acme = AcmeDnsHandler()
        acme_client, registration = self.acme.setup_acme_client(pending_cert.authority)
        order_info = authorization_service.get(pending_cert.external_id)
        if pending_cert.dns_provider_id:
            dns_provider = dns_provider_service.get(pending_cert.dns_provider_id)

            for domain in order_info.domains:
                # Currently, we only support specifying one DNS provider per certificate, even if that
                # certificate has multiple SANs that may belong to different providers.
                self.acme.dns_providers_for_domain[domain] = [dns_provider]
        else:
            for domain in order_info.domains:
                self.acme.autodetect_dns_providers(domain)

        try:
            order = acme_client.new_order(pending_cert.csr)
        except WildcardUnsupportedError:
            metrics.send("get_ordered_certificate_wildcard_unsupported", "counter", 1)
            raise Exception(
                "The currently selected ACME CA endpoint does"
                " not support issuing wildcard certificates."
            )
        try:
            authorizations = self.acme.get_authorizations(
                acme_client, order, order_info
            )
        except ClientError:
            capture_exception()
            metrics.send("get_ordered_certificate_error", "counter", 1)
            current_app.logger.error(
                f"Unable to resolve pending cert: {pending_cert.name}", exc_info=True
            )
            return False

        authorizations = self.acme.finalize_authorizations(acme_client, authorizations)
        pem_certificate, pem_certificate_chain = self.acme.request_certificate(
            acme_client, authorizations, order
        )
        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
Beispiel #49
0
def certificate_rotate(**kwargs):
    """
    This celery task rotates certificates which are reissued but having endpoints attached to the replaced cert
    :return:
    """
    function = f"{__name__}.{sys._getframe().f_code.co_name}"
    task_id = None
    if celery.current_task:
        task_id = celery.current_task.request.id

    region = kwargs.get("region")
    log_data = {
        "function": function,
        "message": "rotating certificates",
        "task_id": task_id,
    }

    if task_id and is_task_active(function, task_id, None):
        log_data["message"] = "Skipping task: Task is already active"
        current_app.logger.debug(log_data)
        return

    current_app.logger.debug(log_data)
    try:
        notify = current_app.config.get("ENABLE_ROTATION_NOTIFICATION", None)
        if region:
            log_data["region"] = region
            cli_certificate.rotate_region(None, None, None, notify, True,
                                          region)
        else:
            cli_certificate.rotate(None, None, None, notify, True)
    except SoftTimeLimitExceeded:
        log_data["message"] = "Certificate rotate: Time limit exceeded."
        current_app.logger.error(log_data)
        capture_exception()
        metrics.send("celery.timeout",
                     "counter",
                     1,
                     metric_tags={"function": function})
        return

    log_data["message"] = "rotation completed"
    current_app.logger.debug(log_data)
    metrics.send(f"{function}.success", "counter", 1)
    return log_data
Beispiel #50
0
def upload_cert(name, body, private_key, path, cert_chain=None, **kwargs):
    """
    Upload a certificate to AWS

    :param name:
    :param body:
    :param private_key:
    :param cert_chain:
    :param path:
    :return:
    """
    assert isinstance(private_key, str)
    client = kwargs.pop("client")

    if not path or path == "/":
        path = "/"
    else:
        name = name + "-" + path.strip("/")

    metrics.send("upload_cert",
                 "counter",
                 1,
                 metric_tags={
                     "name": name,
                     "path": path
                 })
    try:
        if cert_chain:
            return client.upload_server_certificate(
                Path=path,
                ServerCertificateName=name,
                CertificateBody=str(body),
                PrivateKey=str(private_key),
                CertificateChain=str(cert_chain),
            )
        else:
            return client.upload_server_certificate(
                Path=path,
                ServerCertificateName=name,
                CertificateBody=str(body),
                PrivateKey=str(private_key),
            )
    except botocore.exceptions.ClientError as e:
        if e.response["Error"]["Code"] != "EntityAlreadyExists":
            raise e
Beispiel #51
0
def clean_unused_and_issued_since_days(source_strings, days_since_issuance,
                                       commit):
    sources = validate_sources(source_strings)
    for source in sources:
        s = plugins.get(source.plugin_name)

        if not hasattr(s, "clean"):
            info_text = f"Cannot clean source: {source.label}, source plugin does not implement 'clean()'"
            current_app.logger.warning(info_text)
            print(info_text)
            continue

        start_time = time.time()

        print("[+] Staring to clean source: {label}!\n".format(
            label=source.label))

        cleaned = 0
        certificates = certificate_service.get_all_pending_cleaning_issued_since_days(
            source, days_since_issuance)
        for certificate in certificates:
            status = FAILURE_METRIC_STATUS
            if commit:
                status = execute_clean(s, certificate, source)

            metrics.send(
                "certificate_clean",
                "counter",
                1,
                metric_tags={
                    "status": status,
                    "source": source.label,
                    "certificate": certificate.name
                },
            )
            current_app.logger.warning(
                f"Removed {certificate.name} from source {source.label} during cleaning"
            )
            cleaned += 1

        info_text = f"[+] Finished cleaning source: {source.label}. " \
                    f"Removed {cleaned} certificates from source. " \
                    f"Run Time: {(time.time() - start_time)}\n"
        print(info_text)
        current_app.logger.warning(info_text)
Beispiel #52
0
def sync_source(source):
    """
    This celery task will sync the specified source.

    :param source:
    :return:
    """

    function = f"{__name__}.{sys._getframe().f_code.co_name}"
    task_id = None
    if celery.current_task:
        task_id = celery.current_task.request.id

    log_data = {
        "function": function,
        "message": "Syncing source",
        "source": source,
        "task_id": task_id,
    }

    if task_id and is_task_active(function, task_id, (source, )):
        log_data["message"] = "Skipping task: Task is already active"
        current_app.logger.debug(log_data)
        return

    current_app.logger.debug(log_data)
    try:
        sync([source],
             current_app.config.get("CELERY_ENDPOINTS_EXPIRE_TIME_IN_HOURS",
                                    2))
        metrics.send(f"{function}.success",
                     "counter",
                     1,
                     metric_tags={"source": source})
    except SoftTimeLimitExceeded:
        log_data["message"] = "Error syncing source: Time limit exceeded."
        current_app.logger.error(log_data)
        capture_exception()
        metrics.send("sync_source_timeout",
                     "counter",
                     1,
                     metric_tags={"source": source})
        metrics.send("celery.timeout",
                     "counter",
                     1,
                     metric_tags={"function": function})
        return

    log_data["message"] = "Done syncing source"
    current_app.logger.debug(log_data)
    metrics.send(f"{function}.success",
                 "counter",
                 1,
                 metric_tags={"source": source})
    return log_data
Beispiel #53
0
def create(**kwargs):
    """
    Creates a new authority.
    """
    ca_name = kwargs.get("name")
    if get_by_name(ca_name):
        raise Exception(f"Authority with name {ca_name} already exists")
    if role_service.get_by_name(f"{ca_name}_admin") or role_service.get_by_name(f"{ca_name}_operator"):
        raise Exception(f"Admin and/or operator roles for authority {ca_name} already exist")

    body, private_key, chain, roles = mint(**kwargs)

    kwargs["creator"].roles = list(set(list(kwargs["creator"].roles) + roles))

    kwargs["body"] = body
    kwargs["private_key"] = private_key
    kwargs["chain"] = chain

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

    cert = upload(**kwargs)
    kwargs["authority_certificate"] = cert
    if kwargs.get("plugin", {}).get("plugin_options", []):
        # encrypt the private key before persisting in DB
        for option in kwargs.get("plugin").get("plugin_options"):
            if option["name"] == "acme_private_key" and option["value"]:
                option["value"] = data_encrypt(option["value"])
        kwargs["options"] = json.dumps(kwargs["plugin"]["plugin_options"])

    authority = Authority(**kwargs)
    authority = database.create(authority)
    kwargs["creator"].authorities.append(authority)

    log_service.audit_log("create_authority", ca_name, "Created new authority")

    issuer = kwargs["plugin"]["plugin_object"]
    current_app.logger.warning(f"Created new authority {ca_name} with issuer {issuer.title}")

    metrics.send(
        "authority_created", "counter", 1, metric_tags=dict(owner=authority.owner)
    )
    return authority
Beispiel #54
0
def clean_all_sources():
    """
    This function will clean unused certificates from sources. This is a destructive operation and should only
    be ran periodically. This function triggers one celery task per source.
    """
    function = f"{__name__}.{sys._getframe().f_code.co_name}"
    log_data = {
        "function": function,
        "message": "Creating celery task to clean source",
    }
    sources = validate_sources("all")
    for source in sources:
        log_data["source"] = source.label
        current_app.logger.debug(log_data)
        clean_source.delay(source.label)

    red.set(f'{function}.last_success', int(time.time()))
    metrics.send(f"{function}.success", 'counter', 1)
Beispiel #55
0
def worker(data, commit, reason):
    parts = [x for x in data.split(' ') if x]
    try:
        cert = get(int(parts[0].strip()))
        plugin = plugins.get(cert.authority.plugin_name)

        print('[+] Revoking certificate. Id: {0} Name: {1}'.format(cert.id, cert.name))
        if commit:
            plugin.revoke_certificate(cert, reason)

    except Exception as e:
        sentry.captureException()
        metrics.send('certificate_revoke_failure', 'counter', 1)
        print(
            "[!] Failed to revoke certificates. Reason: {}".format(
                e
            )
        )
Beispiel #56
0
    def revoke_certificate(self, certificate, comments):
        """Revoke a Vault certificate."""
        url = '{}/revoke'.format(current_app.config.get('VAULT_PKI_URL'))
        serialNum = format(int(certificate.serial), 'x')
        current_app.logger.info('*********** serialNum: ' + serialNum)
        serialId = '-'.join(serialNum[i:i + 2]
                            for i in range(0, len(serialNum), 2))
        current_app.logger.info('*********** serialId: ' + serialId)
        params = process_serial_data(serialId)
        res, resp = vault_write_request(url, params)

        if res:
            current_app.logger.info('Vaule PKI Revoked successfully.')
        else:
            current_app.logger.info('Vaule PKI Failed to Revoke.')
            raise Exception('Vault error' + resp)
        metrics.send("vault_revoke_certificate_success", "counter", 1)
        return resp.json()
Beispiel #57
0
def describe_ssl_policies_v2(policy_names, **kwargs):
    """
    Fetching all policies currently associated with an ELB.

    :param policy_names:
    :return:
    """
    try:
        return kwargs["client"].describe_ssl_policies(Names=policy_names)
    except Exception as e:  # noqa
        metrics.send(
            "describe_ssl_policies_v2_error",
            "counter",
            1,
            metric_tags={"policy_names": policy_names, "error": str(e)},
        )
        capture_exception(extra={"policy_names": str(policy_names)})
        raise
Beispiel #58
0
def retry_throttled(exception):
    """
    Determines if this exception is due to throttling
    :param exception:
    :return:
    """
    if isinstance(exception, botocore.exceptions.ClientError):
        if exception.response["Error"]["Code"] == "NoSuchEntity":
            return False

        # No need to retry deletion requests if there is a DeleteConflict error.
        # This error indicates that the certificate is still attached to an entity
        # and cannot be deleted.
        if exception.response["Error"]["Code"] == "DeleteConflict":
            return False

    metrics.send("iam_retry", "counter", 1, metric_tags={"exception": str(exception)})
    return True
Beispiel #59
0
def sync_source_destination():
    """
    This celery task will sync destination and source, to make sure all new destinations are also present as source.
    Some destinations do not qualify as sources, and hence should be excluded from being added as sources
    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.
    """
    current_app.logger.debug("Syncing AWS destinations and sources")
    function = f"{__name__}.{sys._getframe().f_code.co_name}"

    for dst in destinations_service.get_all():
        if add_aws_destination_to_sources(dst):
            current_app.logger.debug("Source: %s added", dst.label)

    current_app.logger.debug("Completed Syncing AWS destinations and sources")
    red.set(f'{function}.last_success', int(time.time()))
    metrics.send(f"{function}.success", 'counter', 1)
Beispiel #60
0
def automatically_enable_autorotate():
    """
    This function automatically enables auto-rotation for unexpired certificates that are
    attached to an endpoint but do not have autorotate enabled.

    WARNING: This will overwrite the Auto-rotate toggle!
    """
    log_data = {
        "function": f"{__name__}.{sys._getframe().f_code.co_name}",
        "message": "Enabling auto-rotate for certificate"
    }

    permitted_authorities = current_app.config.get(
        "ENABLE_AUTO_ROTATE_AUTHORITY", [])

    eligible_certs = get_all_certs_attached_to_endpoint_without_autorotate()
    for cert in eligible_certs:

        if cert.authority_id not in permitted_authorities:
            continue

        log_data["certificate"] = cert.name
        log_data["certificate_id"] = cert.id
        log_data["authority_id"] = cert.authority_id
        log_data["authority_name"] = authorities_get_by_id(
            cert.authority_id).name
        if cert.destinations:
            log_data["destination_names"] = ', '.join(
                [d.label for d in cert.destinations])
        else:
            log_data["destination_names"] = "NONE"
        current_app.logger.info(log_data)
        metrics.send("automatically_enable_autorotate",
                     "counter",
                     1,
                     metric_tags={
                         "certificate": log_data["certificate"],
                         "certificate_id": log_data["certificate_id"],
                         "authority_id": log_data["authority_id"],
                         "authority_name": log_data["authority_name"],
                         "destination_names": log_data["destination_names"]
                     })
        cert.rotation = True
        database.update(cert)