mirror of
https://github.com/certbot/certbot.git
synced 2026-07-28 08:45:22 +02:00
* Start of combined manual/script plugin * Return str from hooks.execute, not bytes * finish manual/script rewrite * delete old manual and script plugins * manually specify we want chall.token * use consistent quotes * specify chall for uri * s/script/hook * fix spacing on instructions * remove unneeded response argument * make achall more helpful * simplify perform * remove old test files * add start of manual_tests * fix ParseTest.test_help * stop using manual_test_mode in cli tests * Revert "make achall more helpful" This reverts commit54b01cea30. * use bad response/validation methods on achalls * simplify perform and cleanup environment * finish manual tests * Add HTTP manual hook integration test * add manual http scripts * Add manual DNS script integration test * remove references to the script plugin * they're hooks, not scripts * add --manual-public-ip-logging-ok to integration tests * use --pref-chall for dns integration * does dns work? * validate hooks * test hook validation * Revert "does dns work?" This reverts commit1224cc2961. * busy wait in manual-http-auth * remove DNS script test for now * Fix challenge prefix and add trailing . * Add comment about universal_newlines * Fix typo from0464ba2c4* fix nits and typos * Generalize HookCOmmandNotFound error * Add verify_exe_exists * Don't duplicate code in hooks.py * Revert changes to hooks.py * Use consistent hook error messages
166 lines
6.8 KiB
Python
166 lines
6.8 KiB
Python
"""Manual authenticator plugin"""
|
|
import os
|
|
|
|
import zope.component
|
|
import zope.interface
|
|
|
|
from acme import challenges
|
|
|
|
from certbot import interfaces
|
|
from certbot import errors
|
|
from certbot import hooks
|
|
from certbot.plugins import common
|
|
|
|
|
|
@zope.interface.implementer(interfaces.IAuthenticator)
|
|
@zope.interface.provider(interfaces.IPluginFactory)
|
|
class Authenticator(common.Plugin):
|
|
"""Manual authenticator
|
|
|
|
This plugin allows the user to perform the domain validation
|
|
challenge(s) themselves. This either be done manually by the user or
|
|
through shell scripts provided to Certbot.
|
|
|
|
"""
|
|
|
|
description = 'Manual configuration or run your own shell scripts'
|
|
hidden = True
|
|
long_description = (
|
|
'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 are '
|
|
'$CERTBOT_DOMAIN which contains the domain being authenticated, '
|
|
'$CERTBOT_VALIDATION which is the validation string, and '
|
|
'$CERTBOT_TOKEN which is the filename of the resource requested when '
|
|
'performing an HTTP-01 challenge. An additional cleanup script can '
|
|
'also be provided and can use the additional variable '
|
|
'$CERTBOT_AUTH_OUTPUT which contains the stdout output from the auth '
|
|
'script.')
|
|
_DNS_INSTRUCTIONS = """\
|
|
Please deploy a DNS TXT record under the name
|
|
{domain} with the following value:
|
|
|
|
{validation}
|
|
|
|
Once this is deployed,"""
|
|
_HTTP_INSTRUCTIONS = """\
|
|
Make sure your web server displays the following content at
|
|
{uri} before continuing:
|
|
|
|
{validation}
|
|
|
|
If you don't have HTTP server configured, you can run the following
|
|
command on the target server (as root):
|
|
|
|
mkdir -p /tmp/certbot/public_html/{achall.URI_ROOT_PATH}
|
|
cd /tmp/certbot/public_html
|
|
printf "%s" {validation} > {achall.URI_ROOT_PATH}/{encoded_token}
|
|
# run only once per server:
|
|
$(command -v python2 || command -v python2.7 || command -v python2.6) -c \\
|
|
"import BaseHTTPServer, SimpleHTTPServer; \\
|
|
s = BaseHTTPServer.HTTPServer(('', {port}), SimpleHTTPServer.SimpleHTTPRequestHandler); \\
|
|
s.serve_forever()" """
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super(Authenticator, self).__init__(*args, **kwargs)
|
|
self.env = dict()
|
|
|
|
@classmethod
|
|
def add_parser_arguments(cls, add):
|
|
add('auth-hook',
|
|
help='Path or command to execute for the authentication script')
|
|
add('cleanup-hook',
|
|
help='Path or command to execute for the cleanup script')
|
|
add('public-ip-logging-ok', action='store_true',
|
|
help='Automatically allows public IP logging (default: Ask)')
|
|
|
|
def prepare(self): # pylint: disable=missing-docstring
|
|
if self.config.noninteractive_mode and not self.conf('auth-hook'):
|
|
raise errors.PluginError(
|
|
'An authentication script must be provided with --{0} when '
|
|
'using the manual plugin non-interactively.'.format(
|
|
self.option_name('auth-hook')))
|
|
self._validate_hooks()
|
|
|
|
def _validate_hooks(self):
|
|
if self.config.validate_hooks:
|
|
for name in ('auth-hook', 'cleanup-hook'):
|
|
hook = self.conf(name)
|
|
if hook is not None:
|
|
hook_prefix = self.option_name(name)[:-len('-hook')]
|
|
hooks.validate_hook(hook, hook_prefix)
|
|
|
|
def more_info(self): # pylint: disable=missing-docstring,no-self-use
|
|
return (
|
|
'This plugin allows the user to customize setup for domain '
|
|
'validation challenges either through shell scripts provided by '
|
|
'the user or by performing the setup manually.')
|
|
|
|
def get_chall_pref(self, domain):
|
|
# pylint: disable=missing-docstring,no-self-use,unused-argument
|
|
return [challenges.HTTP01, challenges.DNS01]
|
|
|
|
def perform(self, achalls): # pylint: disable=missing-docstring
|
|
self._verify_ip_logging_ok()
|
|
|
|
if self.conf('auth-hook'):
|
|
perform_achall = self._perform_achall_with_script
|
|
else:
|
|
perform_achall = self._perform_achall_manually
|
|
|
|
responses = []
|
|
for achall in achalls:
|
|
perform_achall(achall)
|
|
responses.append(achall.response(achall.account_key))
|
|
return responses
|
|
|
|
def _verify_ip_logging_ok(self):
|
|
if not self.conf('public-ip-logging-ok'):
|
|
cli_flag = '--{0}'.format(self.option_name('public-ip-logging-ok'))
|
|
msg = ('NOTE: The IP of this machine will be publicly logged as '
|
|
"having requested this certificate. If you're running "
|
|
'certbot in manual mode on a machine that is not your '
|
|
"server, please ensure you're okay with that.\n\n"
|
|
'Are you OK with your IP being logged?')
|
|
display = zope.component.getUtility(interfaces.IDisplay)
|
|
if display.yesno(msg, cli_flag=cli_flag, force_interactive=True):
|
|
setattr(self.config, self.dest('public-ip-logging-ok'), True)
|
|
else:
|
|
raise errors.PluginError('Must agree to IP logging to proceed')
|
|
|
|
def _perform_achall_with_script(self, achall):
|
|
env = dict(CERTBOT_DOMAIN=achall.domain,
|
|
CERTBOT_VALIDATION=achall.validation(achall.account_key))
|
|
if isinstance(achall.chall, challenges.HTTP01):
|
|
env['CERTBOT_TOKEN'] = achall.chall.encode('token')
|
|
else:
|
|
os.environ.pop('CERTBOT_TOKEN', None)
|
|
os.environ.update(env)
|
|
_, out = hooks.execute(self.conf('auth-hook'))
|
|
env['CERTBOT_AUTH_OUTPUT'] = out.strip()
|
|
self.env[achall.domain] = env
|
|
|
|
def _perform_achall_manually(self, achall):
|
|
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(achall.domain), validation=validation)
|
|
else:
|
|
assert isinstance(achall.chall, challenges.DNS01)
|
|
msg = self._DNS_INSTRUCTIONS.format(
|
|
domain=achall.validation_domain_name(achall.domain),
|
|
validation=validation)
|
|
display = zope.component.getUtility(interfaces.IDisplay)
|
|
display.notification(msg, wrap=False, force_interactive=True)
|
|
|
|
def cleanup(self, achalls): # pylint: disable=missing-docstring
|
|
if self.conf('cleanup-hook'):
|
|
for achall in achalls:
|
|
env = self.env.pop(achall.domain)
|
|
if 'CERTBOT_TOKEN' not in env:
|
|
os.environ.pop('CERTBOT_TOKEN', None)
|
|
os.environ.update(env)
|
|
hooks.execute(self.conf('cleanup-hook'))
|