Merge branch 'master' into pebble-no-docker

# Conflicts:
#	certbot-ci/certbot_integration_tests/utils/acme_server.py
This commit is contained in:
Adrien Ferrand
2019-06-15 12:39:58 +02:00
11 changed files with 126 additions and 88 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
# Configuration for https://github.com/marketplace/stale
# Number of days of inactivity before an Issue or Pull Request becomes stale
daysUntilStale: 180
daysUntilStale: 365
# Number of days of inactivity before an Issue or Pull Request with the stale label is closed.
# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale.
+1 -1
View File
@@ -6,7 +6,7 @@ Certbot adheres to [Semantic Versioning](https://semver.org/).
### Added
*
* Turn off session tickets for nginx plugin by default
### Changed
@@ -1,6 +1,7 @@
#!/usr/bin/env python
"""Module to setup an ACME CA server environment able to run multiple tests in parallel"""
from __future__ import print_function
import json
import tempfile
import time
import os
@@ -10,9 +11,8 @@ import sys
from os.path import join
import requests
import json
from certbot_integration_tests.utils import misc, pebble_artifacts
from certbot_integration_tests.utils import misc, proxy, pebble_artifacts
from certbot_integration_tests.utils.constants import *
@@ -48,7 +48,7 @@ class ACMEServer(object):
def start(self):
"""Start the test stack"""
if self._proxy:
self._processes.extend(_prepare_traefik_proxy(self._workspace, self.acme_xdist))
self._processes.extend(_prepare_http_proxy(self.acme_xdist))
if self._acme_type == 'pebble':
self._processes.extend(_prepare_pebble_server(self._workspace, self.acme_xdist))
if self._acme_type == 'boulder':
@@ -163,62 +163,16 @@ def _prepare_boulder_server(workspace, acme_xdist):
return [process]
def _prepare_traefik_proxy(workspace, acme_xdist):
"""Configure and launch Traefik, the HTTP reverse proxy"""
print('=> Starting traefik instance deployment...')
instance_path = join(workspace, 'traefik')
traefik_subnet = '10.33.33'
try:
os.mkdir(instance_path)
def _prepare_http_proxy(acme_xdist):
"""Configure and launch an HTTP proxy"""
print('=> Configuring the HTTP proxy...')
mapping = {r'.+\.{0}\.wtf'.format(node): 'http://127.0.0.1:{0}'.format(port)
for node, port in acme_xdist['http_port'].items()}
command = [sys.executable, proxy.__file__, str(HTTP_01_PORT), json.dumps(mapping)]
process = _launch_process(command)
print('=> Finished configuring the HTTP proxy.')
with open(join(instance_path, 'docker-compose.yml'), 'w') as file_h:
file_h.write('''\
version: '3'
services:
traefik:
image: traefik
command: --api --rest
ports:
- {http_01_port}:80
- {traefik_api_port}:8080
networks:
traefiknet:
ipv4_address: {traefik_subnet}.2
networks:
traefiknet:
ipam:
config:
- subnet: {traefik_subnet}.0/24
'''.format(traefik_subnet=traefik_subnet,
traefik_api_port=TRAEFIK_API_PORT,
http_01_port=HTTP_01_PORT))
process = _launch_process(['docker-compose', 'up', '--force-recreate'], cwd=instance_path)
misc.check_until_timeout('http://localhost:{0}/api'.format(TRAEFIK_API_PORT))
config = {
'backends': {
node: {
'servers': {node: {'url': 'http://{0}.1:{1}'.format(traefik_subnet, port)}}
} for node, port in acme_xdist['http_port'].items()
},
'frontends': {
node: {
'backend': node, 'passHostHeader': True,
'routes': {node: {'rule': 'HostRegexp: {{subdomain:.+}}.{0}.wtf'.format(node)}}
} for node in acme_xdist['http_port'].keys()
}
}
response = requests.put('http://localhost:{0}/api/providers/rest'.format(TRAEFIK_API_PORT),
data=json.dumps(config))
response.raise_for_status()
print('=> Finished traefik instance deployment.')
return [process]
except BaseException:
print('Error while setting up traefik instance.')
raise
return [process]
def _launch_process(command, cwd=os.getcwd(), env=None):
@@ -2,7 +2,6 @@
HTTP_01_PORT = 5002
TLS_ALPN_01_PORT = 5001
CHALLTESTSRV_PORT = 8055
TRAEFIK_API_PORT = 8056
BOULDER_V1_DIRECTORY_URL = 'http://localhost:4000/directory'
BOULDER_V2_DIRECTORY_URL = 'http://localhost:4001/directory'
PEBBLE_DIRECTORY_URL = 'https://localhost:14000/dir'
@@ -0,0 +1,36 @@
#!/usr/bin/env python
import json
import sys
import re
import requests
from six.moves import BaseHTTPServer
from certbot_integration_tests.utils.misc import GracefulTCPServer
def _create_proxy(mapping):
class ProxyHandler(BaseHTTPServer.BaseHTTPRequestHandler):
def do_GET(self):
headers = {key.lower(): value for key, value in self.headers.items()}
backend = [backend for pattern, backend in mapping.items()
if re.match(pattern, headers['host'])][0]
response = requests.get(backend + self.path, headers=headers)
self.send_response(response.status_code)
for key, value in response.headers.items():
self.send_header(key, value)
self.end_headers()
self.wfile.write(response.content)
return ProxyHandler
if __name__ == '__main__':
http_port = int(sys.argv[1])
port_mapping = json.loads(sys.argv[2])
httpd = GracefulTCPServer(('', http_port), _create_proxy(port_mapping))
try:
httpd.serve_forever()
except KeyboardInterrupt:
pass
+1
View File
@@ -3,3 +3,4 @@ include README.rst
recursive-include docs *
recursive-include certbot_nginx/tests/testdata *
include certbot_nginx/options-ssl-nginx.conf
include certbot_nginx/options-ssl-nginx-old.conf
+19 -10
View File
@@ -6,6 +6,8 @@ import subprocess
import tempfile
import time
import pkg_resources
import OpenSSL
import zope.interface
@@ -120,6 +122,14 @@ class NginxConfigurator(common.Installer):
self.reverter.recovery_routine()
@property
def mod_ssl_conf_src(self):
"""Full absolute path to SSL configuration file source."""
config_filename = "options-ssl-nginx.conf"
if self.version < (1, 5, 9):
config_filename = "options-ssl-nginx-old.conf"
return pkg_resources.resource_filename("certbot_nginx", config_filename)
@property
def mod_ssl_conf(self):
"""Full absolute path to SSL configuration file."""
@@ -130,6 +140,11 @@ class NginxConfigurator(common.Installer):
"""Full absolute path to digest of updated SSL configuration file."""
return os.path.join(self.config.config_dir, constants.UPDATED_MOD_SSL_CONF_DIGEST)
def install_ssl_options_conf(self, options_ssl, options_ssl_digest):
"""Copy Certbot's SSL options file into the system's config dir if required."""
return common.install_version_controlled_file(options_ssl, options_ssl_digest,
self.mod_ssl_conf_src, constants.ALL_SSL_OPTIONS_HASHES)
# This is called in determine_authenticator and determine_installer
def prepare(self):
"""Prepare the authenticator/installer.
@@ -148,14 +163,14 @@ class NginxConfigurator(common.Installer):
self.parser = parser.NginxParser(self.conf('server-root'))
install_ssl_options_conf(self.mod_ssl_conf, self.updated_mod_ssl_conf_digest)
self.install_ssl_dhparams()
# Set Version
if self.version is None:
self.version = self.get_version()
self.install_ssl_options_conf(self.mod_ssl_conf, self.updated_mod_ssl_conf_digest)
self.install_ssl_dhparams()
# Prevent two Nginx plugins from modifying a config at once
try:
util.lock_dir_until_exit(self.conf('server-root'))
@@ -1131,12 +1146,6 @@ def nginx_restart(nginx_ctl, nginx_conf):
time.sleep(1)
def install_ssl_options_conf(options_ssl, options_ssl_digest):
"""Copy Certbot's SSL options file into the system's config dir if required."""
return common.install_version_controlled_file(options_ssl, options_ssl_digest,
constants.MOD_SSL_CONF_SRC, constants.ALL_SSL_OPTIONS_HASHES)
def _determine_default_server_root():
if os.environ.get("CERTBOT_DOCS") == "1":
default_server_root = "%s or %s" % (constants.LINUX_SERVER_ROOT,
+5 -8
View File
@@ -1,8 +1,6 @@
"""nginx plugin constants."""
import platform
import pkg_resources
FREEBSD_DARWIN_SERVER_ROOT = "/usr/local/etc/nginx"
LINUX_SERVER_ROOT = "/etc/nginx"
@@ -21,14 +19,13 @@ CLI_DEFAULTS = dict(
MOD_SSL_CONF_DEST = "options-ssl-nginx.conf"
"""Name of the mod_ssl config file as saved in `IConfig.config_dir`."""
MOD_SSL_CONF_SRC = pkg_resources.resource_filename(
"certbot_nginx", "options-ssl-nginx.conf")
"""Path to the nginx mod_ssl config file found in the Certbot
distribution."""
UPDATED_MOD_SSL_CONF_DIGEST = ".updated-options-ssl-nginx-conf-digest.txt"
"""Name of the hash of the updated or informed mod_ssl_conf as saved in `IConfig.config_dir`."""
SSL_OPTIONS_HASHES_NEW = [
'63e2bddebb174a05c9d8a7cf2adf72f7af04349ba59a1a925fe447f73b2f1abf',
]
"""SHA256 hashes of the contents of versions of MOD_SSL_CONF_SRC for nginx >= 1.5.9"""
ALL_SSL_OPTIONS_HASHES = [
'0f81093a1465e3d4eaa8b0c14e77b2a2e93568b0fc1351c2b87893a95f0de87c',
@@ -37,7 +34,7 @@ ALL_SSL_OPTIONS_HASHES = [
'7f95624dd95cf5afc708b9f967ee83a24b8025dc7c8d9df2b556bbc64256b3ff',
'394732f2bbe3e5e637c3fb5c6e980a1f1b90b01e2e8d6b7cff41dde16e2a756d',
'4b16fec2bcbcd8a2f3296d886f17f9953ffdcc0af54582452ca1e52f5f776f16',
]
] + SSL_OPTIONS_HASHES_NEW
"""SHA256 hashes of the contents of all versions of MOD_SSL_CONF_SRC"""
def os_constant(key):
@@ -0,0 +1,13 @@
# This file contains important security parameters. If you modify this file
# manually, Certbot will be unable to automatically provide future security
# updates. Instead, Certbot will print and log an error message with a path to
# the up-to-date file that you will need to refer to when manually updating
# this file.
ssl_session_cache shared:le_nginx_SSL:1m;
ssl_session_timeout 1440m;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_ciphers "ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS";
@@ -6,6 +6,7 @@
ssl_session_cache shared:le_nginx_SSL:1m;
ssl_session_timeout 1440m;
ssl_session_tickets off;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
@@ -13,7 +13,6 @@ from certbot import errors
from certbot.compat import os
from certbot.tests import util as certbot_test_util
from certbot_nginx import constants
from certbot_nginx import obj
from certbot_nginx import parser
from certbot_nginx.configurator import _redirect_block_for_domain
@@ -883,12 +882,11 @@ class InstallSslOptionsConfTest(util.NginxTest):
self.config_path, self.config_dir, self.work_dir, self.logs_dir)
def _call(self):
from certbot_nginx.configurator import install_ssl_options_conf
install_ssl_options_conf(self.config.mod_ssl_conf, self.config.updated_mod_ssl_conf_digest)
self.config.install_ssl_options_conf(self.config.mod_ssl_conf,
self.config.updated_mod_ssl_conf_digest)
def _current_ssl_options_hash(self):
from certbot_nginx.constants import MOD_SSL_CONF_SRC
return crypto_util.sha256sum(MOD_SSL_CONF_SRC)
return crypto_util.sha256sum(self.config.mod_ssl_conf_src)
def _assert_current_file(self):
self.assertTrue(os.path.isfile(self.config.mod_ssl_conf))
@@ -908,13 +906,33 @@ class InstallSslOptionsConfTest(util.NginxTest):
self._call()
self._assert_current_file()
def _mock_hash_except_ssl_conf_src(self, fake_hash):
# Write a bad file in place so that update tests fail if no update occurs.
# We're going to pretend this file (the currently installed conf file)
# actually hashes to `fake_hash` for the update tests.
with open(self.config.mod_ssl_conf, "w") as f:
f.write("bogus")
sha256 = crypto_util.sha256sum
def _hash(filename):
return sha256(filename) if filename == self.config.mod_ssl_conf_src else fake_hash
return _hash
def test_prev_file_updates_to_current(self):
from certbot_nginx.constants import ALL_SSL_OPTIONS_HASHES
with mock.patch('certbot.crypto_util.sha256sum') as mock_sha256:
mock_sha256.return_value = ALL_SSL_OPTIONS_HASHES[0]
with mock.patch('certbot.crypto_util.sha256sum',
new=self._mock_hash_except_ssl_conf_src(ALL_SSL_OPTIONS_HASHES[0])):
self._call()
self._assert_current_file()
def test_prev_file_updates_to_current_old_nginx(self):
from certbot_nginx.constants import ALL_SSL_OPTIONS_HASHES, SSL_OPTIONS_HASHES_NEW
self.config.version = (1, 5, 8)
with mock.patch('certbot.crypto_util.sha256sum',
new=self._mock_hash_except_ssl_conf_src(ALL_SSL_OPTIONS_HASHES[0])):
self._call()
self._assert_current_file()
self.assertTrue(self._current_ssl_options_hash() not in SSL_OPTIONS_HASHES_NEW)
def test_manually_modified_current_file_does_not_update(self):
with open(self.config.mod_ssl_conf, "a") as mod_ssl_conf:
mod_ssl_conf.write("a new line for the wrong hash\n")
@@ -922,7 +940,7 @@ class InstallSslOptionsConfTest(util.NginxTest):
self._call()
self.assertFalse(mock_logger.warning.called)
self.assertTrue(os.path.isfile(self.config.mod_ssl_conf))
self.assertEqual(crypto_util.sha256sum(constants.MOD_SSL_CONF_SRC),
self.assertEqual(crypto_util.sha256sum(self.config.mod_ssl_conf_src),
self._current_ssl_options_hash())
self.assertNotEqual(crypto_util.sha256sum(self.config.mod_ssl_conf),
self._current_ssl_options_hash())
@@ -937,7 +955,7 @@ class InstallSslOptionsConfTest(util.NginxTest):
self.assertEqual(mock_logger.warning.call_args[0][0],
"%s has been manually modified; updated file "
"saved to %s. We recommend updating %s for security purposes.")
self.assertEqual(crypto_util.sha256sum(constants.MOD_SSL_CONF_SRC),
self.assertEqual(crypto_util.sha256sum(self.config.mod_ssl_conf_src),
self._current_ssl_options_hash())
# only print warning once
with mock.patch("certbot.plugins.common.logger") as mock_logger:
@@ -950,6 +968,16 @@ class InstallSslOptionsConfTest(util.NginxTest):
"Constants.ALL_SSL_OPTIONS_HASHES must be appended"
" with the sha256 hash of self.config.mod_ssl_conf when it is updated.")
def test_old_nginx_version_uses_old_config(self):
self.config.version = (1, 5, 8)
self.assertEqual(os.path.basename(self.config.mod_ssl_conf_src),
"options-ssl-nginx-old.conf")
self._call()
self._assert_current_file()
self.config.version = (1, 5, 9)
self.assertEqual(os.path.basename(self.config.mod_ssl_conf_src),
"options-ssl-nginx.conf")
class DetermineDefaultServerRootTest(certbot_test_util.ConfigTestCase):
"""Tests for certbot_nginx.configurator._determine_default_server_root."""