diff options
| author | vg <vgm+dev@devys.org> | 2026-10-19 23:49:41 +0200 |
|---|---|---|
| committer | vg <vgm+dev@devys.org> | 2026-10-19 23:49:41 +0200 |
| commit | ac559e8e87eff8304365063f92739bcb5d0c0a51 (patch) | |
| tree | 207b72dc7c180da6fdbb76d8ff2d1099fca38ded | |
| parent | f201868b9089da733ae268a5a72f8359be10e51d (diff) | |
| download | acme-dns-tiny-v1.tar.gz acme-dns-tiny-v1.tar.bz2 acme-dns-tiny-v1.zip | |
| -rwxr-xr-x[-rw-r--r--] | acme_dns_tiny.py | 28 | ||||
| -rw-r--r-- | tests/test_acme_dns_tiny.py | 39 |
2 files changed, 32 insertions, 35 deletions
diff --git a/acme_dns_tiny.py b/acme_dns_tiny.py index 9922e45..8c0f2b8 100644..100755 --- a/acme_dns_tiny.py +++ b/acme_dns_tiny.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# """\ Tiny ACME client to get TLS certificate by responding to DNS challenges. It uses Let's Encrypt servers by default. @@ -60,21 +59,20 @@ esac """ -import subprocess -import json import base64 -import time -import hashlib -import re -import logging import collections import contextlib +import hashlib +import json +import logging +import re +import subprocess +import time +import dns import docopt import requests -import dns -import dns.resolver as resolver - +from dns import resolver ACME_DEFAULT_DIRECTORY = 'https://acme-v02.api.letsencrypt.org/directory' @@ -95,9 +93,9 @@ def b64json(obj): def openssl(subcommand, stdin=None): """Run openssl command line and raise IOError on non-zero return.""" - binary = True if '-sign' in subcommand or 'DER' in subcommand else False + binary = bool('-sign' in subcommand or 'DER' in subcommand) return subprocess.run( - ['openssl'] + subcommand, + ['openssl', *subcommand], input=stdin.encode('utf8') if binary and stdin is not None else stdin, stdout=subprocess.PIPE, check=True, @@ -115,7 +113,7 @@ def extract_domains_from_csr(csr_path): r'X509v3 Subject Alternative Name:\s+([^\r\n]+)\r?\n', csr, re.MULTILINE) domains = set(re.findall(r'Subject:.*?\s+?CN\s*?=\s*?([^\s,;/]+)', csr) - + re.findall(r'DNS:\s*(\S+)\s*(?:,|$)', san and san[0] or '')) + + re.findall(r'DNS:\s*(\S+)\s*(?:,|$)', (san and san[0]) or '')) if not domains: raise ValueError('No domain to validate in the provided CSR.') return domains @@ -183,7 +181,7 @@ class ACME: def init_sreqs(self, directory_url=None): self.generate_jws_header(self.account_key_path) log.info('Fetch information from the ACME directory.') - self.directory = dirmap = requests.get(directory_url, + self.directory = requests.get(directory_url, headers=self.dirheaders).json() self.request_new_nonce() @@ -281,7 +279,7 @@ class ACME: log.info('Process challenge for authorization: %s', authurl) sreq = self.sreq(authurl).map domain = sreq['identifier']['value'] - challenge = [c for c in sreq['challenges'] if c['type'] == 'dns-01'][0] + challenge = next(c for c in sreq['challenges'] if c['type'] == 'dns-01') token = re.sub(r'[^A-Za-z0-9_-]', '_', challenge['token']) # b64url keyauth = f'{token}.{self.jwk_thumbprint}' keydigest64 = b64sha256(keyauth) diff --git a/tests/test_acme_dns_tiny.py b/tests/test_acme_dns_tiny.py index 5a4a479..bfa8d4a 100644 --- a/tests/test_acme_dns_tiny.py +++ b/tests/test_acme_dns_tiny.py @@ -1,6 +1,3 @@ -#!python3 - - import contextlib import logging import os @@ -11,14 +8,13 @@ import time import pytest - # append cwd to module importable paths (pytest-3 should be executed from main # directory). sys.path.append(os.path.realpath(".")) import acme_dns_tiny - acme_dns_tiny.log_configure = False +log = logging.getLogger(__name__) ACME_STAGING_DIRECTORY = \ @@ -44,16 +40,18 @@ original_sreq_method = acme_dns_tiny.ACME.sreq nonce_expiration_sreq_wrapper_counter_max = 3 -nonce_expiration_sreq_wrapper_counter = nonce_expiration_sreq_wrapper_counter_max +nonce_expiration_sreq_wrapper_counter = \ + nonce_expiration_sreq_wrapper_counter_max def nonce_expiration_sreq_wrapper(*args, **kwargs): global nonce_expiration_sreq_wrapper_counter nonce_expiration_sreq_wrapper_counter -= 1 if nonce_expiration_sreq_wrapper_counter <= 0: - nonce_expiration_sreq_wrapper_counter = nonce_expiration_sreq_wrapper_counter_max + nonce_expiration_sreq_wrapper_counter \ + = nonce_expiration_sreq_wrapper_counter_max wait = 30 - logging.info('waiting %dmin for nonce expiration test', wait) + log.info('waiting %dmin for nonce expiration test', wait) for i in range(wait, 0, -1): - logging.info('%d min left', i) + log.info('%d min left', i) time.sleep(60) acme_dns_tiny.ACME.sreq = original_sreq_method # reset original method return original_sreq_method(*args, **kwargs) @@ -187,7 +185,7 @@ def main_args(tmpdir_factory, keysdef, request, subj_fixture): domain_key = keysdef['domain_key_path'](tmpdir) path = str(tmpdir.join(name)) with tempfile.NamedTemporaryFile('w', encoding='utf8') as conf, \ - open('/etc/ssl/openssl.cnf', 'r', encoding='utf8') as orig: + open('/etc/ssl/openssl.cnf', encoding='utf8') as orig: conf.write(orig.read()) conf.write(f'\n[SAN]\nsubjectAltName={subj_fixture["san"]}\n') conf.flush() @@ -215,7 +213,8 @@ def test_sanity_env(): def test_sanity_command(): 'check ssh access is correctly working before getting further' - subprocess.run([SCRIPT, 'add', f'_acme-challenge.{DOMAIN}.', 'dummy']) + subprocess.run([SCRIPT, 'add', f'_acme-challenge.{DOMAIN}.', 'dummy'], + check=True) def assert_cert(capsys, args, keysdef): @@ -223,14 +222,14 @@ def assert_cert(capsys, args, keysdef): #assert not captured.err #certlist = captured.out.split() - logging.debug('captured stdout %s', captured.out) - logging.debug('captured stderr %s', captured.err) + log.debug('captured stdout %s', captured.out) + log.debug('captured stderr %s', captured.err) # Subscriber certificates with RSA public keys are issued from our RSA # intermediates, which are issued only from our RSA root ISRG Root X1 - # (i.e. they are not cross-signed). Therefore, all RSA subscriber certificates - # have only a single chain available. (since 2025-06-11, see here for more - # information https://letsencrypt.org/certificates/). + # (i.e. they are not cross-signed). Therefore, all RSA subscriber + # certificates have only a single chain available. (since 2025-06-11, see + # here for more information https://letsencrypt.org/certificates/). if ( args['--separator'] is None or ( @@ -258,19 +257,19 @@ def assert_cert(capsys, args, keysdef): def module_main_caller(*, capsys, args, expectation, do_expire_nonce, keysdef): - logging.info(f'module_main_caller({args}, {expectation})') + log.info(f'module_main_caller({args}, {expectation})') - logging.debug('before call to acme_dns_tiny.main()') + log.debug('before call to acme_dns_tiny.main()') with expectation: acme_dns_tiny.ACME.sreq = original_sreq_method if do_expire_nonce: - logging.info('doing expire nonce test') + log.info('doing expire nonce test') acme_dns_tiny.ACME.sreq = nonce_expiration_sreq_wrapper acme_dns_tiny.main(args) # check_cert is under the expectation context manager since if # acme_dns_tiny.main() raises, following statement must not be run. assert_cert(capsys, args, keysdef) - logging.debug('after call to acme_dns_tiny.main()') + log.debug('after call to acme_dns_tiny.main()') def test_main(main_args, capsys): |
