certbot-ci: add integration test for dns-persist

This also bumps the pebble version to the current release w/
dns-persist-01 support
This commit is contained in:
Will Greenberg 2026-05-06 20:13:44 -07:00
parent b1a3c488eb
commit 483681ff2f
3 changed files with 45 additions and 25 deletions

View file

@ -38,30 +38,34 @@ class IntegrationTestsContext:
os.close(probe[0])
self.hook_probe = probe[1]
self.manual_dns_auth_hook = (
'{0} -c "import os; import requests; import json; '
"assert not os.environ.get('CERTBOT_DOMAIN').startswith('fail'); "
"data = {{'host':'_acme-challenge.{{0}}.'.format(os.environ.get('CERTBOT_DOMAIN')),"
"'value':os.environ.get('CERTBOT_VALIDATION')}}; "
"request = requests.post('{1}/set-txt', data=json.dumps(data)); "
"request.raise_for_status(); "
'"'
).format(sys.executable, self.challtestsrv_url)
self.manual_dns_auth_hook_allow_fail = (
'{0} -c "import os; import requests; import json; '
"data = {{'host':'_acme-challenge.{{0}}.'.format(os.environ.get('CERTBOT_DOMAIN')),"
"'value':os.environ.get('CERTBOT_VALIDATION')}}; "
"request = requests.post('{1}/set-txt', data=json.dumps(data)); "
"request.raise_for_status(); "
'"'
).format(sys.executable, self.challtestsrv_url)
self.manual_dns_cleanup_hook = (
'{0} -c "import os; import requests; import json; '
"data = {{'host':'_acme-challenge.{{0}}.'.format(os.environ.get('CERTBOT_DOMAIN'))}}; "
"request = requests.post('{1}/clear-txt', data=json.dumps(data)); "
"request.raise_for_status(); "
'"'
).format(sys.executable, self.challtestsrv_url)
self.manual_dns_auth_hook = self.generate_dns_auth_hook('_acme-challenge', True)
self.manual_dns_auth_hook_allow_fail = self.generate_dns_auth_hook('_acme-challenge', False)
self.manual_dns_persist_auth_hook = self.generate_dns_auth_hook('_validation-persist', True)
self.manual_dns_cleanup_hook = self.generate_dns_cleanup_hook('_acme-challenge')
self.manual_dns_persist_cleanup_hook = self.generate_dns_cleanup_hook('_validation-persist')
def generate_dns_auth_hook(self, challenge_subdomain: str, fail_on_subdomain: bool) -> str:
"""Generates a python one-liner script which sets a DNS challenge TXT record challtestsrv URL,
and optionally fails if the subdomain starts with the word "fail" to simulate a faulty script"""
script_lines = ['import os', 'import requests', 'import json']
if fail_on_subdomain:
script_lines.append("assert not os.environ.get('CERTBOT_DOMAIN').startswith('fail')")
script_lines.append(f"data = {{'host':'{challenge_subdomain}.{{0}}.'.format("
"os.environ.get('CERTBOT_DOMAIN')), 'value':"
"os.environ.get('CERTBOT_VALIDATION')}")
script_lines.append(f"request = requests.post('{self.challtestsrv_url}/set-txt', data=json.dumps(data))")
script_lines.append("request.raise_for_status()")
script = '; '.join(script_lines)
return f'{sys.executable} -c "{script}"'
def generate_dns_cleanup_hook(self, challenge_subdomain: str) -> str:
"""Generates a python one-liner script which cleans up the TXT record made by `generate_dns_auth_hook`"""
script_lines = ['import os', 'import requests', 'import json']
script_lines.append(f"data = {{'host':'{challenge_subdomain}.{{0}}.'.format(os.environ.get('CERTBOT_DOMAIN'))}}")
script_lines.append(f"request = requests.post('{self.challtestsrv_url}/clear-txt', data=json.dumps(data))")
script_lines.append("request.raise_for_status()")
script = '; '.join(script_lines)
return f'{sys.executable} -c "{script}"'
def cleanup(self) -> None:
"""Cleanup the integration test context."""

View file

@ -206,6 +206,22 @@ def test_manual_dns_auth(context: IntegrationTestsContext) -> None:
assert_cert_count_for_lineage(context.config_dir, certname, 2)
def test_manual_dns_persist_auth(context: IntegrationTestsContext) -> None:
"""Test the DNS-PERSIST-01 challenge using manual plugin."""
certname = context.get_domain('dns-persist')
context.certbot([
'-a', 'manual', '-d', certname, '--preferred-challenges', 'dns-persist',
'run', '--cert-name', certname,
'--manual-auth-hook', context.manual_dns_persist_auth_hook,
'--manual-cleanup-hook', context.manual_dns_persist_cleanup_hook,
'--deploy-hook', misc.echo('deploy', context.hook_probe),
])
assert_hook_execution(context.hook_probe, 'deploy')
assert_saved_deploy_hook(context.config_dir, certname)
assert_cert_count_for_lineage(context.config_dir, certname, 1)
def test_certonly(context: IntegrationTestsContext) -> None:
"""Test the certonly verb on certbot."""
context.certbot(['certonly', '--cert-name', 'newname', '-d', context.get_domain('newname')])

View file

@ -15,7 +15,7 @@ import requests
from certbot_integration_tests.utils.constants import DEFAULT_HTTP_01_PORT
from certbot_integration_tests.utils.constants import MOCK_OCSP_SERVER_PORT
PEBBLE_VERSION = 'v2.8.0'
PEBBLE_VERSION = 'v2.10.1'
def fetch(workspace: str, http_01_port: int = DEFAULT_HTTP_01_PORT) -> tuple[str, str, str]: