renewal: use lineage-specific server for ARI (#10307)

Previously, we were constructing an ACME client for ARI checking that
used the global value for `server`, not the one recorded in a lineage's
renewal file.

This resulted in errors in the logs and failure to observe ARI for
lineages that used a non-default `--server` (e.g. staging or non-Let's
Encrypt CAs).

---------

Co-authored-by: ohemorange <ebportnoy@gmail.com>
This commit is contained in:
Jacob Hoffman-Andrews
2025-06-09 11:44:04 -07:00
committed by GitHub
co-authored by ohemorange
parent a75057042f
commit 1d9fc8dccf
5 changed files with 34 additions and 8 deletions
+5 -1
View File
@@ -6,7 +6,11 @@ Certbot adheres to [Semantic Versioning](https://semver.org/).
### Added
*
* ACME Renewal Info (ARI) support. https://datatracker.ietf.org/doc/draft-ietf-acme-ari/
`certbot renew` will automatically check ARI when using an ACME server that supports it,
and may renew early based on the ARI information. For Let's Encrypt certificates this
will typically cause renewal at around 2/3rds of the certificate's lifetime, even if
the renew_before_expiry field of a lineage renewal config is set a later date.
### Changed
+5 -1
View File
@@ -610,11 +610,15 @@ def handle_renewal_request(config: configuration.NamespaceConfig) -> Tuple[list,
if not renewal_candidate:
parse_failures.append(renewal_file)
else:
# We check ARI against the server stored in the lineage config even if the user
# specified a different `--server` on the command line. That's the server that
# issued the existing certificate, so it's the only server that can respond to
# ARI requests for it.
server = lineage_config.server
if not server:
raise errors.Error(f"Renewal config for {lineage_config.names} has no server.")
if server not in acme_clients:
acme_clients[server] = client.acme_from_config_key(config)
acme_clients[server] = client.acme_from_config_key(lineage_config)
renewal_candidate.ensure_deployed()
from certbot._internal import main
@@ -216,10 +216,12 @@ class RenewalTest(test_util.ConfigTestCase):
assert lineage_config.key_type == 'rsa'
@test_util.patch_display_util()
@mock.patch.object(configuration.NamespaceConfig, 'set_by_user')
@mock.patch('certbot._internal.client.acme_from_config_key')
@mock.patch('certbot._internal.main.renew_cert')
@mock.patch("certbot._internal.renewal.datetime")
def test_renewal_via_ari(self, mock_datetime, mock_renew_cert, mock_acme_from_config, unused_mock_display):
def test_renewal_via_ari(self, mock_datetime, mock_renew_cert, mock_acme_from_config, mock_set_by_user, unused_mock_display):
mock_set_by_user.return_value = False
from certbot._internal import renewal
acme_client = mock.MagicMock()
mock_acme_from_config.return_value = acme_client
@@ -230,12 +232,17 @@ class RenewalTest(test_util.ConfigTestCase):
acme_client.renewal_time.return_value = past, future
test_util.make_lineage(self.config.config_dir, 'sample-renewal.conf', ec=False)
lineage_config = copy.deepcopy(self.config)
config = configuration.NamespaceConfig(self.config)
with mock.patch('time.sleep') as sleep:
renewal.handle_renewal_request(lineage_config)
renewal.handle_renewal_request(config)
mock_renew_cert.assert_called_once()
# This value comes from `sample-renewal.conf` and is different than
# the global default.
expected_server = "https://acme-staging-v02.api.letsencrypt.org/directory"
assert expected_server != config.server
assert mock_acme_from_config.call_args[0][0].server == expected_server
def test_default_renewal_time(self):
from certbot._internal import renewal