Esempio n. 1
0
 def __init__(self, config, key):
     log('You currently use ACMEv1 which is deprecated, consider using ACMEv2 (RFC8555) if at all possible.',
         warning=True)
     AbstractACMEAuthority.__init__(self, config, key)
     self.registered_account = False
     self.ca = config['authority']
     self.agreement = config['authority_tos_agreement']
Esempio n. 2
0
    def __init__(self, config, key):
        AbstractACMEAuthority.__init__(self, config, key)
        # Initialize config vars
        self.ca = config['authority']
        self.tos_agreed = str(
            config.get('authority_tos_agreement')).lower() == 'true'
        contact_email = config.get('authority_contact_email')
        if contact_email is None:
            self.contact = None
        elif isinstance(contact_email, list):
            self.contact = [
                "mailto:{}".format(contact) for contact in contact_email
            ]
        else:
            self.contact = ["mailto:{}".format(contact_email)]

        # Initialize runtime vars
        code, self.directory, _ = self._request_url(self.ca + '/directory')
        if code >= 400 or not self.directory:
            self.directory = {
                "meta": {},
                "newAccount": "{}/acme/new-acct".format(self.ca),
                "newNonce": "{}/acme/new-nonce".format(self.ca),
                "newOrder": "{}/acme/new-order".format(self.ca),
                "revokeCert": "{}/acme/revoke-cert".format(self.ca),
            }
            log("API directory retrieval failed ({}). Guessed necessary values: {}"
                .format(code, self.directory),
                warning=True)
        self.nonce = None
        self.nonce_time = 0

        self.algorithm, jwk = tools.get_key_alg_and_jwk(key)
        self.account_protected = {"alg": self.algorithm, "jwk": jwk}
        self.account_id = None  # will be updated to correct value during account registration
Esempio n. 3
0
 def revoke_crt(self, crt, reason=None):
     payload = {'certificate': tools.bytes_to_base64url(tools.convert_cert_to_der_bytes(crt))}
     if reason:
         payload['reason'] = int(reason)
     code, result, _ = self._request_acme_endpoint("revokeCert", payload)
     if code < 400:
         log("Revocation successful")
     else:
         raise ValueError("Revocation failed: {}".format(result))
Esempio n. 4
0
    def verify_dns_record(self, domain, txtvalue):
        if self.nsupdate_verify and not self.dns_verify_all_ns and not self.nsupdate_verified:
            # Verify master DNS only if we don't do a full NS check and it has not yet been verified
            _, nameserverip = self._determine_zone_and_nameserverip(domain)
            if self._check_txt_record_value(domain, txtvalue, nameserverip, use_tcp=True):
                log('Verified \'{} {} IN TXT "{}"\' on {}'.format(domain, self.dns_ttl, txtvalue, nameserverip))
                self.nsupdate_verified = True
            else:
                # Master DNS verification failed. Return immediately and try again.
                return False

        return DNSChallengeHandler.verify_dns_record(self, domain, txtvalue)
Esempio n. 5
0
 def start_challenge(self, domain, thumbprint, token):
     domain = self._determine_challenge_domain(domain)
     txtvalue = self._determine_txtvalue(thumbprint, token)
     failtime = datetime.now() + timedelta(seconds=self.dns_verify_failtime)
     if self.verify_dns_record(domain, txtvalue):
         return
     else:
         log("Waiting until TXT record '{}' is ready".format(domain))
         while failtime > datetime.now():
             time.sleep(self.dns_verify_interval)
             if self.verify_dns_record(domain, txtvalue):
                 return
         raise ValueError(
             "DNS challenge is not ready after waiting {} seconds".format(
                 self.dns_verify_waittime))
Esempio n. 6
0
def cert_revoke(cert, configs, fallback_authority, reason=None):
    domains = set(tools.get_cert_domains(cert))
    acmeconfig = None
    for config in configs:
        if domains == set(config['domainlist']):
            acmeconfig = config['authority']
            break
    if not acmeconfig:
        acmeconfig = fallback_authority
        log("No matching authority found to revoke {}: {}, using globalconfig/defaults".format(tools.get_cert_cn(cert),
                                                                                               tools.get_cert_domains(
                                                                                                   cert)), warning=True)
    acme = authority(acmeconfig)
    acme.register_account()
    acme.revoke_crt(cert, reason)
Esempio n. 7
0
 def revoke_crt(self, crt, reason=None):
     header = self._prepare_header()
     payload = {
         "resource":
         "revoke-cert",
         "certificate":
         tools.bytes_to_base64url(tools.convert_cert_to_der_bytes(crt))
     }
     if reason:
         payload['reason'] = int(reason)
     code, result = self._send_signed(self.ca + "/acme/revoke-cert", header,
                                      payload)
     if code < 400:
         log("Revocation successful")
     else:
         raise ValueError("Revocation failed: {}".format(result))
Esempio n. 8
0
def authority(settings):
    key = json.dumps(settings, sort_keys=True)
    if key in authorities:
        return authorities[key]
    else:
        acc_file = settings['account_key']
        if os.path.isfile(acc_file):
            log("Reading account key from {}".format(acc_file))
            acc_key = tools.read_pem_file(acc_file, key=True)
        else:
            log("Account key not found at '{0}'. Creating key.".format(acc_file))
            acc_key = tools.new_account_key(acc_file, settings['account_key_algorithm'], settings['account_key_length'])

        authority_module = importlib.import_module("acertmgr.authority.{0}".format(settings["api"]))
        authority_class = getattr(authority_module, "ACMEAuthority")
        authority_obj = authority_class(settings, acc_key)
        authorities[key] = authority_obj
        return authority_obj
Esempio n. 9
0
def cert_put(settings):
    if 'path' not in settings:
        raise ValueError('Deployment settings are missing required element: path')
    if 'format' not in settings:
        raise ValueError('Deployment settings are missing required element: format')

    with io.open(settings['path'], "w+") as crt_fd:
        for fmt in [str.strip(x) for x in settings['format'].split(",")]:
            if fmt == "crt":
                with io.open(settings['cert_file'], "r") as src_fd:
                    crt_fd.write(src_fd.read())
            elif fmt == "key":
                with io.open(settings['key_file'], "r") as src_fd:
                    crt_fd.write(src_fd.read())
            elif fmt == "ca":
                with io.open(settings['ca_file'], "r") as src_fd:
                    crt_fd.write(src_fd.read())
            else:
                log("Ignored unknown deployment format key: {}".format(fmt), warning=True)

    # set owner and group
    if 'user' in settings or 'group' in settings:
        if 'pwd' in sys.modules and 'grp' in sys.modules and hasattr(os, 'chown') and hasattr(os, 'geteuid') and \
                hasattr(os, 'getegid'):
            try:
                uid = pwd.getpwnam(settings['user']).pw_uid if 'user' in settings else os.geteuid()
                gid = grp.getgrnam(settings['group']).gr_gid if 'group' in settings else os.getegid()
                os.chown(settings['path'], uid, gid)
            except OSError as e:
                log('Could not set certificate file ownership', e, warning=True)
        else:
            log('File user and group handling unavailable on this platform', warning=True)
    # set permissions
    if 'perm' in settings:
        if hasattr(os, 'chmod'):
            try:
                os.chmod(settings['path'], int(settings['perm'], 8))
            except OSError as e:
                log('Could not set certificate file permissions', e, warning=True)
        else:
            log('File permission handling unavailable on this platform', warning=True)

    return settings['action']
