From a75057042fc415aa1e964b8a14dbd8a538bd2ee6 Mon Sep 17 00:00:00 2001 From: Jacob Hoffman-Andrews Date: Fri, 6 Jun 2025 14:39:10 -0700 Subject: [PATCH] integration: add test for early renewal from ARI (#10311) This depends on a pending Pebble pull request and so will fail integration tests until/unless that lands: https://github.com/letsencrypt/pebble/pull/501 However, I'd appreciate some eyes on this PR in this regard: is the interface we're using in Pebble useful and appropriate? If not, we can adjust the Pebble PR. Inspired based on conversation on https://github.com/certbot/certbot/pull/10307, but note that this just tests the general case; it does not test the "default server differs from lineage server" case yet; when I try adding that I get some bugs that may reflect a problem in #10307 I need to fix (or may reflect that I need to inhibit the `--server` flag rather than trying to override it late in the command line). --- .../certbot_tests/test_main.py | 26 +++++++++++++++++++ .../certbot_integration_tests/utils/misc.py | 18 +++++++++++++ .../utils/pebble_artifacts.py | 2 +- 3 files changed, 45 insertions(+), 1 deletion(-) diff --git a/certbot-ci/src/certbot_integration_tests/certbot_tests/test_main.py b/certbot-ci/src/certbot_integration_tests/certbot_tests/test_main.py index f32b46f78..0cfaad752 100644 --- a/certbot-ci/src/certbot_integration_tests/certbot_tests/test_main.py +++ b/certbot-ci/src/certbot_integration_tests/certbot_tests/test_main.py @@ -1,4 +1,5 @@ """Module executing integration tests against certbot core.""" +import json import os from os.path import exists from os.path import join @@ -310,6 +311,31 @@ def test_graceful_renew_it_is_time(context: IntegrationTestsContext) -> None: assert_hook_execution(context.hook_probe, 'deploy') +def test_renew_when_ari_says_its_time(context: IntegrationTestsContext) -> None: + """Test graceful renew is done when it is due time.""" + certname = context.get_domain('renew') + context.certbot(['-d', certname]) + + assert_cert_count_for_lineage(context.config_dir, certname, 1) + + # Tell Pebble to make ARI look urgent + with open(join(context.config_dir, 'live', certname, 'cert.pem'), 'r') as c: + certificate_pem = c.read() + + misc.set_ari_response(certificate_pem, json.dumps({ + 'suggestedWindow': { + 'start': '2020-01-01T00:00:00Z', + 'end': '2020-01-01T00:00:00Z' + } + })) + + context.certbot(['renew', '--deploy-hook', misc.echo('deploy', context.hook_probe)], + force_renew=False) + + assert_cert_count_for_lineage(context.config_dir, certname, 2) + assert_hook_execution(context.hook_probe, 'deploy') + + def test_renew_with_changed_private_key_complexity(context: IntegrationTestsContext) -> None: """Test proper renew with updated private key complexity.""" certname = context.get_domain('renew') diff --git a/certbot-ci/src/certbot_integration_tests/utils/misc.py b/certbot-ci/src/certbot_integration_tests/utils/misc.py index a81007d56..34687d9e8 100644 --- a/certbot-ci/src/certbot_integration_tests/utils/misc.py +++ b/certbot-ci/src/certbot_integration_tests/utils/misc.py @@ -8,6 +8,7 @@ import errno import functools import http.server as SimpleHTTPServer import importlib.resources +import json import os import re import shutil @@ -318,3 +319,20 @@ def get_acme_issuers() -> List[Certificate]: issuers.append(load_pem_x509_certificate(request.content, default_backend())) return issuers + +def set_ari_response(certificate_pem: str, response_json: str) -> None: + """POST to an endpoint on the Pebble server setting the ARI response + for the given certificate.""" + set_renewal_info_body = json.dumps( + { + 'certificate': certificate_pem, + 'ariResponse': response_json, + }) + + _suppress_x509_verification_warnings() + url = PEBBLE_MANAGEMENT_URL + '/set-renewal-info/' + print(f'sending to {url}: {set_renewal_info_body}') + resp = requests.post(url, verify=False, timeout=10, data=set_renewal_info_body) + if resp.status_code != 200: + print(f'setting renewal info: {resp.status_code} {resp.text}') + assert resp.status_code == 200 diff --git a/certbot-ci/src/certbot_integration_tests/utils/pebble_artifacts.py b/certbot-ci/src/certbot_integration_tests/utils/pebble_artifacts.py index 21fbb4077..bb504bfaa 100644 --- a/certbot-ci/src/certbot_integration_tests/utils/pebble_artifacts.py +++ b/certbot-ci/src/certbot_integration_tests/utils/pebble_artifacts.py @@ -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.7.0' +PEBBLE_VERSION = 'v2.8.0' def fetch(workspace: str, http_01_port: int = DEFAULT_HTTP_01_PORT) -> Tuple[str, str, str]: