mirror of
https://github.com/certbot/certbot.git
synced 2026-06-03 13:59:02 -04:00
Change Nginx http01 to modify server block so the site doesn't stop serving while getting a cert
This commit is contained in:
parent
88d4a7da55
commit
b11c685339
4 changed files with 304 additions and 5 deletions
|
|
@ -25,7 +25,8 @@ from certbot.plugins import common
|
|||
from certbot_nginx import constants
|
||||
from certbot_nginx import nginxparser
|
||||
from certbot_nginx import parser
|
||||
from certbot_nginx import challenges as nginx_challenges
|
||||
from certbot_nginx import tls_sni_01
|
||||
from certbot_nginx import http_01
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
@ -853,8 +854,8 @@ class NginxConfigurator(common.Installer):
|
|||
"""
|
||||
self._chall_out += len(achalls)
|
||||
responses = [None] * len(achalls)
|
||||
sni_doer = nginx_challenges.NginxTlsSni01(self)
|
||||
http_doer = nginx_challenges.NginxHttp01(self)
|
||||
sni_doer = tls_sni_01.NginxTlsSni01(self)
|
||||
http_doer = http_01.NginxHttp01(self)
|
||||
|
||||
for i, achall in enumerate(achalls):
|
||||
# Currently also have chall_doer hold associated index of the
|
||||
|
|
|
|||
124
certbot-nginx/certbot_nginx/http_01.py
Normal file
124
certbot-nginx/certbot_nginx/http_01.py
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
"""A class that performs HTTP-01 challenges for Nginx"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
|
||||
import six
|
||||
|
||||
from acme import challenges
|
||||
|
||||
from certbot import errors
|
||||
from certbot.plugins import common
|
||||
|
||||
from certbot_nginx import obj
|
||||
from certbot_nginx import nginxparser
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class NginxHttp01(common.ChallengePerformer):
|
||||
"""HTTP-01 authenticator for Nginx
|
||||
|
||||
:ivar configurator: NginxConfigurator object
|
||||
:type configurator: :class:`~nginx.configurator.NginxConfigurator`
|
||||
|
||||
:ivar list achalls: Annotated
|
||||
class:`~certbot.achallenges.KeyAuthorizationAnnotatedChallenge`
|
||||
challenges
|
||||
|
||||
:param list indices: Meant to hold indices of challenges in a
|
||||
larger array. NginxHttp01 is capable of solving many challenges
|
||||
at once which causes an indexing issue within NginxConfigurator
|
||||
who must return all responses in order. Imagine NginxConfigurator
|
||||
maintaining state about where all of the http-01 Challenges,
|
||||
TLS-SNI-01 Challenges belong in the response array. This is an
|
||||
optional utility.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, configurator):
|
||||
super(NginxHttp01, self).__init__(configurator)
|
||||
|
||||
def perform(self):
|
||||
"""Perform a challenge on Nginx.
|
||||
|
||||
:returns: list of :class:`certbot.acme.challenges.HTTP01Response`
|
||||
:rtype: list
|
||||
|
||||
"""
|
||||
if not self.achalls:
|
||||
return []
|
||||
|
||||
responses = [x.response(x.account_key) for x in self.achalls]
|
||||
|
||||
# Set up the configuration
|
||||
self._mod_config()
|
||||
|
||||
# Save reversible changes
|
||||
self.configurator.save("HTTP Challenge", True)
|
||||
|
||||
return responses
|
||||
|
||||
def _add_bucket_directive(self):
|
||||
"""Modifies Nginx config to include server_names_hash_bucket_size directive."""
|
||||
root = self.configurator.parser.config_root
|
||||
|
||||
bucket_directive = ['\n', 'server_names_hash_bucket_size', ' ', '128']
|
||||
|
||||
main = self.configurator.parser.parsed[root]
|
||||
for line in main:
|
||||
if line[0] == ['http']:
|
||||
body = line[1]
|
||||
found_bucket = False
|
||||
posn = 0
|
||||
for inner_line in body:
|
||||
if inner_line[0] == bucket_directive[1]:
|
||||
if int(inner_line[1]) < int(bucket_directive[3]):
|
||||
body[posn] = bucket_directive
|
||||
found_bucket = True
|
||||
posn += 1
|
||||
if not found_bucket:
|
||||
body.insert(0, bucket_directive)
|
||||
break
|
||||
|
||||
def _mod_config(self):
|
||||
"""Modifies Nginx config to handle challenges.
|
||||
|
||||
"""
|
||||
self._add_bucket_directive()
|
||||
|
||||
for achall in self.achalls:
|
||||
self._mod_server_block(achall)
|
||||
|
||||
def _get_validation_path(self, achall):
|
||||
return os.sep + os.path.join(challenges.HTTP01.URI_ROOT_PATH, achall.chall.encode("token"))
|
||||
|
||||
def _mod_server_block(self, achall):
|
||||
"""Modifies a server block to respond to a challenge.
|
||||
|
||||
:param achall: Annotated HTTP-01 challenge
|
||||
:type achall:
|
||||
:class:`certbot.achallenges.KeyAuthorizationAnnotatedChallenge`
|
||||
|
||||
"""
|
||||
vhost = self.configurator.choose_vhost(achall.domain, create_if_no_match=True)
|
||||
validation = achall.validation(achall.account_key)
|
||||
validation_path = self._get_validation_path(achall)
|
||||
|
||||
location_directive = [[['location', ' ', '=', ' ', validation_path],
|
||||
[['default_type', ' ', 'text/plain'],
|
||||
['return', ' ', '200', ' ', validation]]]]
|
||||
log_directives = [# access and error logs necessary for
|
||||
# integration testing (non-root)
|
||||
['access_log', ' ', os.path.join(
|
||||
self.configurator.config.work_dir, 'access.log')],
|
||||
['error_log', ' ', os.path.join(
|
||||
self.configurator.config.work_dir, 'error.log')]
|
||||
]
|
||||
|
||||
self.configurator.parser.add_server_directives(vhost,
|
||||
location_directive, replace=False)
|
||||
if False: # TODO: detect if we're integration testing
|
||||
self.configurator.parser.add_server_directives(vhost,
|
||||
log_directives, replace=False)
|
||||
|
|
@ -524,7 +524,7 @@ def _is_ssl_on_directive(entry):
|
|||
def _add_directives(directives, replace, block):
|
||||
"""Adds or replaces directives in a config block.
|
||||
|
||||
When replace=False, it's an error to try and add a directive that already
|
||||
When replace=False, it's an error to try and add a nonrepeatable directive that already
|
||||
exists in the config block with a conflicting value.
|
||||
|
||||
When replace=True and a directive with the same name already exists in the
|
||||
|
|
@ -545,7 +545,7 @@ def _add_directives(directives, replace, block):
|
|||
|
||||
|
||||
INCLUDE = 'include'
|
||||
REPEATABLE_DIRECTIVES = set(['server_name', 'listen', INCLUDE])
|
||||
REPEATABLE_DIRECTIVES = set(['server_name', 'listen', INCLUDE, 'location'])
|
||||
COMMENT = ' managed by Certbot'
|
||||
COMMENT_BLOCK = [' ', '#', COMMENT]
|
||||
|
||||
|
|
|
|||
174
certbot-nginx/certbot_nginx/tls_sni_01.py
Normal file
174
certbot-nginx/certbot_nginx/tls_sni_01.py
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
"""A class that performs TLS-SNI-01 challenges for Nginx"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
|
||||
import six
|
||||
|
||||
from certbot import errors
|
||||
from certbot.plugins import common
|
||||
|
||||
from certbot_nginx import obj
|
||||
from certbot_nginx import nginxparser
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class NginxTlsSni01(common.TLSSNI01):
|
||||
"""TLS-SNI-01 authenticator for Nginx
|
||||
|
||||
:ivar configurator: NginxConfigurator object
|
||||
:type configurator: :class:`~nginx.configurator.NginxConfigurator`
|
||||
|
||||
:ivar list achalls: Annotated
|
||||
class:`~certbot.achallenges.KeyAuthorizationAnnotatedChallenge`
|
||||
challenges
|
||||
|
||||
:param list indices: Meant to hold indices of challenges in a
|
||||
larger array. NginxTlsSni01 is capable of solving many challenges
|
||||
at once which causes an indexing issue within NginxConfigurator
|
||||
who must return all responses in order. Imagine NginxConfigurator
|
||||
maintaining state about where all of the http-01 Challenges,
|
||||
TLS-SNI-01 Challenges belong in the response array. This is an
|
||||
optional utility.
|
||||
|
||||
:param str challenge_conf: location of the challenge config file
|
||||
|
||||
"""
|
||||
|
||||
def perform(self):
|
||||
"""Perform a challenge on Nginx.
|
||||
|
||||
:returns: list of :class:`certbot.acme.challenges.TLSSNI01Response`
|
||||
:rtype: list
|
||||
|
||||
"""
|
||||
if not self.achalls:
|
||||
return []
|
||||
|
||||
addresses = []
|
||||
default_addr = "{0} ssl".format(
|
||||
self.configurator.config.tls_sni_01_port)
|
||||
|
||||
ipv6, ipv6only = self.configurator.ipv6_info(
|
||||
self.configurator.config.tls_sni_01_port)
|
||||
|
||||
for achall in self.achalls:
|
||||
vhost = self.configurator.choose_vhost(achall.domain, create_if_no_match=True)
|
||||
|
||||
if vhost is not None and vhost.addrs:
|
||||
addresses.append(list(vhost.addrs))
|
||||
else:
|
||||
if ipv6:
|
||||
# If IPv6 is active in Nginx configuration
|
||||
ipv6_addr = "[::]:{0} ssl".format(
|
||||
self.configurator.config.tls_sni_01_port)
|
||||
if not ipv6only:
|
||||
# If ipv6only=on is not already present in the config
|
||||
ipv6_addr = ipv6_addr + " ipv6only=on"
|
||||
addresses.append([obj.Addr.fromstring(default_addr),
|
||||
obj.Addr.fromstring(ipv6_addr)])
|
||||
logger.info(("Using default addresses %s and %s for " +
|
||||
"TLSSNI01 authentication."),
|
||||
default_addr,
|
||||
ipv6_addr)
|
||||
else:
|
||||
addresses.append([obj.Addr.fromstring(default_addr)])
|
||||
logger.info("Using default address %s for TLSSNI01 authentication.",
|
||||
default_addr)
|
||||
|
||||
# Create challenge certs
|
||||
responses = [self._setup_challenge_cert(x) for x in self.achalls]
|
||||
|
||||
# Set up the configuration
|
||||
self._mod_config(addresses)
|
||||
|
||||
# Save reversible changes
|
||||
self.configurator.save("SNI Challenge", True)
|
||||
|
||||
return responses
|
||||
|
||||
def _mod_config(self, ll_addrs):
|
||||
"""Modifies Nginx config to include challenge server blocks.
|
||||
|
||||
:param list ll_addrs: list of lists of
|
||||
:class:`certbot_nginx.obj.Addr` to apply
|
||||
|
||||
:raises .MisconfigurationError:
|
||||
Unable to find a suitable HTTP block in which to include
|
||||
authenticator hosts.
|
||||
|
||||
"""
|
||||
# Add the 'include' statement for the challenges if it doesn't exist
|
||||
# already in the main config
|
||||
included = False
|
||||
include_directive = ['\n', 'include', ' ', self.challenge_conf]
|
||||
root = self.configurator.parser.config_root
|
||||
|
||||
bucket_directive = ['\n', 'server_names_hash_bucket_size', ' ', '128']
|
||||
|
||||
main = self.configurator.parser.parsed[root]
|
||||
for line in main:
|
||||
if line[0] == ['http']:
|
||||
body = line[1]
|
||||
found_bucket = False
|
||||
posn = 0
|
||||
for inner_line in body:
|
||||
if inner_line[0] == bucket_directive[1]:
|
||||
if int(inner_line[1]) < int(bucket_directive[3]):
|
||||
body[posn] = bucket_directive
|
||||
found_bucket = True
|
||||
posn += 1
|
||||
if not found_bucket:
|
||||
body.insert(0, bucket_directive)
|
||||
if include_directive not in body:
|
||||
body.insert(0, include_directive)
|
||||
included = True
|
||||
break
|
||||
if not included:
|
||||
raise errors.MisconfigurationError(
|
||||
'Certbot could not find an HTTP block to include '
|
||||
'TLS-SNI-01 challenges in %s.' % root)
|
||||
config = [self._make_server_block(pair[0], pair[1])
|
||||
for pair in six.moves.zip(self.achalls, ll_addrs)]
|
||||
config = nginxparser.UnspacedList(config)
|
||||
|
||||
self.configurator.reverter.register_file_creation(
|
||||
True, self.challenge_conf)
|
||||
|
||||
with open(self.challenge_conf, "w") as new_conf:
|
||||
nginxparser.dump(config, new_conf)
|
||||
|
||||
def _make_server_block(self, achall, addrs):
|
||||
"""Creates a server block for a challenge.
|
||||
|
||||
:param achall: Annotated TLS-SNI-01 challenge
|
||||
:type achall:
|
||||
:class:`certbot.achallenges.KeyAuthorizationAnnotatedChallenge`
|
||||
|
||||
:param list addrs: addresses of challenged domain
|
||||
:class:`list` of type :class:`~nginx.obj.Addr`
|
||||
|
||||
:returns: server block for the challenge host
|
||||
:rtype: list
|
||||
|
||||
"""
|
||||
document_root = os.path.join(
|
||||
self.configurator.config.work_dir, "tls_sni_01_page")
|
||||
|
||||
block = [['listen', ' ', addr.to_string(include_default=False)] for addr in addrs]
|
||||
|
||||
block.extend([['server_name', ' ',
|
||||
achall.response(achall.account_key).z_domain.decode('ascii')],
|
||||
# access and error logs necessary for
|
||||
# integration testing (non-root)
|
||||
['access_log', ' ', os.path.join(
|
||||
self.configurator.config.work_dir, 'access.log')],
|
||||
['error_log', ' ', os.path.join(
|
||||
self.configurator.config.work_dir, 'error.log')],
|
||||
['ssl_certificate', ' ', self.get_cert_path(achall)],
|
||||
['ssl_certificate_key', ' ', self.get_key_path(achall)],
|
||||
['include', ' ', self.configurator.mod_ssl_conf],
|
||||
[['location', ' ', '/'], [['root', ' ', document_root]]]])
|
||||
return [['server'], block]
|
||||
Loading…
Reference in a new issue