Esempio n. 10
0
def cert_get(settings):
    log("Getting certificate for %s" % settings['domainlist'])

    acme = authority(settings['authority'])
    acme.register_account()

    # create challenge handlers for this certificate
    challenge_handlers = dict()
    for domain in settings['domainlist']:
        # Create the challenge handler
        challenge_handlers[domain] = challenge_handler(settings['handlers'][domain])

    # create ssl key
    key_file = settings['key_file']
    if os.path.isfile(key_file):
        key = tools.read_pem_file(key_file, key=True)
    else:
        log("SSL key not found at '{0}'. Creating key.".format(key_file))
        key = tools.new_ssl_key(key_file, settings['key_algorithm'], settings['key_length'])

    # create ssl csr
    csr_file = settings['csr_file']
    if os.path.isfile(csr_file) and str(settings['csr_static']).lower() == 'true':
        log('Loading CSR from {}'.format(csr_file))
        cr = tools.read_pem_file(csr_file, csr=True)
    else:
        log('Generating CSR for {}'.format(settings['domainlist']))
        must_staple = str(settings.get('cert_must_staple')).lower() == "true"
        cr = tools.new_cert_request(settings['domainlist'], key, must_staple)
        tools.write_pem_file(cr, csr_file)

    # request cert with csr
    crt, ca = acme.get_crt_from_csr(cr, settings['domainlist'], challenge_handlers)

    #  if resulting certificate is valid: store in final location
    if tools.is_cert_valid(crt, settings['ttl_days']):
        log("Certificate '{}' renewed and valid until {}".format(tools.get_cert_cn(crt),
                                                                 tools.get_cert_valid_until(crt)))
        tools.write_pem_file(crt, settings['cert_file'], stat.S_IREAD)
        if (not str(settings.get('ca_static')).lower() == 'true' or not os.path.exists(settings['ca_file'])) \
                and ca is not None:
            tools.write_pem_file(ca, settings['ca_file'])
Esempio n. 11
0
    def register_account(self):
        if self.registered_account:
            # We already have registered with this authority, just return
            return

        header = self._prepare_header()
        code, result = self._send_signed(self.ca + "/acme/new-reg", header, {
            "resource": "new-reg",
            "agreement": self.agreement,
        })
        if code == 201:
            log("Registered!")
            self.registered_account = True
            return True
        elif code == 409:
            log("Already registered!")
            self.registered_account = True
            return False
        else:
            raise ValueError("Error registering: {0} {1}".format(code, result))
Esempio n. 12
0
    def register_account(self):
        if self.account_id:
            # We already have registered with this authority, just return
            return

        protected = copy.deepcopy(self.account_protected)
        payload = {
            "termsOfServiceAgreed": self.tos_agreed,
            "onlyReturnExisting": False,
        }
        if self.contact:
            payload["contact"] = self.contact
        code, result, headers = self._request_acme_endpoint("newAccount", payload, protected)
        if code < 400 and result['status'] == 'valid':
            self.account_id = headers['Location']
            if 'meta' in self.directory and 'termsOfService' in self.directory['meta']:
                log("ToS at {} have been accepted.".format(self.directory['meta']['termsOfService']))
            log("Account registered and valid on {}.".format(self.ca))
        else:
            raise ValueError("Error registering account: {0} {1}".format(code, result))
Esempio n. 13
0
    def verify_dns_record(self, domain, txtvalue):
        if self.dns_verify_all_ns:
            try:
                nameserverip = None
                if self.dns_verify_server:
                    # Use the specific dns server to determine NS for domain, will otherwise default to SOA master
                    nameserverip = self._lookup_ip(self.dns_verify_server)
                ns_ip = self._lookup_ns_ip(domain, nameserverip)
                if len(ns_ip) > 0 and all(
                        self._check_txt_record_value(domain, txtvalue, ip)
                        for ip in ns_ip):
                    # All NS servers have the necessary TXT record. Succeed immediately!
                    log("All NS ({}) for '{}' have the correct TXT record".
                        format(','.join(ns_ip), domain))
                    return True
            except (ValueError, dns.exception.DNSException):
                # Fall back to next verification
                pass

        if self.dns_verify_server and not self.dns_verify_all_ns:
            try:
                # Verify using specific dns server
                nameserverip = self._lookup_ip(self.dns_verify_server)
                if self._check_txt_record_value(domain, txtvalue,
                                                nameserverip):
                    # Verify server confirms the necessary TXT record. Succeed immediately!
                    log("DNS server '{}' found correct TXT record for '{}'".
                        format(self.dns_verify_server, domain))
                    return True
            except (ValueError, dns.exception.DNSException):
                # Fall back to next verification
                pass

        if domain not in self._valid_times:
            # No valid wait time for domain. Verification fails!
            return False
        # Verification fails or succeeds based on valid wait time set by add_dns_record
        return datetime.now() >= self._valid_times[domain]
