mirror of
https://github.com/certbot/certbot.git
synced 2026-07-26 07:39:52 +02:00
manual plugin: add IP address support (#10544)
The manual plugin offers environment variables for its hook called CERTBOT_DOMAIN and CERTBOT_ALL_DOMAINS. I added CERTBOT_IDENTIFIER and CERTBOT_ALL_IDENTIFIERS, while keeping the old variables for backwards compatibility. Certbot will pass IP addresses in the CERTBOT_DOMAIN environment variable rather than erroring out. Part of #10346 --------- Co-authored-by: Brad Warren <bmw@users.noreply.github.com>
This commit is contained in:
co-authored by
Brad Warren
parent
b00e3de9d2
commit
1b7c11e6a5
@@ -24,7 +24,7 @@ logger = logging.getLogger(__name__)
|
||||
class Authenticator(common.Plugin, interfaces.Authenticator):
|
||||
"""Manual authenticator
|
||||
|
||||
This plugin allows the user to perform the domain validation
|
||||
This plugin allows the user to perform the validation
|
||||
challenge(s) themselves. This either be done manually by the user or
|
||||
through shell scripts provided to Certbot.
|
||||
|
||||
@@ -36,7 +36,7 @@ class Authenticator(common.Plugin, interfaces.Authenticator):
|
||||
'Authenticate through manual configuration or custom shell scripts. '
|
||||
'When using shell scripts, an authenticator script must be provided. '
|
||||
'The environment variables available to this script depend on the '
|
||||
'type of challenge. $CERTBOT_DOMAIN will always contain the domain '
|
||||
'type of challenge. $CERTBOT_IDENTIFIER will always contain the domain or IP address '
|
||||
'being authenticated. For HTTP-01 and DNS-01, $CERTBOT_VALIDATION '
|
||||
'is the validation string, and $CERTBOT_TOKEN is the filename of the '
|
||||
'resource requested when performing an HTTP-01 challenge. An additional '
|
||||
@@ -44,8 +44,8 @@ class Authenticator(common.Plugin, interfaces.Authenticator):
|
||||
'$CERTBOT_AUTH_OUTPUT which contains the stdout output from the auth script. '
|
||||
'For both authenticator and cleanup script, on HTTP-01 and DNS-01 challenges, '
|
||||
'$CERTBOT_REMAINING_CHALLENGES will be equal to the number of challenges that '
|
||||
'remain after the current one, and $CERTBOT_ALL_DOMAINS contains a comma-separated '
|
||||
'list of all domains that are challenged for the current certificate.')
|
||||
'remain after the current one, and $CERTBOT_ALL_IDENTIFIERS contains a comma-separated '
|
||||
'list of all identifiers that are challenged for the current certificate.')
|
||||
# Include the full stop at the end of the FQDN in the instructions below for the null
|
||||
# label of the DNS root, as stated in section 3.1 of RFC 1035. While not necessary
|
||||
# for most day to day usage of hostnames, when adding FQDNs to a DNS zone editor, this
|
||||
@@ -123,7 +123,7 @@ permitted by DNS standards.)
|
||||
|
||||
def more_info(self) -> str: # pylint: disable=missing-function-docstring
|
||||
return (
|
||||
'This plugin allows the user to customize setup for domain '
|
||||
'This plugin allows the user to customize setup for '
|
||||
'validation challenges either through shell scripts provided by '
|
||||
'the user or by performing the setup manually.')
|
||||
|
||||
@@ -184,13 +184,14 @@ permitted by DNS standards.)
|
||||
|
||||
def _perform_achall_with_script(self, achall: achallenges.AnnotatedChallenge,
|
||||
achalls: list[achallenges.AnnotatedChallenge]) -> None:
|
||||
if not achall.identifier.typ == messages.IDENTIFIER_FQDN:
|
||||
raise errors.ConfigurationError("non-FQDN identifiers not yet supported")
|
||||
domain = achall.identifier.value
|
||||
identifier_value = achall.identifier.value
|
||||
env = {
|
||||
"CERTBOT_DOMAIN": domain,
|
||||
"CERTBOT_DOMAIN": identifier_value,
|
||||
"CERTBOT_IDENTIFIER": identifier_value,
|
||||
"CERTBOT_VALIDATION": achall.validation(achall.account_key),
|
||||
"CERTBOT_ALL_DOMAINS": ','.join(one_achall.identifier.value for one_achall in achalls),
|
||||
"CERTBOT_ALL_IDENTIFIERS":
|
||||
','.join(one_achall.identifier.value for one_achall in achalls),
|
||||
"CERTBOT_REMAINING_CHALLENGES": str(len(achalls) - achalls.index(achall) - 1),
|
||||
}
|
||||
if isinstance(achall.chall, challenges.HTTP01):
|
||||
@@ -198,25 +199,24 @@ permitted by DNS standards.)
|
||||
else:
|
||||
os.environ.pop('CERTBOT_TOKEN', None)
|
||||
os.environ.update(env)
|
||||
_, out = self._execute_hook('auth-hook', domain)
|
||||
_, out = self._execute_hook('auth-hook', identifier_value)
|
||||
env['CERTBOT_AUTH_OUTPUT'] = out.strip()
|
||||
self.env[achall] = env
|
||||
|
||||
def _perform_achall_manually(self, achall: achallenges.AnnotatedChallenge,
|
||||
last_dns_achall: bool = False) -> None:
|
||||
if not achall.identifier.typ == messages.IDENTIFIER_FQDN:
|
||||
raise errors.ConfigurationError("non-FQDN identifiers not yet supported")
|
||||
domain = achall.identifier.value
|
||||
identifier_value = achall.identifier.value
|
||||
validation = achall.validation(achall.account_key)
|
||||
if isinstance(achall.chall, challenges.HTTP01):
|
||||
msg = self._HTTP_INSTRUCTIONS.format(
|
||||
achall=achall, encoded_token=achall.chall.encode('token'),
|
||||
port=self.config.http01_port,
|
||||
uri=achall.chall.uri(domain), validation=validation)
|
||||
uri=achall.chall.uri(identifier_value), validation=validation)
|
||||
else:
|
||||
assert isinstance(achall.chall, challenges.DNS01)
|
||||
assert achall.identifier.typ == messages.IDENTIFIER_FQDN
|
||||
msg = self._DNS_INSTRUCTIONS.format(
|
||||
domain=achall.validation_domain_name(domain),
|
||||
domain=achall.validation_domain_name(identifier_value),
|
||||
validation=validation)
|
||||
if isinstance(achall.chall, challenges.DNS01):
|
||||
if self.subsequent_dns_challenge:
|
||||
@@ -230,7 +230,7 @@ permitted by DNS standards.)
|
||||
if last_dns_achall:
|
||||
# last dns-01 challenge
|
||||
msg += self._DNS_VERIFY_INSTRUCTIONS.format(
|
||||
domain=achall.validation_domain_name(domain))
|
||||
domain=achall.validation_domain_name(identifier_value))
|
||||
elif self.subsequent_any_challenge:
|
||||
# 2nd or later challenge of another type
|
||||
msg += self._SUBSEQUENT_CHALLENGE_INSTRUCTIONS
|
||||
@@ -247,13 +247,13 @@ permitted by DNS standards.)
|
||||
self._execute_hook('cleanup-hook', achall.identifier.value)
|
||||
self.reverter.recovery_routine()
|
||||
|
||||
def _execute_hook(self, hook_name: str, achall_domain: str) -> tuple[str, str]:
|
||||
def _execute_hook(self, hook_name: str, identifier_value: str) -> tuple[str, str]:
|
||||
returncode, err, out = misc.execute_command_status(
|
||||
self.option_name(hook_name), self.conf(hook_name),
|
||||
env=util.env_no_snap_for_external_calls()
|
||||
)
|
||||
|
||||
display_ops.report_executed_command(
|
||||
f"Hook '--manual-{hook_name}' for {achall_domain}", returncode, out, err)
|
||||
f"Hook '--manual-{hook_name}' for {identifier_value}", returncode, out, err)
|
||||
|
||||
return err, out
|
||||
|
||||
@@ -67,20 +67,28 @@ class AuthenticatorTest(test_util.TempDirTestCase):
|
||||
'{0} -c "'
|
||||
'from certbot.compat import os;'
|
||||
'print(os.environ.get(\'CERTBOT_DOMAIN\'));'
|
||||
'print(os.environ.get(\'CERTBOT_IDENTIFIER\'));'
|
||||
'print(os.environ.get(\'CERTBOT_TOKEN\', \'notoken\'));'
|
||||
'print(os.environ.get(\'CERTBOT_VALIDATION\', \'novalidation\'));'
|
||||
'print(os.environ.get(\'CERTBOT_ALL_DOMAINS\'));'
|
||||
'print(os.environ.get(\'CERTBOT_ALL_IDENTIFIERS\'));'
|
||||
'print(os.environ.get(\'CERTBOT_REMAINING_CHALLENGES\'));"'
|
||||
.format(sys.executable))
|
||||
dns_expected = '{0}\n{1}\n{2}\n{3}\n{4}'.format(
|
||||
self.dns_achall.identifier.value, 'notoken',
|
||||
dns_expected = '{0}\n{1}\n{2}\n{3}\n{4}\n{5}\n{6}'.format(
|
||||
self.dns_achall.identifier.value,
|
||||
self.dns_achall.identifier.value,
|
||||
'notoken',
|
||||
self.dns_achall.validation(self.dns_achall.account_key),
|
||||
','.join(achall.identifier.value for achall in self.achalls),
|
||||
','.join(achall.identifier.value for achall in self.achalls),
|
||||
len(self.achalls) - self.achalls.index(self.dns_achall) - 1)
|
||||
http_expected = '{0}\n{1}\n{2}\n{3}\n{4}'.format(
|
||||
self.http_achall.identifier.value, self.http_achall.chall.encode('token'),
|
||||
http_expected = '{0}\n{1}\n{2}\n{3}\n{4}\n{5}\n{6}'.format(
|
||||
self.http_achall.identifier.value,
|
||||
self.http_achall.identifier.value,
|
||||
self.http_achall.chall.encode('token'),
|
||||
self.http_achall.validation(self.http_achall.account_key),
|
||||
','.join(achall.identifier.value for achall in self.achalls),
|
||||
','.join(achall.identifier.value for achall in self.achalls),
|
||||
len(self.achalls) - self.achalls.index(self.http_achall) - 1)
|
||||
|
||||
assert self.auth.perform(self.achalls) == \
|
||||
@@ -116,6 +124,7 @@ class AuthenticatorTest(test_util.TempDirTestCase):
|
||||
self.auth.cleanup([achall])
|
||||
assert os.environ['CERTBOT_AUTH_OUTPUT'] == 'foo'
|
||||
assert os.environ['CERTBOT_DOMAIN'] == achall.identifier.value
|
||||
assert os.environ['CERTBOT_IDENTIFIER'] == achall.identifier.value
|
||||
if isinstance(achall.chall, (challenges.HTTP01, challenges.DNS01)):
|
||||
assert os.environ['CERTBOT_VALIDATION'] == \
|
||||
achall.validation(achall.account_key)
|
||||
|
||||
@@ -1 +1 @@
|
||||
A new command line flag, --ip-address, has been added. This requests certificates with IP address SANs when using the standalone plugin. Note that for Let's Encrypt's implementation of IP address certificates, you'll also need to pass `--preferred-profile shortlived`.
|
||||
A new command line flag, --ip-address, has been added. This requests certificates with IP address SANs when using the standalone or manual plugin. Note that for Let's Encrypt's implementation of IP address certificates, you'll also need to pass `--preferred-profile shortlived`.
|
||||
|
||||
Reference in New Issue
Block a user