Esempio n. 14
0
 def log_message(self, fmt, *args):
     log("Request from '%s': %s" % (self.address_string(), fmt % args))
Esempio n. 15
0
    def get_crt_from_csr(self, csr, domains, challenge_handlers):
        account_thumbprint = tools.bytes_to_base64url(
            tools.hash_of_str(
                json.dumps(self.account_protected['jwk'],
                           sort_keys=True,
                           separators=(',', ':'))))

        log("Ordering certificate for {}".format(domains))
        identifiers = [{'type': 'dns', 'value': domain} for domain in domains]
        code, order, headers = self._request_acme_endpoint(
            'newOrder', {'identifiers': identifiers})
        if code >= 400:
            raise ValueError("Error with certificate order: {0} {1}".format(
                code, order))

        order_url = headers['Location']
        authorizations = list()
        # verify each domain
        try:
            for authorizationUrl in order['authorizations']:
                # get new challenge
                code, authorization, _ = self._request_acme_url(
                    authorizationUrl)
                if code >= 400:
                    raise ValueError(
                        "Error requesting authorization: {0} {1}".format(
                            code, authorization))

                authorization['_domain'] = "*.{}".format(authorization['identifier']['value']) if \
                    'wildcard' in authorization and authorization['wildcard'] else authorization['identifier']['value']

                if authorization.get('status', 'no-status-found') == 'valid':
                    log("{} has already been authorized".format(
                        authorization['_domain']))
                    continue
                if authorization['_domain'] not in challenge_handlers:
                    raise ValueError(
                        "No challenge handler given for domain: {0}".format(
                            authorization['_domain']))
                log("Authorizing {0}".format(authorization['_domain']))

                # create the challenge
                ctype = challenge_handlers[
                    authorization['_domain']].get_challenge_type()
                matching_challenges = [
                    c for c in authorization['challenges']
                    if c['type'] == ctype
                ]
                if len(matching_challenges) == 0:
                    raise ValueError(
                        "Error no challenge matching {0} found: {1}".format(
                            ctype, authorization))

                authorization['_challenge'] = matching_challenges[0]
                if authorization['_challenge'].get(
                        'status', 'no-status-found') == 'valid':
                    log("{} has already been authorized using {}".format(
                        authorization['_domain'], ctype))
                    continue

                authorization['_token'] = re.sub(
                    r"[^A-Za-z0-9_\-]", "_",
                    authorization['_challenge']['token'])
                challenge_handlers[authorization['_domain']].create_challenge(
                    authorization['identifier']['value'], account_thumbprint,
                    authorization['_token'])
                authorizations.append(authorization)

            # after all challenges are created, start processing authorizations
            for authorization in authorizations:
                try:
                    log("Starting verification of {}".format(
                        authorization['_domain']))
                    challenge_handlers[
                        authorization['_domain']].start_challenge(
                            authorization['identifier']['value'],
                            account_thumbprint, authorization['_token'])
                    # notify challenge is met
                    code, challenge_status, _ = self._request_acme_url(
                        authorization['_challenge']['url'], {
                            "keyAuthorization":
                            "{0}.{1}".format(authorization['_token'],
                                             account_thumbprint),
                        })
                    # wait for challenge to be verified
                    while code < 400 and challenge_status.get(
                            'status') == "pending":
                        time.sleep(5)
                        code, challenge_status, _ = self._request_acme_url(
                            authorization['_challenge']['url'])

                    if code < 400 and challenge_status.get(
                            'status') == "valid":
                        log("{0} verified".format(authorization['_domain']))
                    else:
                        raise ValueError(
                            "{0} challenge did not pass ({1}): {2}".format(
                                authorization['_domain'], code,
                                challenge_status))
                finally:
                    challenge_handlers[
                        authorization['_domain']].stop_challenge(
                            authorization['identifier']['value'],
                            account_thumbprint, authorization['_token'])
        finally:
            # Destroy challenge handlers in reverse order to replay
            # any saved state information in the handlers correctly
            for authorization in reversed(authorizations):
                try:
                    challenge_handlers[
                        authorization['_domain']].destroy_challenge(
                            authorization['identifier']['value'],
                            account_thumbprint, authorization['_token'])
                except Exception as e:
                    log('Challenge destruction failed: {}'.format(e),
                        error=True)

        # check order status and retry once
        code, order, _ = self._request_acme_url(order_url)
        if code < 400 and order.get('status') == 'pending':
            time.sleep(5)
            code, order, _ = self._request_acme_url(order_url)
        if code >= 400:
            raise ValueError(
                "Order is still not ready to be finalized: {0} {1}".format(
                    code, order))

        # get the new certificate
        log("Finalizing certificate")
        code, finalize, _ = self._request_acme_url(
            order['finalize'], {
                "csr":
                tools.bytes_to_base64url(tools.convert_cert_to_der_bytes(csr)),
            })
        while code < 400 and (finalize.get('status') == 'pending'
                              or finalize.get('status') == 'processing'):
            time.sleep(5)
            code, finalize, _ = self._request_acme_url(order_url)
        if code >= 400:
            raise ValueError("Error finalizing certificate: {0} {1}".format(
                code, finalize))
        log("Certificate ready!")

        # return certificate
        code, certificate, _ = self._request_acme_url(finalize['certificate'],
                                                      raw_result=True)
        if code >= 400:
            raise ValueError(
                "Error downloading certificate chain: {0} {1}".format(
                    code, certificate))

        cert_dict = re.match((
            r'(?P<cert>-----BEGIN CERTIFICATE-----[^\-]+-----END CERTIFICATE-----)\n\n'
            r'(?P<ca>-----BEGIN CERTIFICATE-----[^\-]+-----END CERTIFICATE-----)?'
        ), certificate, re.DOTALL).groupdict()
        cert = tools.convert_pem_str_to_cert(cert_dict['cert'])
        if cert_dict['ca'] is None:
            ca = tools.download_issuer_ca(cert)
        else:
            ca = tools.convert_pem_str_to_cert(cert_dict['ca'])

        return cert, ca
Esempio n. 16
0
 def remove_dns_record(self, domain, txtvalue):
     zone, nameserverip = self._determine_zone_and_nameserverip(domain)
     update = dns.update.Update(zone, keyring=self.keyring, keyalgorithm=self.keyalgorithm)
     update.delete(domain, dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.TXT, txtvalue))
     log('Deleting \'{} {} IN TXT "{}"\' from {}'.format(domain, self.dns_ttl, txtvalue, nameserverip))
     dns.query.tcp(update, nameserverip, timeout=QUERY_TIMEOUT)
Esempio n. 17
0
    def get_crt_from_csr(self, csr, domains, challenge_handlers):
        header = self._prepare_header()
        account_thumbprint = tools.bytes_to_base64url(
            tools.hash_of_str(
                json.dumps(header['jwk'],
                           sort_keys=True,
                           separators=(',', ':'))))

        challenges = dict()
        tokens = dict()
        authdomains = list()
        # verify each domain
        try:
            for domain in domains:
                log("Verifying {0}...".format(domain))

                # get new challenge
                code, result = self._send_signed(
                    self.ca + "/acme/new-authz", header, {
                        "resource": "new-authz",
                        "identifier": {
                            "type": "dns",
                            "value": domain
                        },
                    })
                if code != 201:
                    raise ValueError(
                        "Error requesting challenges: {0} {1}".format(
                            code, result))

                # create the challenge
                authz = json.loads(result.decode('utf8'))
                if authz.get('status', 'no-status-found') == 'valid':
                    log("{} has already been verified".format(domain))
                    continue
                challenges[domain] = [
                    c for c in authz['challenges'] if c['type'] ==
                    challenge_handlers[domain].get_challenge_type()
                ][0]
                tokens[domain] = re.sub(r"[^A-Za-z0-9_\-]", "_",
                                        challenges[domain]['token'])

                if domain not in challenge_handlers:
                    raise ValueError(
                        "No challenge handler given for domain: {0}".format(
                            domain))

                challenge_handlers[domain].create_challenge(
                    domain, account_thumbprint, tokens[domain])
                authdomains.append(domain)

            # after all challenges are created, start processing authorizations
            for domain in authdomains:
                try:
                    challenge_handlers[domain].start_challenge(
                        domain, account_thumbprint, tokens[domain])
                    # notify challenge are met
                    log("Starting key authorization")
                    keyauthorization = "{0}.{1}".format(
                        tokens[domain], account_thumbprint)
                    code, result = self._send_signed(
                        challenges[domain]['uri'], header, {
                            "resource": "challenge",
                            "keyAuthorization": keyauthorization,
                        })
                    if code != 202:
                        raise ValueError(
                            "Error triggering challenge: {0} {1}".format(
                                code, result))

                    # wait for challenge to be verified
                    while True:
                        try:
                            resp = tools.get_url(challenges[domain]['uri'])
                            challenge_status = json.loads(
                                resp.read().decode('utf8'))
                        except IOError as e:
                            raise ValueError(
                                "Error checking challenge: {0} {1}".format(
                                    e.code,
                                    json.loads(e.read().decode('utf8'))))
                        if challenge_status['status'] == "pending":
                            time.sleep(2)
                        elif challenge_status['status'] == "valid":
                            log("{0} verified!".format(domain))
                            break
                        else:
                            raise ValueError(
                                "{0} challenge did not pass: {1}".format(
                                    domain, challenge_status))
                finally:
                    challenge_handlers[domain].stop_challenge(
                        domain, account_thumbprint, tokens[domain])
        finally:
            # Destroy challenge handlers in reverse order to replay
            # any saved state information in the handlers correctly
            for domain in reversed(domains):
                try:
                    challenge_handlers[domain].destroy_challenge(
                        domain, account_thumbprint, tokens[domain])
                except Exception as e:
                    log('Challenge destruction failed: {}'.format(e),
                        error=True)

        # get the new certificate
        log("Signing certificate...")
        code, result = self._send_signed(
            self.ca + "/acme/new-cert", header, {
                "resource":
                "new-cert",
                "csr":
                tools.bytes_to_base64url(tools.convert_cert_to_der_bytes(csr)),
            })
        if code != 201:
            raise ValueError("Error signing certificate: {0} {1}".format(
                code, result))

        # return signed certificate!
        log("Certificate signed!")
        cert = tools.convert_der_bytes_to_cert(result)
        return cert, tools.download_issuer_ca(cert)
Esempio n. 18
0
def main():
    # load config
    runtimeconfig, domainconfigs = configuration.load()
    # register idna-mapped domains as LOG_REPLACEMENTS for better readability of log output
    LOG_REPLACEMENTS.update({k: "{} [{}]".format(k, v) for k, v in domainconfigs['domainlist_idna_mapped']})
    # Start processing
    if runtimeconfig.get('mode') == 'revoke':
        # Mode: revoke certificate
        log("Revoking {}".format(runtimeconfig['revoke']))
        cert_revoke(tools.read_pem_file(runtimeconfig['revoke']),
                    domainconfigs,
                    runtimeconfig['fallback_authority'],
                    runtimeconfig['revoke_reason'])
    else:
        # Mode: issue certificates (implicit)
        # post-update actions (run only once)
        actions = set()
        superseded = set()
        exceptions = list()
        # check certificate validity and obtain/renew certificates if needed
        for config in domainconfigs:
            try:
                cert = None
                if os.path.isfile(config['cert_file']):
                    cert = tools.read_pem_file(config['cert_file'])
                validate_ocsp = str(config.get('validate_ocsp')).lower() != 'false'
                if validate_ocsp and cert and os.path.isfile(config['ca_file']):
                    try:
                        issuer = tools.read_pem_file(config['ca_file'])
                    except Exception as e1:
                        log("Failed to retrieve issuer from ca file: {}. Trying to download...".format(e1))
                        try:
                            issuer = tools.download_issuer_ca(cert)
                        except Exception as e2:
                            log("Failed to download issuer for cert file: {}. Cannot validate OCSP.".format(e2))
                            validate_ocsp = False
                if not cert or ('force_renew' in runtimeconfig and all(
                        d in config['domainlist'] for d in runtimeconfig['force_renew'])) \
                        or not tools.is_cert_valid(cert, config['ttl_days']) \
                        or (validate_ocsp and not tools.is_ocsp_valid(cert, issuer, config['validate_ocsp'])):
                    cert_get(config)
                    if str(config.get('cert_revoke_superseded')).lower() == 'true' and cert:
                        superseded.add(cert)
            except Exception as e:
                log("Certificate issue/renew failed", e, error=True)
                exceptions.append(e)

        # deploy new certificates after all are renewed
        deployment_success = True
        for config in domainconfigs:
            for cfg in config['actions']:
                try:
                    if not tools.target_is_current(cfg['path'], config['cert_file']):
                        actions.add(cert_put(cfg))
                        log("Updated '{}' due to newer version".format(cfg['path']))
                except Exception as e:
                    log("Certificate deployment to {} failed".format(cfg['path']), e, error=True)
                    exceptions.append(e)
                    deployment_success = False

        # run post-update actions
        for action in actions:
            if action is not None:
                try:
                    # Run actions in a shell environment (to allow shell syntax) as stated in the configuration
                    output = subprocess.check_output(action, shell=True, stderr=subprocess.STDOUT)
                    logmsg = "Action succeeded: {}".format(action)
                    if len(output) > 0:
                        if getattr(output, 'decode', None):
                            # Decode function available? Use it to get a proper str
                            output = output.decode('utf-8')
                        logmsg += os.linesep + tools.indent(output, 18)  # 18 = len("Action succeeded: ")
                    log(logmsg)
                except subprocess.CalledProcessError as e:
                    output = e.output
                    logmsg = "Action failed: ({}) {}".format(e.returncode, e.cmd)
                    if len(output) > 0:
                        if getattr(output, 'decode', None):
                            # Decode function available? Use it to get a proper str
                            output = output.decode('utf-8')
                        logmsg += os.linesep + tools.indent(output, 15)  # 15 = len("Action failed: ")
                    log(logmsg, error=True)
                    exceptions.append(e)
                    deployment_success = False

        # revoke old certificates as superseded
        if deployment_success:
            for superseded_cert in superseded:
                try:
                    log("Revoking '{}' valid until {} as superseded".format(
                        tools.get_cert_cn(superseded_cert),
                        tools.get_cert_valid_until(superseded_cert)))
                    cert_revoke(superseded_cert, domainconfigs, runtimeconfig['fallback_authority'], reason=4)
                except Exception as e:
                    log("Certificate supersede revoke failed", e, error=True)
                    exceptions.append(e)

        # throw a RuntimeError with all exceptions caught while working if there were any
        if len(exceptions) > 0:
            raise RuntimeError("{} exception(s) occurred during processing".format(len(exceptions)))
Esempio n. 19
0
 def add_dns_record(self, domain, txtvalue):
     zone, nameserverip = self._determine_zone_and_nameserverip(domain)
     update = dns.update.Update(zone, keyring=self.keyring, keyalgorithm=self.keyalgorithm)
     update.add(domain, self.dns_ttl, dns.rdatatype.TXT, txtvalue)
     log('Adding \'{} {} IN TXT "{}"\' to {}'.format(domain, self.dns_ttl, txtvalue, nameserverip))
     dns.query.tcp(update, nameserverip, timeout=QUERY_TIMEOUT)