mirror of
https://github.com/certbot/certbot.git
synced 2026-08-03 00:22:04 +02:00
IConfig, constants
This commit is contained in:
@@ -0,0 +1,5 @@
|
|||||||
|
:mod:`letsencrypt.client.constants`
|
||||||
|
-----------------------------------
|
||||||
|
|
||||||
|
.. automodule:: letsencrypt.client.constants
|
||||||
|
:members:
|
||||||
@@ -1,6 +1,13 @@
|
|||||||
"""Config for Let's Encrypt."""
|
"""Config for Let's Encrypt."""
|
||||||
import os.path
|
import os.path
|
||||||
|
|
||||||
|
import zope.component
|
||||||
|
|
||||||
|
from letsencrypt.client import interfaces
|
||||||
|
|
||||||
|
|
||||||
|
zope.component.moduleProvides(interfaces.IConfig)
|
||||||
|
|
||||||
|
|
||||||
ACME_SERVER = "letsencrypt-demo.org:443"
|
ACME_SERVER = "letsencrypt-demo.org:443"
|
||||||
"""CA hostname (and optionally :port).
|
"""CA hostname (and optionally :port).
|
||||||
@@ -10,9 +17,6 @@ If you create your own server... change this line
|
|||||||
Note: the server certificate must be trusted in order to avoid
|
Note: the server certificate must be trusted in order to avoid
|
||||||
further modifications to the client."""
|
further modifications to the client."""
|
||||||
|
|
||||||
# Directories
|
|
||||||
SERVER_ROOT = "/etc/apache2/"
|
|
||||||
"""Apache server root directory"""
|
|
||||||
|
|
||||||
CONFIG_DIR = "/etc/letsencrypt/"
|
CONFIG_DIR = "/etc/letsencrypt/"
|
||||||
"""Configuration file directory for letsencrypt"""
|
"""Configuration file directory for letsencrypt"""
|
||||||
@@ -35,69 +39,43 @@ CERT_KEY_BACKUP = os.path.join(WORK_DIR, "keys-certs/")
|
|||||||
REV_TOKENS_DIR = os.path.join(WORK_DIR, "revocation_tokens/")
|
REV_TOKENS_DIR = os.path.join(WORK_DIR, "revocation_tokens/")
|
||||||
"""Directory where all revocation tokens are saved."""
|
"""Directory where all revocation tokens are saved."""
|
||||||
|
|
||||||
KEY_DIR = os.path.join(SERVER_ROOT, "keys/")
|
KEY_DIR = os.path.join(CONFIG_DIR, "keys/")
|
||||||
"""Where all keys should be stored"""
|
"""Keys storage."""
|
||||||
|
|
||||||
CERT_DIR = os.path.join(SERVER_ROOT, "certs/")
|
CERT_DIR = os.path.join(CONFIG_DIR, "certs/")
|
||||||
"""Certificate storage"""
|
"""Certificate storage."""
|
||||||
|
|
||||||
# Files and extensions
|
|
||||||
OPTIONS_SSL_CONF = os.path.join(CONFIG_DIR, "options-ssl.conf")
|
|
||||||
"""Contains standard Apache SSL directives"""
|
|
||||||
|
|
||||||
LE_VHOST_EXT = "-le-ssl.conf"
|
LE_VHOST_EXT = "-le-ssl.conf"
|
||||||
"""Let's Encrypt SSL vhost configuration extension"""
|
"""Let's Encrypt SSL vhost configuration extension."""
|
||||||
|
|
||||||
CERT_PATH = CERT_DIR + "cert-letsencrypt.pem"
|
CERT_PATH = os.path.join(CERT_DIR, "cert-letsencrypt.pem")
|
||||||
"""Let's Encrypt cert file."""
|
"""Let's Encrypt cert file."""
|
||||||
|
|
||||||
CHAIN_PATH = CERT_DIR + "chain-letsencrypt.pem"
|
CHAIN_PATH = os.path.join(CERT_DIR, "chain-letsencrypt.pem")
|
||||||
"""Let's Encrypt chain file."""
|
"""Let's Encrypt chain file."""
|
||||||
|
|
||||||
INVALID_EXT = ".acme.invalid"
|
|
||||||
"""Invalid Extension"""
|
|
||||||
|
|
||||||
# Challenge Sets
|
|
||||||
EXCLUSIVE_CHALLENGES = [frozenset(["dvsni", "simpleHttps"])]
|
|
||||||
"""Mutually Exclusive Challenges - only solve 1"""
|
|
||||||
|
|
||||||
DV_CHALLENGES = frozenset(["dvsni", "simpleHttps", "dns"])
|
|
||||||
"""These are challenges that must be solved by an Authenticator object"""
|
|
||||||
|
|
||||||
CLIENT_CHALLENGES = frozenset(
|
|
||||||
["recoveryToken", "recoveryContact", "proofOfPossession"])
|
|
||||||
"""These are challenges that are handled by client.py"""
|
|
||||||
|
|
||||||
# Challenge Constants
|
|
||||||
S_SIZE = 32
|
|
||||||
"""Byte size of S"""
|
|
||||||
|
|
||||||
NONCE_SIZE = 16
|
|
||||||
"""byte size of Nonce"""
|
|
||||||
|
|
||||||
# Key Sizes
|
|
||||||
RSA_KEY_SIZE = 2048
|
RSA_KEY_SIZE = 2048
|
||||||
"""Key size"""
|
"""Key size"""
|
||||||
|
|
||||||
# Enhancements
|
|
||||||
ENHANCEMENTS = ["redirect", "http-header", "ocsp-stapling", "spdy"]
|
|
||||||
"""List of possible IInstaller enhancements.
|
|
||||||
|
|
||||||
List of expected options parameters:
|
|
||||||
redirect, None
|
|
||||||
http-header, TODO
|
|
||||||
ocsp-stapling, TODO
|
|
||||||
spdy, TODO
|
|
||||||
|
|
||||||
"""
|
|
||||||
# Apache Enhancement Arguments
|
|
||||||
REWRITE_HTTPS_ARGS = [
|
|
||||||
"^.*$", "https://%{SERVER_NAME}%{REQUEST_URI}", "[L,R=permanent]"]
|
|
||||||
"""Rewrite rule arguments used for redirections to https vhost"""
|
|
||||||
|
|
||||||
# Apache Interaction
|
|
||||||
APACHE_CTL = "/usr/sbin/apache2ctl"
|
APACHE_CTL = "/usr/sbin/apache2ctl"
|
||||||
"""Command used for configtest and version number."""
|
"""Path to the ``apache2ctl`` binary, used for ``configtest`` and
|
||||||
|
retrieving Apache2 version number."""
|
||||||
|
|
||||||
APACHE2 = "/etc/init.d/apache2"
|
APACHE_ENMOD = "apache"
|
||||||
"""Command used for reload and restart."""
|
"""Path to the Apache ``a2enmod`` binary."""
|
||||||
|
|
||||||
|
APACHE_INIT_SCRIPT = "/etc/init.d/apache2"
|
||||||
|
"""Path to the Apache init script (used for server reload/restart)."""
|
||||||
|
|
||||||
|
APACHE_REWRITE_HTTPS_ARGS = [
|
||||||
|
"^.*$", "https://%{SERVER_NAME}%{REQUEST_URI}", "[L,R=permanent]"]
|
||||||
|
"""Apache rewrite rule arguments used for redirections to https vhost"""
|
||||||
|
|
||||||
|
APACHE_SERVER_ROOT = "/etc/apache2/"
|
||||||
|
"""Apache server root directory"""
|
||||||
|
|
||||||
|
APACHE_MOD_SSL_CONF = os.path.join(CONFIG_DIR, "options-ssl.conf")
|
||||||
|
"""Contains standard Apache SSL directives"""
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
"""Apache Configuration based off of Augeas Configurator."""
|
"""Apache Configuration based off of Augeas Configurator."""
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import pkg_resources
|
|
||||||
import re
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
import socket
|
import socket
|
||||||
@@ -12,7 +11,7 @@ import zope.interface
|
|||||||
|
|
||||||
from letsencrypt.client import augeas_configurator
|
from letsencrypt.client import augeas_configurator
|
||||||
from letsencrypt.client import challenge_util
|
from letsencrypt.client import challenge_util
|
||||||
from letsencrypt.client import CONFIG
|
from letsencrypt.client import constants
|
||||||
from letsencrypt.client import errors
|
from letsencrypt.client import errors
|
||||||
from letsencrypt.client import interfaces
|
from letsencrypt.client import interfaces
|
||||||
from letsencrypt.client import le_util
|
from letsencrypt.client import le_util
|
||||||
@@ -63,6 +62,9 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
|||||||
The API of this class will change in the coming weeks as the exact
|
The API of this class will change in the coming weeks as the exact
|
||||||
needs of clients are clarified with the new and developing protocol.
|
needs of clients are clarified with the new and developing protocol.
|
||||||
|
|
||||||
|
:ivar config: Configuration.
|
||||||
|
:type config: :class:`~letsencrypt.client.interfaces.IConfig`
|
||||||
|
|
||||||
:ivar str server_root: Path to Apache root directory
|
:ivar str server_root: Path to Apache root directory
|
||||||
:ivar dict location: Path to various files associated
|
:ivar dict location: Path to various files associated
|
||||||
with the configuration
|
with the configuration
|
||||||
@@ -75,27 +77,27 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
|||||||
"""
|
"""
|
||||||
zope.interface.implements(interfaces.IAuthenticator, interfaces.IInstaller)
|
zope.interface.implements(interfaces.IAuthenticator, interfaces.IInstaller)
|
||||||
|
|
||||||
def __init__(self, server_root=CONFIG.SERVER_ROOT, direc=None,
|
def __init__(self, config, direc=None, version=None):
|
||||||
ssl_options=CONFIG.OPTIONS_SSL_CONF, version=None):
|
|
||||||
"""Initialize an Apache Configurator.
|
"""Initialize an Apache Configurator.
|
||||||
|
|
||||||
:param str server_root: the apache server root directory
|
|
||||||
:param dict direc: locations of various config directories
|
:param dict direc: locations of various config directories
|
||||||
(used mostly for unittesting)
|
(used mostly for unittesting)
|
||||||
:param str ssl_options: path of options-ssl.conf
|
|
||||||
(used mostly for unittesting)
|
|
||||||
:param tup version: version of Apache as a tuple (2, 4, 7)
|
:param tup version: version of Apache as a tuple (2, 4, 7)
|
||||||
(used mostly for unittesting)
|
(used mostly for unittesting)
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if direc is None:
|
self.config = config
|
||||||
direc = {"backup": CONFIG.BACKUP_DIR,
|
server_root = self.config.APACHE_SERVER_ROOT
|
||||||
"temp": CONFIG.TEMP_CHECKPOINT_DIR,
|
ssl_options = self.config.APACHE_MOD_SSL_CONF
|
||||||
"progress": CONFIG.IN_PROGRESS_DIR,
|
|
||||||
"config": CONFIG.CONFIG_DIR,
|
|
||||||
"work": CONFIG.WORK_DIR}
|
|
||||||
|
|
||||||
super(ApacheConfigurator, self).__init__(direc)
|
if direc is None:
|
||||||
|
direc = {"backup": self.config.BACKUP_DIR,
|
||||||
|
"temp": self.config.TEMP_CHECKPOINT_DIR,
|
||||||
|
"progress": self.config.IN_PROGRESS_DIR,
|
||||||
|
"config": self.config.CONFIG_DIR,
|
||||||
|
"work": self.config.WORK_DIR}
|
||||||
|
|
||||||
|
super(ApacheConfigurator, self).__init__(config, direc)
|
||||||
self.direc = direc
|
self.direc = direc
|
||||||
|
|
||||||
# Verify that all directories and files exist with proper permissions
|
# Verify that all directories and files exist with proper permissions
|
||||||
@@ -382,9 +384,10 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
|||||||
is appropriately listening on port 443.
|
is appropriately listening on port 443.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if not mod_loaded("ssl_module"):
|
if not mod_loaded("ssl_module", self.config.APACHE_CTL):
|
||||||
logging.info("Loading mod_ssl into Apache Server")
|
logging.info("Loading mod_ssl into Apache Server")
|
||||||
enable_mod("ssl")
|
enable_mod("ssl", self.config.APACHE_INIT_SCRIPT,
|
||||||
|
self.config.APACHE_ENMOD)
|
||||||
|
|
||||||
# Check for Listen 443
|
# Check for Listen 443
|
||||||
# Note: This could be made to also look for ip:443 combo
|
# Note: This could be made to also look for ip:443 combo
|
||||||
@@ -427,7 +430,8 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
|||||||
"""Makes an ssl_vhost version of a nonssl_vhost.
|
"""Makes an ssl_vhost version of a nonssl_vhost.
|
||||||
|
|
||||||
Duplicates vhost and adds default ssl options
|
Duplicates vhost and adds default ssl options
|
||||||
New vhost will reside as (nonssl_vhost.path) + CONFIG.LE_VHOST_EXT
|
New vhost will reside as (nonssl_vhost.path) + ``IConfig.LE_VHOST_EXT``
|
||||||
|
|
||||||
.. note:: This function saves the configuration
|
.. note:: This function saves the configuration
|
||||||
|
|
||||||
:param nonssl_vhost: Valid VH that doesn't have SSLEngine on
|
:param nonssl_vhost: Valid VH that doesn't have SSLEngine on
|
||||||
@@ -440,27 +444,24 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
|||||||
avail_fp = nonssl_vhost.filep
|
avail_fp = nonssl_vhost.filep
|
||||||
# Get filepath of new ssl_vhost
|
# Get filepath of new ssl_vhost
|
||||||
if avail_fp.endswith(".conf"):
|
if avail_fp.endswith(".conf"):
|
||||||
ssl_fp = avail_fp[:-(len(".conf"))] + CONFIG.LE_VHOST_EXT
|
ssl_fp = avail_fp[:-(len(".conf"))] + self.config.LE_VHOST_EXT
|
||||||
else:
|
else:
|
||||||
ssl_fp = avail_fp + CONFIG.LE_VHOST_EXT
|
ssl_fp = avail_fp + self.config.LE_VHOST_EXT
|
||||||
|
|
||||||
# First register the creation so that it is properly removed if
|
# First register the creation so that it is properly removed if
|
||||||
# configuration is rolled back
|
# configuration is rolled back
|
||||||
self.reverter.register_file_creation(False, ssl_fp)
|
self.reverter.register_file_creation(False, ssl_fp)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
orig_file = open(avail_fp, 'r')
|
with open(avail_fp, 'r') as orig_file:
|
||||||
new_file = open(ssl_fp, 'w')
|
with open(ssl_fp, 'w') as new_file:
|
||||||
new_file.write("<IfModule mod_ssl.c>\n")
|
new_file.write("<IfModule mod_ssl.c>\n")
|
||||||
for line in orig_file:
|
for line in orig_file:
|
||||||
new_file.write(line)
|
new_file.write(line)
|
||||||
new_file.write("</IfModule>\n")
|
new_file.write("</IfModule>\n")
|
||||||
except IOError:
|
except IOError:
|
||||||
logging.fatal("Error writing/reading to file in make_vhost_ssl")
|
logging.fatal("Error writing/reading to file in make_vhost_ssl")
|
||||||
sys.exit(49)
|
sys.exit(49)
|
||||||
finally:
|
|
||||||
orig_file.close()
|
|
||||||
new_file.close()
|
|
||||||
|
|
||||||
self.aug.load()
|
self.aug.load()
|
||||||
|
|
||||||
@@ -528,9 +529,9 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
|||||||
|
|
||||||
:param str domain: domain to enhance
|
:param str domain: domain to enhance
|
||||||
:param str enhancement: enhancement type defined in
|
:param str enhancement: enhancement type defined in
|
||||||
:class:`letsencrypt.client.CONFIG.ENHANCEMENTS`
|
:const:`~letsencrypt.client.constants.ENHANCEMENTS`
|
||||||
:param options: options for the enhancement
|
:param options: options for the enhancement
|
||||||
:type options: See :class:`letsencrypt.client.CONFIG.ENHANCEMENTS`
|
See :const:`~letsencrypt.client.constants.ENHANCEMENTS`
|
||||||
documentation for appropriate parameter.
|
documentation for appropriate parameter.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@@ -565,8 +566,9 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
|||||||
:rtype: (bool, :class:`letsencrypt.client.apache.obj.VirtualHost`)
|
:rtype: (bool, :class:`letsencrypt.client.apache.obj.VirtualHost`)
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if not mod_loaded("rewrite_module"):
|
if not mod_loaded("rewrite_module", self.config.APACHE_CTL):
|
||||||
enable_mod("rewrite")
|
enable_mod("rewrite", self.config.APACHE_INIT_SCRIPT,
|
||||||
|
self.config.APACHE_ENMOD)
|
||||||
|
|
||||||
general_v = self._general_vhost(ssl_vhost)
|
general_v = self._general_vhost(ssl_vhost)
|
||||||
if general_v is None:
|
if general_v is None:
|
||||||
@@ -590,8 +592,8 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
|||||||
"in {}".format(general_v.filep))
|
"in {}".format(general_v.filep))
|
||||||
# Add directives to server
|
# Add directives to server
|
||||||
self.parser.add_dir(general_v.path, "RewriteEngine", "On")
|
self.parser.add_dir(general_v.path, "RewriteEngine", "On")
|
||||||
self.parser.add_dir(
|
self.parser.add_dir(general_v.path, "RewriteRule",
|
||||||
general_v.path, "RewriteRule", CONFIG.REWRITE_HTTPS_ARGS)
|
self.config.APACHE_REWRITE_HTTPS_ARGS)
|
||||||
self.save_notes += ('Redirecting host in %s to ssl vhost in %s\n' %
|
self.save_notes += ('Redirecting host in %s to ssl vhost in %s\n' %
|
||||||
(general_v.filep, ssl_vhost.filep))
|
(general_v.filep, ssl_vhost.filep))
|
||||||
self.save()
|
self.save()
|
||||||
@@ -630,9 +632,10 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
|||||||
if not rewrite_path:
|
if not rewrite_path:
|
||||||
# "No existing redirection for virtualhost"
|
# "No existing redirection for virtualhost"
|
||||||
return False, -1
|
return False, -1
|
||||||
if len(rewrite_path) == len(CONFIG.REWRITE_HTTPS_ARGS):
|
if len(rewrite_path) == len(self.config.APACHE_REWRITE_HTTPS_ARGS):
|
||||||
for idx, match in enumerate(rewrite_path):
|
for idx, match in enumerate(rewrite_path):
|
||||||
if self.aug.get(match) != CONFIG.REWRITE_HTTPS_ARGS[idx]:
|
if (self.aug.get(match) !=
|
||||||
|
self.config.APACHE_REWRITE_HTTPS_ARGS[idx]):
|
||||||
# Not a letsencrypt https rewrite
|
# Not a letsencrypt https rewrite
|
||||||
return True, 2
|
return True, 2
|
||||||
# Existing letsencrypt https rewrite rule is in place
|
# Existing letsencrypt https rewrite rule is in place
|
||||||
@@ -681,7 +684,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
|||||||
"LogLevel warn\n"
|
"LogLevel warn\n"
|
||||||
"</VirtualHost>\n"
|
"</VirtualHost>\n"
|
||||||
% (servername, serveralias,
|
% (servername, serveralias,
|
||||||
" ".join(CONFIG.REWRITE_HTTPS_ARGS)))
|
" ".join(self.config.APACHE_REWRITE_HTTPS_ARGS)))
|
||||||
|
|
||||||
# Write out the file
|
# Write out the file
|
||||||
# This is the default name
|
# This is the default name
|
||||||
@@ -876,14 +879,14 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
|||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def restart(self): # pylint: disable=no-self-use
|
def restart(self):
|
||||||
"""Restarts apache server.
|
"""Restarts apache server.
|
||||||
|
|
||||||
:returns: Success
|
:returns: Success
|
||||||
:rtype: bool
|
:rtype: bool
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return apache_restart()
|
return apache_restart(self.config.APACHE_INIT_SCRIPT)
|
||||||
|
|
||||||
def config_test(self): # pylint: disable=no-self-use
|
def config_test(self): # pylint: disable=no-self-use
|
||||||
"""Check the configuration of Apache for errors.
|
"""Check the configuration of Apache for errors.
|
||||||
@@ -894,7 +897,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
proc = subprocess.Popen(
|
proc = subprocess.Popen(
|
||||||
['sudo', '/usr/sbin/apache2ctl', 'configtest'],
|
['sudo', self.config.APACHE_CTL, 'configtest'], # TODO: sudo?
|
||||||
stdout=subprocess.PIPE,
|
stdout=subprocess.PIPE,
|
||||||
stderr=subprocess.PIPE)
|
stderr=subprocess.PIPE)
|
||||||
stdout, stderr = proc.communicate()
|
stdout, stderr = proc.communicate()
|
||||||
@@ -925,13 +928,13 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
proc = subprocess.Popen(
|
proc = subprocess.Popen(
|
||||||
[CONFIG.APACHE_CTL, '-v'],
|
[self.config.APACHE_CTL, '-v'],
|
||||||
stdout=subprocess.PIPE,
|
stdout=subprocess.PIPE,
|
||||||
stderr=subprocess.PIPE)
|
stderr=subprocess.PIPE)
|
||||||
text = proc.communicate()[0]
|
text = proc.communicate()[0]
|
||||||
except (OSError, ValueError):
|
except (OSError, ValueError):
|
||||||
raise errors.LetsEncryptConfiguratorError(
|
raise errors.LetsEncryptConfiguratorError(
|
||||||
"Unable to run %s -v" % CONFIG.APACHE_CTL)
|
"Unable to run %s -v" % self.config.APACHE_CTL)
|
||||||
|
|
||||||
regex = re.compile(r"Apache/([0-9\.]*)", re.IGNORECASE)
|
regex = re.compile(r"Apache/([0-9\.]*)", re.IGNORECASE)
|
||||||
matches = regex.findall(text)
|
matches = regex.findall(text)
|
||||||
@@ -1012,47 +1015,51 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
|||||||
self.restart()
|
self.restart()
|
||||||
|
|
||||||
|
|
||||||
def enable_mod(mod_name):
|
def enable_mod(mod_name, apache_init_script, apache_enmod):
|
||||||
"""Enables module in Apache.
|
"""Enables module in Apache.
|
||||||
|
|
||||||
Both enables and restarts Apache so module is active.
|
Both enables and restarts Apache so module is active.
|
||||||
|
|
||||||
:param str mod_name: Name of the module to enable
|
:param str mod_name: Name of the module to enable.
|
||||||
|
:param str apache_init_script: Path to the Apache init script.
|
||||||
|
:param str apache_enmod: Path to the Apache a2enmod script.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# Use check_output so the command will finish before reloading
|
# Use check_output so the command will finish before reloading
|
||||||
# TODO: a2enmod is debian specific...
|
# TODO: a2enmod is debian specific...
|
||||||
subprocess.check_call(["sudo", "a2enmod", mod_name],
|
subprocess.check_call(["sudo", apache_enmod, mod_name], # TODO: sudo?
|
||||||
stdout=open("/dev/null", 'w'),
|
stdout=open("/dev/null", 'w'),
|
||||||
stderr=open("/dev/null", 'w'))
|
stderr=open("/dev/null", 'w'))
|
||||||
apache_restart()
|
apache_restart(apache_init_script)
|
||||||
except (OSError, subprocess.CalledProcessError) as err:
|
except (OSError, subprocess.CalledProcessError) as err:
|
||||||
logging.error("Error enabling mod_%s", mod_name)
|
logging.error("Error enabling mod_%s", mod_name)
|
||||||
logging.error("Exception: %s", err)
|
logging.error("Exception: %s", err)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
def mod_loaded(module):
|
def mod_loaded(module, apache_ctl):
|
||||||
"""Checks to see if mod_ssl is loaded
|
"""Checks to see if mod_ssl is loaded
|
||||||
|
|
||||||
Uses CONFIG.APACHE_CTL to get loaded module list. This also effectively
|
Uses ``apache_ctl`` to get loaded module list. This also effectively
|
||||||
serves as a config_test.
|
serves as a config_test.
|
||||||
|
|
||||||
|
:param str apache_ctl: Path to apache2ctl binary.
|
||||||
|
|
||||||
:returns: If ssl_module is included and active in Apache
|
:returns: If ssl_module is included and active in Apache
|
||||||
:rtype: bool
|
:rtype: bool
|
||||||
|
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
proc = subprocess.Popen(
|
proc = subprocess.Popen(
|
||||||
[CONFIG.APACHE_CTL, '-M'],
|
[apache_ctl, '-M'],
|
||||||
stdout=subprocess.PIPE,
|
stdout=subprocess.PIPE,
|
||||||
stderr=subprocess.PIPE)
|
stderr=subprocess.PIPE)
|
||||||
stdout, stderr = proc.communicate()
|
stdout, stderr = proc.communicate()
|
||||||
|
|
||||||
except (OSError, ValueError):
|
except (OSError, ValueError):
|
||||||
logging.error(
|
logging.error(
|
||||||
"Error accessing %s for loaded modules!", CONFIG.APACHE_CTL)
|
"Error accessing %s for loaded modules!", apache_ctl)
|
||||||
raise errors.LetsEncryptConfiguratorError(
|
raise errors.LetsEncryptConfiguratorError(
|
||||||
"Error accessing loaded modules")
|
"Error accessing loaded modules")
|
||||||
# Small errors that do not impede
|
# Small errors that do not impede
|
||||||
@@ -1067,9 +1074,11 @@ def mod_loaded(module):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def apache_restart():
|
def apache_restart(apache_init_script):
|
||||||
"""Restarts the Apache Server.
|
"""Restarts the Apache Server.
|
||||||
|
|
||||||
|
:param str apache_init_script: Path to the Apache init script.
|
||||||
|
|
||||||
.. todo:: Try to use reload instead. (This caused timing problems before)
|
.. todo:: Try to use reload instead. (This caused timing problems before)
|
||||||
|
|
||||||
.. todo:: On failure, this should be a recovery_routine call with another
|
.. todo:: On failure, this should be a recovery_routine call with another
|
||||||
@@ -1081,7 +1090,7 @@ def apache_restart():
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
proc = subprocess.Popen([CONFIG.APACHE2, 'restart'],
|
proc = subprocess.Popen([apache_init_script, 'restart'],
|
||||||
stdout=subprocess.PIPE,
|
stdout=subprocess.PIPE,
|
||||||
stderr=subprocess.PIPE)
|
stderr=subprocess.PIPE)
|
||||||
stdout, stderr = proc.communicate()
|
stdout, stderr = proc.communicate()
|
||||||
@@ -1138,6 +1147,4 @@ def temp_install(options_ssl):
|
|||||||
|
|
||||||
# Check to make sure options-ssl.conf is installed
|
# Check to make sure options-ssl.conf is installed
|
||||||
if not os.path.isfile(options_ssl):
|
if not os.path.isfile(options_ssl):
|
||||||
dist_conf = pkg_resources.resource_filename(
|
shutil.copyfile(constants.APACHE_MOD_SSL_CONF, options_ssl)
|
||||||
__name__, os.path.basename(options_ssl))
|
|
||||||
shutil.copyfile(dist_conf, options_ssl)
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import logging
|
|||||||
import os
|
import os
|
||||||
|
|
||||||
from letsencrypt.client import challenge_util
|
from letsencrypt.client import challenge_util
|
||||||
from letsencrypt.client import CONFIG
|
from letsencrypt.client import constants
|
||||||
|
|
||||||
from letsencrypt.client.apache import parser
|
from letsencrypt.client.apache import parser
|
||||||
|
|
||||||
@@ -12,7 +12,8 @@ class ApacheDvsni(object):
|
|||||||
"""Class performs DVSNI challenges within the Apache configurator.
|
"""Class performs DVSNI challenges within the Apache configurator.
|
||||||
|
|
||||||
:ivar config: ApacheConfigurator object
|
:ivar config: ApacheConfigurator object
|
||||||
:type config: :class:`letsencrypt.client.apache.configurator`
|
:type config:
|
||||||
|
:class:`letsencrypt.client.apache.configurator.ApacheConfigurator`
|
||||||
|
|
||||||
:ivar dvsni_chall: Data required for challenges.
|
:ivar dvsni_chall: Data required for challenges.
|
||||||
where DvsniChall tuples have the following fields
|
where DvsniChall tuples have the following fields
|
||||||
@@ -165,7 +166,7 @@ class ApacheDvsni(object):
|
|||||||
"""
|
"""
|
||||||
ips = " ".join(str(i) for i in ip_addrs)
|
ips = " ".join(str(i) for i in ip_addrs)
|
||||||
return ("<VirtualHost " + ips + ">\n"
|
return ("<VirtualHost " + ips + ">\n"
|
||||||
"ServerName " + nonce + CONFIG.INVALID_EXT + "\n"
|
"ServerName " + nonce + constants.DVSNI_DOMAIN_SUFFIX + "\n"
|
||||||
"UseCanonicalName on\n"
|
"UseCanonicalName on\n"
|
||||||
"SSLStrictSNIVHostCheck on\n"
|
"SSLStrictSNIVHostCheck on\n"
|
||||||
"\n"
|
"\n"
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import logging
|
|||||||
|
|
||||||
import augeas
|
import augeas
|
||||||
|
|
||||||
from letsencrypt.client import CONFIG
|
|
||||||
from letsencrypt.client import reverter
|
from letsencrypt.client import reverter
|
||||||
|
|
||||||
|
|
||||||
@@ -20,18 +19,20 @@ class AugeasConfigurator(object):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, direc=None):
|
def __init__(self, config, direc=None):
|
||||||
"""Initialize Augeas Configurator.
|
"""Initialize Augeas Configurator.
|
||||||
|
|
||||||
|
:param config: Configuration.
|
||||||
|
:type config: :class:`~letsencrypt.client.interfaces.IConfig`
|
||||||
|
|
||||||
:param dict direc: location of save directories
|
:param dict direc: location of save directories
|
||||||
(used mostly for testing)
|
(used mostly for testing)
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not direc:
|
if not direc:
|
||||||
direc = {"backup": CONFIG.BACKUP_DIR,
|
direc = {"backup": config.BACKUP_DIR,
|
||||||
"temp": CONFIG.TEMP_CHECKPOINT_DIR,
|
"temp": config.TEMP_CHECKPOINT_DIR,
|
||||||
"progress": CONFIG.IN_PROGRESS_DIR}
|
"progress": config.IN_PROGRESS_DIR}
|
||||||
|
|
||||||
# Set Augeas flags to not save backup (we do it ourselves)
|
# Set Augeas flags to not save backup (we do it ourselves)
|
||||||
# Set Augeas to not load anything by default
|
# Set Augeas to not load anything by default
|
||||||
@@ -43,7 +44,7 @@ class AugeasConfigurator(object):
|
|||||||
# This needs to occur before VirtualHost objects are setup...
|
# This needs to occur before VirtualHost objects are setup...
|
||||||
# because this will change the underlying configuration and potential
|
# because this will change the underlying configuration and potential
|
||||||
# vhosts
|
# vhosts
|
||||||
self.reverter = reverter.Reverter(direc)
|
self.reverter = reverter.Reverter(config, direc)
|
||||||
self.reverter.recovery_routine()
|
self.reverter.recovery_routine()
|
||||||
|
|
||||||
def check_parsing_errors(self, lens):
|
def check_parsing_errors(self, lens):
|
||||||
|
|||||||
@@ -3,18 +3,20 @@ import logging
|
|||||||
import sys
|
import sys
|
||||||
|
|
||||||
from letsencrypt.client import acme
|
from letsencrypt.client import acme
|
||||||
from letsencrypt.client import CONFIG
|
|
||||||
from letsencrypt.client import challenge_util
|
from letsencrypt.client import challenge_util
|
||||||
|
from letsencrypt.client import constants
|
||||||
from letsencrypt.client import errors
|
from letsencrypt.client import errors
|
||||||
|
|
||||||
|
|
||||||
class AuthHandler(object): # pylint: disable=too-many-instance-attributes
|
class AuthHandler(object): # pylint: disable=too-many-instance-attributes
|
||||||
"""ACME Authorization Handler for a client.
|
"""ACME Authorization Handler for a client.
|
||||||
|
|
||||||
:ivar dv_auth: Authenticator capable of solving CONFIG.DV_CHALLENGES
|
:ivar dv_auth: Authenticator capable of solving
|
||||||
|
:const:`~letsencrypt.client.constants.DV_CHALLENGES`
|
||||||
:type dv_auth: :class:`letsencrypt.client.interfaces.IAuthenticator`
|
:type dv_auth: :class:`letsencrypt.client.interfaces.IAuthenticator`
|
||||||
|
|
||||||
:ivar client_auth: Authenticator capable of solving CONFIG.CLIENT_CHALLENGES
|
:ivar client_auth: Authenticator capable of solving
|
||||||
|
:const:`~letsencrypt.client_auth.constants.CLIENT_CHALLENGES`
|
||||||
:type client_auth: :class:`letsencrypt.client.interfaces.IAuthenticator`
|
:type client_auth: :class:`letsencrypt.client.interfaces.IAuthenticator`
|
||||||
|
|
||||||
:ivar network: Network object for sending and receiving authorization
|
:ivar network: Network object for sending and receiving authorization
|
||||||
@@ -238,12 +240,12 @@ class AuthHandler(object): # pylint: disable=too-many-instance-attributes
|
|||||||
chall = challenges[index]
|
chall = challenges[index]
|
||||||
|
|
||||||
# Authenticator Challenges
|
# Authenticator Challenges
|
||||||
if chall["type"] in CONFIG.DV_CHALLENGES:
|
if chall["type"] in constants.DV_CHALLENGES:
|
||||||
dv_chall.append(challenge_util.IndexedChall(
|
dv_chall.append(challenge_util.IndexedChall(
|
||||||
self._construct_dv_chall(chall, domain), index))
|
self._construct_dv_chall(chall, domain), index))
|
||||||
|
|
||||||
# Client Challenges
|
# Client Challenges
|
||||||
elif chall["type"] in CONFIG.CLIENT_CHALLENGES:
|
elif chall["type"] in constants.CLIENT_CHALLENGES:
|
||||||
client_chall.append(challenge_util.IndexedChall(
|
client_chall.append(challenge_util.IndexedChall(
|
||||||
self._construct_client_chall(chall, domain), index))
|
self._construct_client_chall(chall, domain), index))
|
||||||
|
|
||||||
@@ -430,7 +432,7 @@ def _find_dumb_path(challenges, preferences):
|
|||||||
def is_preferred(offered_challenge_type, path):
|
def is_preferred(offered_challenge_type, path):
|
||||||
"""Return whether or not the challenge is preferred in path."""
|
"""Return whether or not the challenge is preferred in path."""
|
||||||
for _, challenge_type in path:
|
for _, challenge_type in path:
|
||||||
for mutually_exclusive in CONFIG.EXCLUSIVE_CHALLENGES:
|
for mutually_exclusive in constants.EXCLUSIVE_CHALLENGES:
|
||||||
# Second part is in case we eventually allow multiple names
|
# Second part is in case we eventually allow multiple names
|
||||||
# to be challenges at the same time
|
# to be challenges at the same time
|
||||||
if (challenge_type in mutually_exclusive and
|
if (challenge_type in mutually_exclusive and
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import hashlib
|
|||||||
|
|
||||||
from Crypto import Random
|
from Crypto import Random
|
||||||
|
|
||||||
from letsencrypt.client import CONFIG
|
from letsencrypt.client import constants
|
||||||
from letsencrypt.client import crypto_util
|
from letsencrypt.client import crypto_util
|
||||||
from letsencrypt.client import le_util
|
from letsencrypt.client import le_util
|
||||||
|
|
||||||
@@ -44,14 +44,14 @@ def dvsni_gen_cert(name, r_b64, nonce, key):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
# Generate S
|
# Generate S
|
||||||
dvsni_s = Random.get_random_bytes(CONFIG.S_SIZE)
|
dvsni_s = Random.get_random_bytes(constants.S_SIZE)
|
||||||
dvsni_r = le_util.jose_b64decode(r_b64)
|
dvsni_r = le_util.jose_b64decode(r_b64)
|
||||||
|
|
||||||
# Generate extension
|
# Generate extension
|
||||||
ext = _dvsni_gen_ext(dvsni_r, dvsni_s)
|
ext = _dvsni_gen_ext(dvsni_r, dvsni_s)
|
||||||
|
|
||||||
cert_pem = crypto_util.make_ss_cert(
|
cert_pem = crypto_util.make_ss_cert(
|
||||||
key.pem, [nonce + CONFIG.INVALID_EXT, name, ext])
|
key.pem, [nonce + constants.DVSNI_DOMAIN_SUFFIX, name, ext])
|
||||||
|
|
||||||
return cert_pem, le_util.jose_b64encode(dvsni_s)
|
return cert_pem, le_util.jose_b64encode(dvsni_s)
|
||||||
|
|
||||||
@@ -62,7 +62,7 @@ def _dvsni_gen_ext(dvsni_r, dvsni_s):
|
|||||||
:param bytearray dvsni_r: DVSNI r value
|
:param bytearray dvsni_r: DVSNI r value
|
||||||
:param bytearray dvsni_s: DVSNI s value
|
:param bytearray dvsni_s: DVSNI s value
|
||||||
|
|
||||||
:returns: z + CONFIG.INVALID_EXT
|
:returns: z + :const:`~letsencrypt.client.constants.DVSNI_DOMAIN_SUFFIX`
|
||||||
:rtype: str
|
:rtype: str
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@@ -70,4 +70,4 @@ def _dvsni_gen_ext(dvsni_r, dvsni_s):
|
|||||||
z_base.update(dvsni_r)
|
z_base.update(dvsni_r)
|
||||||
z_base.update(dvsni_s)
|
z_base.update(dvsni_s)
|
||||||
|
|
||||||
return z_base.hexdigest() + CONFIG.INVALID_EXT
|
return z_base.hexdigest() + constants.DVSNI_DOMAIN_SUFFIX
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import zope.component
|
|||||||
from letsencrypt.client import acme
|
from letsencrypt.client import acme
|
||||||
from letsencrypt.client import auth_handler
|
from letsencrypt.client import auth_handler
|
||||||
from letsencrypt.client import client_authenticator
|
from letsencrypt.client import client_authenticator
|
||||||
from letsencrypt.client import CONFIG
|
|
||||||
from letsencrypt.client import crypto_util
|
from letsencrypt.client import crypto_util
|
||||||
from letsencrypt.client import errors
|
from letsencrypt.client import errors
|
||||||
from letsencrypt.client import interfaces
|
from letsencrypt.client import interfaces
|
||||||
@@ -40,6 +39,9 @@ class Client(object):
|
|||||||
:ivar installer: Object supporting the IInstaller interface.
|
:ivar installer: Object supporting the IInstaller interface.
|
||||||
:type installer: :class:`letsencrypt.client.interfaces.IInstaller`
|
:type installer: :class:`letsencrypt.client.interfaces.IInstaller`
|
||||||
|
|
||||||
|
:ivar config: Configuration.
|
||||||
|
:type config: :class:`~letsencrypt.client.interfaces.IConfig`
|
||||||
|
|
||||||
"""
|
"""
|
||||||
zope.interface.implements(interfaces.IAuthenticator)
|
zope.interface.implements(interfaces.IAuthenticator)
|
||||||
|
|
||||||
@@ -47,12 +49,12 @@ class Client(object):
|
|||||||
# Note: form is the type of data, "pem" or "der"
|
# Note: form is the type of data, "pem" or "der"
|
||||||
CSR = collections.namedtuple("CSR", "file data form")
|
CSR = collections.namedtuple("CSR", "file data form")
|
||||||
|
|
||||||
def __init__(self, server, authkey, dv_auth, installer):
|
def __init__(self, server, authkey, dv_auth, installer, config):
|
||||||
"""Initialize a client.
|
"""Initialize a client.
|
||||||
|
|
||||||
:param str server: CA server to contact
|
:param str server: CA server to contact
|
||||||
:param dv_auth: IAuthenticator Interface that can solve the
|
:param dv_auth: IAuthenticator that can solve the
|
||||||
CONFIG.DV_CHALLENGES
|
:const:`letsencrypt.client.constants.DV_CHALLENGES`
|
||||||
:type dv_auth: :class:`letsencrypt.client.interfaces.IAuthenticator`
|
:type dv_auth: :class:`letsencrypt.client.interfaces.IAuthenticator`
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@@ -61,30 +63,32 @@ class Client(object):
|
|||||||
|
|
||||||
self.installer = installer
|
self.installer = installer
|
||||||
|
|
||||||
|
self.config = config
|
||||||
|
|
||||||
if dv_auth is not None:
|
if dv_auth is not None:
|
||||||
client_auth = client_authenticator.ClientAuthenticator(server)
|
client_auth = client_authenticator.ClientAuthenticator(
|
||||||
|
server, config)
|
||||||
self.auth_handler = auth_handler.AuthHandler(
|
self.auth_handler = auth_handler.AuthHandler(
|
||||||
dv_auth, client_auth, self.network)
|
dv_auth, client_auth, self.network)
|
||||||
else:
|
else:
|
||||||
self.auth_handler = None
|
self.auth_handler = None
|
||||||
|
|
||||||
def obtain_certificate(self, domains, csr=None,
|
def obtain_certificate(self, domains, csr=None):
|
||||||
cert_path=CONFIG.CERT_PATH,
|
|
||||||
chain_path=CONFIG.CHAIN_PATH):
|
|
||||||
"""Obtains a certificate from the ACME server.
|
"""Obtains a certificate from the ACME server.
|
||||||
|
|
||||||
:param str domains: list of domains to get a certificate
|
:param str domains: list of domains to get a certificate
|
||||||
|
|
||||||
:param csr: CSR must contain requested domains, the key used to generate
|
:param csr: CSR must contain requested domains, the key used to generate
|
||||||
this CSR can be different than self.authkey
|
this CSR can be different than self.authkey
|
||||||
:type csr: :class:`CSR`
|
:type csr: :class:`CSR`
|
||||||
|
|
||||||
:param str cert_path: Full desired path to end certificate.
|
|
||||||
:param str chain_path: Full desired path to end chain file.
|
|
||||||
|
|
||||||
:returns: cert_file, chain_file (paths to respective files)
|
:returns: cert_file, chain_file (paths to respective files)
|
||||||
:rtype: `tuple` of `str`
|
:rtype: `tuple` of `str`
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
cert_path = self.config.CERT_PATH
|
||||||
|
chain_path = self.config.CHAIN_PATH
|
||||||
|
|
||||||
if self.auth_handler is None:
|
if self.auth_handler is None:
|
||||||
logging.warning("Unable to obtain a certificate, because client "
|
logging.warning("Unable to obtain a certificate, because client "
|
||||||
"does not have a valid auth handler.")
|
"does not have a valid auth handler.")
|
||||||
@@ -99,7 +103,7 @@ class Client(object):
|
|||||||
|
|
||||||
# Create CSR from names
|
# Create CSR from names
|
||||||
if csr is None:
|
if csr is None:
|
||||||
csr = init_csr(self.authkey, domains)
|
csr = init_csr(self.authkey, domains, self.config.CERT_DIR)
|
||||||
|
|
||||||
# Retrieve certificate
|
# Retrieve certificate
|
||||||
certificate_dict = self.acme_certificate(csr.data)
|
certificate_dict = self.acme_certificate(csr.data)
|
||||||
@@ -241,8 +245,8 @@ class Client(object):
|
|||||||
:rtype: bool
|
:rtype: bool
|
||||||
|
|
||||||
"""
|
"""
|
||||||
list_file = os.path.join(CONFIG.CERT_KEY_BACKUP, "LIST")
|
list_file = os.path.join(self.config.CERT_KEY_BACKUP, "LIST")
|
||||||
le_util.make_or_verify_dir(CONFIG.CERT_KEY_BACKUP, 0o700)
|
le_util.make_or_verify_dir(self.config.CERT_KEY_BACKUP, 0o700)
|
||||||
idx = 0
|
idx = 0
|
||||||
|
|
||||||
if encrypt:
|
if encrypt:
|
||||||
@@ -267,11 +271,11 @@ class Client(object):
|
|||||||
|
|
||||||
shutil.copy2(self.authkey.file,
|
shutil.copy2(self.authkey.file,
|
||||||
os.path.join(
|
os.path.join(
|
||||||
CONFIG.CERT_KEY_BACKUP,
|
self.config.CERT_KEY_BACKUP,
|
||||||
os.path.basename(self.authkey.file) + "_" + str(idx)))
|
os.path.basename(self.authkey.file) + "_" + str(idx)))
|
||||||
shutil.copy2(cert_file,
|
shutil.copy2(cert_file,
|
||||||
os.path.join(
|
os.path.join(
|
||||||
CONFIG.CERT_KEY_BACKUP,
|
self.config.CERT_KEY_BACKUP,
|
||||||
os.path.basename(cert_file) + "_" + str(idx)))
|
os.path.basename(cert_file) + "_" + str(idx)))
|
||||||
|
|
||||||
return True
|
return True
|
||||||
@@ -339,7 +343,7 @@ def validate_key_csr(privkey, csr=None):
|
|||||||
"The key and CSR do not match")
|
"The key and CSR do not match")
|
||||||
|
|
||||||
|
|
||||||
def init_key(key_size):
|
def init_key(key_size, key_dir):
|
||||||
"""Initializes privkey.
|
"""Initializes privkey.
|
||||||
|
|
||||||
Inits key and CSR using provided files or generating new files
|
Inits key and CSR using provided files or generating new files
|
||||||
@@ -347,19 +351,19 @@ def init_key(key_size):
|
|||||||
filesystem. The CSR is placed into DER format to allow
|
filesystem. The CSR is placed into DER format to allow
|
||||||
the namedtuple to easily work with the protocol.
|
the namedtuple to easily work with the protocol.
|
||||||
|
|
||||||
|
:param str key_dir: Key save directory.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
key_pem = crypto_util.make_key(key_size)
|
key_pem = crypto_util.make_key(key_size)
|
||||||
except ValueError as err:
|
except ValueError as err:
|
||||||
logging.fatal(str(err))
|
logging.fatal(str(err))
|
||||||
logging.info("Note: The default RSA key size is %d bits.",
|
|
||||||
CONFIG.RSA_KEY_SIZE)
|
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# Save file
|
# Save file
|
||||||
le_util.make_or_verify_dir(CONFIG.KEY_DIR, 0o700)
|
le_util.make_or_verify_dir(key_dir, 0o700)
|
||||||
key_f, key_filename = le_util.unique_file(
|
key_f, key_filename = le_util.unique_file(
|
||||||
os.path.join(CONFIG.KEY_DIR, "key-letsencrypt.pem"), 0o600)
|
os.path.join(key_dir, "key-letsencrypt.pem"), 0o600)
|
||||||
key_f.write(key_pem)
|
key_f.write(key_pem)
|
||||||
key_f.close()
|
key_f.close()
|
||||||
|
|
||||||
@@ -368,15 +372,18 @@ def init_key(key_size):
|
|||||||
return Client.Key(key_filename, key_pem)
|
return Client.Key(key_filename, key_pem)
|
||||||
|
|
||||||
|
|
||||||
def init_csr(privkey, names):
|
def init_csr(privkey, names, cert_dir):
|
||||||
"""Initialize a CSR with the given private key."""
|
"""Initialize a CSR with the given private key.
|
||||||
|
|
||||||
|
:param str cert_dir: Certificate save directory.
|
||||||
|
|
||||||
|
"""
|
||||||
csr_pem, csr_der = crypto_util.make_csr(privkey.pem, names)
|
csr_pem, csr_der = crypto_util.make_csr(privkey.pem, names)
|
||||||
|
|
||||||
# Save CSR
|
# Save CSR
|
||||||
le_util.make_or_verify_dir(CONFIG.CERT_DIR, 0o755)
|
le_util.make_or_verify_dir(cert_dir, 0o755)
|
||||||
csr_f, csr_filename = le_util.unique_file(
|
csr_f, csr_filename = le_util.unique_file(
|
||||||
os.path.join(CONFIG.CERT_DIR, "csr-letsencrypt.pem"), 0o644)
|
os.path.join(cert_dir, "csr-letsencrypt.pem"), 0o644)
|
||||||
csr_f.write(csr_pem)
|
csr_f.write(csr_pem)
|
||||||
csr_f.close()
|
csr_f.close()
|
||||||
|
|
||||||
@@ -393,23 +400,33 @@ def csr_pem_to_der(csr):
|
|||||||
|
|
||||||
|
|
||||||
# This should be controlled by commandline parameters
|
# This should be controlled by commandline parameters
|
||||||
def determine_authenticator():
|
def determine_authenticator(config):
|
||||||
"""Returns a valid IAuthenticator."""
|
"""Returns a valid IAuthenticator.
|
||||||
|
|
||||||
|
:param config: Configuration.
|
||||||
|
:type config: :class:`letsencrypt.client.interfaces.IConfig`
|
||||||
|
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
return configurator.ApacheConfigurator()
|
return configurator.ApacheConfigurator(config)
|
||||||
except errors.LetsEncryptNoInstallationError:
|
except errors.LetsEncryptNoInstallationError:
|
||||||
logging.info("Unable to determine a way to authenticate the server")
|
logging.info("Unable to determine a way to authenticate the server")
|
||||||
|
|
||||||
|
|
||||||
def determine_installer():
|
def determine_installer(config):
|
||||||
"""Returns a valid installer if one exists."""
|
"""Returns a valid installer if one exists.
|
||||||
|
|
||||||
|
:param config: Configuration.
|
||||||
|
:type config: :class:`letsencrypt.client.interfaces.IConfig`
|
||||||
|
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
return configurator.ApacheConfigurator()
|
return configurator.ApacheConfigurator(config)
|
||||||
except errors.LetsEncryptNoInstallationError:
|
except errors.LetsEncryptNoInstallationError:
|
||||||
logging.info("Unable to find a way to install the certificate.")
|
logging.info("Unable to find a way to install the certificate.")
|
||||||
|
|
||||||
|
|
||||||
def rollback(checkpoints):
|
def rollback(checkpoints, config):
|
||||||
"""Revert configuration the specified number of checkpoints.
|
"""Revert configuration the specified number of checkpoints.
|
||||||
|
|
||||||
.. note:: If another installer uses something other than the reverter class
|
.. note:: If another installer uses something other than the reverter class
|
||||||
@@ -426,12 +443,15 @@ def rollback(checkpoints):
|
|||||||
|
|
||||||
:param int checkpoints: Number of checkpoints to revert.
|
:param int checkpoints: Number of checkpoints to revert.
|
||||||
|
|
||||||
|
:param config: Configuration.
|
||||||
|
:type config: :class:`letsencrypt.client.interfaces.IConfig`
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# Misconfigurations are only a slight problems... allow the user to rollback
|
# Misconfigurations are only a slight problems... allow the user to rollback
|
||||||
try:
|
try:
|
||||||
installer = determine_installer()
|
installer = determine_installer(config)
|
||||||
except errors.LetsEncryptMisconfigurationError:
|
except errors.LetsEncryptMisconfigurationError:
|
||||||
_misconfigured_rollback(checkpoints)
|
_misconfigured_rollback(checkpoints, config)
|
||||||
return
|
return
|
||||||
|
|
||||||
# No Errors occurred during init... proceed normally
|
# No Errors occurred during init... proceed normally
|
||||||
@@ -442,8 +462,13 @@ def rollback(checkpoints):
|
|||||||
installer.restart()
|
installer.restart()
|
||||||
|
|
||||||
|
|
||||||
def _misconfigured_rollback(checkpoints):
|
def _misconfigured_rollback(checkpoints, config):
|
||||||
"""Handles the case where the Installer is misconfigured."""
|
"""Handles the case where the Installer is misconfigured.
|
||||||
|
|
||||||
|
:param config: Configuration.
|
||||||
|
:type config: :class:`letsencrypt.client.interfaces.IConfig`
|
||||||
|
|
||||||
|
"""
|
||||||
yes = zope.component.getUtility(interfaces.IDisplay).generic_yesno(
|
yes = zope.component.getUtility(interfaces.IDisplay).generic_yesno(
|
||||||
"Oh, no! The web server is currently misconfigured.{0}{0}"
|
"Oh, no! The web server is currently misconfigured.{0}{0}"
|
||||||
"Would you still like to rollback the "
|
"Would you still like to rollback the "
|
||||||
@@ -457,13 +482,13 @@ def _misconfigured_rollback(checkpoints):
|
|||||||
# recovery routine has probably already been run by installer
|
# recovery routine has probably already been run by installer
|
||||||
# in the__init__ attempt, run it again for safety... it shouldn't hurt
|
# in the__init__ attempt, run it again for safety... it shouldn't hurt
|
||||||
# Also... not sure how future installers will handle recovery.
|
# Also... not sure how future installers will handle recovery.
|
||||||
rev = reverter.Reverter()
|
rev = reverter.Reverter(config)
|
||||||
rev.recovery_routine()
|
rev.recovery_routine()
|
||||||
rev.rollback_checkpoints(checkpoints)
|
rev.rollback_checkpoints(checkpoints)
|
||||||
|
|
||||||
# We should try to restart the server
|
# We should try to restart the server
|
||||||
try:
|
try:
|
||||||
installer = determine_installer()
|
installer = determine_installer(config)
|
||||||
installer.restart()
|
installer.restart()
|
||||||
logging.info("Hooray! Rollback solved the misconfiguration!")
|
logging.info("Hooray! Rollback solved the misconfiguration!")
|
||||||
logging.info("Your web server is back up and running.")
|
logging.info("Your web server is back up and running.")
|
||||||
@@ -472,16 +497,19 @@ def _misconfigured_rollback(checkpoints):
|
|||||||
"Rollback was unable to solve the misconfiguration issues")
|
"Rollback was unable to solve the misconfiguration issues")
|
||||||
|
|
||||||
|
|
||||||
def revoke(server):
|
def revoke(server, config):
|
||||||
"""Revoke certificates.
|
"""Revoke certificates.
|
||||||
|
|
||||||
:param str server: ACME server the client wishes to revoke certificates from
|
:param str server: ACME server the client wishes to revoke certificates from
|
||||||
|
|
||||||
|
:param config: Configuration.
|
||||||
|
:type config: :class:`letsencrypt.client.interfaces.IConfig`
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# Misconfigurations don't really matter. Determine installer better choose
|
# Misconfigurations don't really matter. Determine installer better choose
|
||||||
# correctly though.
|
# correctly though.
|
||||||
try:
|
try:
|
||||||
installer = determine_installer()
|
installer = determine_installer(config)
|
||||||
except errors.LetsEncryptMisconfigurationError:
|
except errors.LetsEncryptMisconfigurationError:
|
||||||
zope.component.getUtility(interfaces.IDisplay).generic_notification(
|
zope.component.getUtility(interfaces.IDisplay).generic_notification(
|
||||||
"The web server is currently misconfigured. Some "
|
"The web server is currently misconfigured. Some "
|
||||||
@@ -497,16 +525,19 @@ def revoke(server):
|
|||||||
"revocation without a valid installer. This feature should come "
|
"revocation without a valid installer. This feature should come "
|
||||||
"soon.")
|
"soon.")
|
||||||
return
|
return
|
||||||
revoc = revoker.Revoker(server, installer)
|
revoc = revoker.Revoker(server, installer, config)
|
||||||
revoc.list_certs_keys()
|
revoc.list_certs_keys()
|
||||||
|
|
||||||
|
|
||||||
def view_config_changes():
|
def view_config_changes(config):
|
||||||
"""View checkpoints and associated configuration changes.
|
"""View checkpoints and associated configuration changes.
|
||||||
|
|
||||||
.. note:: This assumes that the installation is using a Reverter object.
|
.. note:: This assumes that the installation is using a Reverter object.
|
||||||
|
|
||||||
|
:param config: Configuration.
|
||||||
|
:type config: :class:`letsencrypt.client.interfaces.IConfig`
|
||||||
|
|
||||||
"""
|
"""
|
||||||
rev = reverter.Reverter()
|
rev = reverter.Reverter(config)
|
||||||
rev.recovery_routine()
|
rev.recovery_routine()
|
||||||
rev.view_config_changes()
|
rev.view_config_changes()
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ from letsencrypt.client import recovery_token
|
|||||||
|
|
||||||
|
|
||||||
class ClientAuthenticator(object):
|
class ClientAuthenticator(object):
|
||||||
"""IAuthenticator for CONFIG.CLIENT_CHALLENGES.
|
"""IAuthenticator for
|
||||||
|
:const:`~letsencrypt.client.constants.CLIENT_CHALLENGES`.
|
||||||
|
|
||||||
:ivar rec_token: Performs "recoveryToken" challenges
|
:ivar rec_token: Performs "recoveryToken" challenges
|
||||||
:type rec_token: :class:`letsencrypt.client.recovery_token.RecoveryToken`
|
:type rec_token: :class:`letsencrypt.client.recovery_token.RecoveryToken`
|
||||||
@@ -17,13 +18,17 @@ class ClientAuthenticator(object):
|
|||||||
zope.interface.implements(interfaces.IAuthenticator)
|
zope.interface.implements(interfaces.IAuthenticator)
|
||||||
|
|
||||||
# This will have an installer soon for get_key/cert purposes
|
# This will have an installer soon for get_key/cert purposes
|
||||||
def __init__(self, server):
|
def __init__(self, server, config):
|
||||||
"""Initialize Client Authenticator.
|
"""Initialize Client Authenticator.
|
||||||
|
|
||||||
:param str server: ACME CA Server
|
:param str server: ACME CA Server
|
||||||
|
|
||||||
|
:param config: Configuration.
|
||||||
|
:type config: :class:`letsencrypt.client.interfaces.IConfig`
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self.rec_token = recovery_token.RecoveryToken(server)
|
self.rec_token = recovery_token.RecoveryToken(
|
||||||
|
server, config.REV_TOKEN_DIRS)
|
||||||
|
|
||||||
def get_chall_pref(self, unused_domain): # pylint: disable=no-self-use
|
def get_chall_pref(self, unused_domain): # pylint: disable=no-self-use
|
||||||
"""Return list of challenge preferences."""
|
"""Return list of challenge preferences."""
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
"""Let's Encrypt constants."""
|
||||||
|
import pkg_resources
|
||||||
|
|
||||||
|
|
||||||
|
S_SIZE = 32
|
||||||
|
"""Size (in bytes) of secret base64-encoded octet string "s" used in
|
||||||
|
challanges."""
|
||||||
|
|
||||||
|
NONCE_SIZE = 16
|
||||||
|
"""Size of nonce used in JWS objects (in bytes)."""
|
||||||
|
|
||||||
|
EXCLUSIVE_CHALLENGES = [frozenset(["dvsni", "simpleHttps"])]
|
||||||
|
"""Mutually exclusive challenges."""
|
||||||
|
|
||||||
|
DV_CHALLENGES = frozenset(["dvsni", "simpleHttps", "dns"])
|
||||||
|
"""Challenges that must be solved by a
|
||||||
|
:class:`letsencrypt.client.interfaces.IAuthenticator` object."""
|
||||||
|
|
||||||
|
CLIENT_CHALLENGES = frozenset(
|
||||||
|
["recoveryToken", "recoveryContact", "proofOfPossession"])
|
||||||
|
"""Challenges that are handled by the Let's Encrypt client."""
|
||||||
|
|
||||||
|
ENHANCEMENTS = ["redirect", "http-header", "ocsp-stapling", "spdy"]
|
||||||
|
"""List of possible :class:`letsencrypt.client.interfaces.IInstaller`
|
||||||
|
enhancements.
|
||||||
|
|
||||||
|
List of expected options parameters:
|
||||||
|
- redirect: None
|
||||||
|
- http-header: TODO
|
||||||
|
- ocsp-stapling: TODO
|
||||||
|
- spdy: TODO
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
APACHE_MOD_SSL_CONF = pkg_resources.resource_filename(
|
||||||
|
'letsencrypt.client.apache', 'options-ssl.conf')
|
||||||
|
"""Path to the Apache mod_ssl config file found in the Let's Encrypt
|
||||||
|
distribution."""
|
||||||
|
|
||||||
|
DVSNI_DOMAIN_SUFFIX = ".acme.invalid"
|
||||||
|
"""Suffix appended to domains in DVSNI validation."""
|
||||||
@@ -10,11 +10,11 @@ import Crypto.Signature.PKCS1_v1_5
|
|||||||
|
|
||||||
import M2Crypto
|
import M2Crypto
|
||||||
|
|
||||||
from letsencrypt.client import CONFIG
|
from letsencrypt.client import constants
|
||||||
from letsencrypt.client import le_util
|
from letsencrypt.client import le_util
|
||||||
|
|
||||||
|
|
||||||
def create_sig(msg, key_str, nonce=None, nonce_len=CONFIG.NONCE_SIZE):
|
def create_sig(msg, key_str, nonce=None):
|
||||||
"""Create signature with nonce prepended to the message.
|
"""Create signature with nonce prepended to the message.
|
||||||
|
|
||||||
.. todo:: Change this over to M2Crypto... PKey
|
.. todo:: Change this over to M2Crypto... PKey
|
||||||
@@ -24,22 +24,17 @@ def create_sig(msg, key_str, nonce=None, nonce_len=CONFIG.NONCE_SIZE):
|
|||||||
|
|
||||||
:param str key_str: Key in string form. Accepted formats
|
:param str key_str: Key in string form. Accepted formats
|
||||||
are the same as for `Crypto.PublicKey.RSA.importKey`.
|
are the same as for `Crypto.PublicKey.RSA.importKey`.
|
||||||
|
|
||||||
:param str msg: Message to be signed
|
:param str msg: Message to be signed
|
||||||
|
:param str nonce: Nonce to be used (required size
|
||||||
:param nonce: Nonce to be used. If None, nonce of `nonce_len` size
|
|
||||||
will be randomly generated.
|
|
||||||
:type nonce: str or None
|
|
||||||
|
|
||||||
:param int nonce_len: Size of the automatically generated nonce.
|
|
||||||
|
|
||||||
:returns: Signature.
|
:returns: Signature.
|
||||||
:rtype: dict
|
:rtype: dict
|
||||||
|
|
||||||
"""
|
"""
|
||||||
msg = str(msg)
|
|
||||||
key = Crypto.PublicKey.RSA.importKey(key_str)
|
key = Crypto.PublicKey.RSA.importKey(key_str)
|
||||||
nonce = Random.get_random_bytes(nonce_len) if nonce is None else nonce
|
if nonce is None:
|
||||||
|
nonce = Random.get_random_bytes(constants.NONCE_SIZE)
|
||||||
|
assert len(nonce) == constants.NONCE_SIZE
|
||||||
|
|
||||||
msg_with_nonce = nonce + msg
|
msg_with_nonce = nonce + msg
|
||||||
hashed = Crypto.Hash.SHA256.new(msg_with_nonce)
|
hashed = Crypto.Hash.SHA256.new(msg_with_nonce)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import zope.interface
|
import zope.interface
|
||||||
|
|
||||||
# pylint: disable=no-self-argument,no-method-argument,no-init,inherit-non-class
|
# pylint: disable=no-self-argument,no-method-argument,no-init,inherit-non-class
|
||||||
|
# pylint: disable=too-few-public-methods
|
||||||
|
|
||||||
|
|
||||||
class IAuthenticator(zope.interface.Interface):
|
class IAuthenticator(zope.interface.Interface):
|
||||||
@@ -58,6 +59,10 @@ class IChallenge(zope.interface.Interface):
|
|||||||
"""Cleanup."""
|
"""Cleanup."""
|
||||||
|
|
||||||
|
|
||||||
|
class IConfig(zope.interface.Interface):
|
||||||
|
"""Marker interface for Let's Encrypt config."""
|
||||||
|
|
||||||
|
|
||||||
class IInstaller(zope.interface.Interface):
|
class IInstaller(zope.interface.Interface):
|
||||||
"""Generic Let's Encrypt Installer Interface.
|
"""Generic Let's Encrypt Installer Interface.
|
||||||
|
|
||||||
|
|||||||
@@ -3,9 +3,7 @@ import errno
|
|||||||
import os
|
import os
|
||||||
|
|
||||||
import zope.component
|
import zope.component
|
||||||
# import zope.interface
|
|
||||||
|
|
||||||
from letsencrypt.client import CONFIG
|
|
||||||
from letsencrypt.client import le_util
|
from letsencrypt.client import le_util
|
||||||
from letsencrypt.client import interfaces
|
from letsencrypt.client import interfaces
|
||||||
|
|
||||||
@@ -16,7 +14,7 @@ class RecoveryToken(object):
|
|||||||
Based on draft-barnes-acme, section 6.4.
|
Based on draft-barnes-acme, section 6.4.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def __init__(self, server, direc=CONFIG.REV_TOKENS_DIR):
|
def __init__(self, server, direc):
|
||||||
self.token_dir = os.path.join(direc, server)
|
self.token_dir = os.path.join(direc, server)
|
||||||
|
|
||||||
def perform(self, chall):
|
def perform(self, chall):
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import time
|
|||||||
|
|
||||||
import zope.component
|
import zope.component
|
||||||
|
|
||||||
from letsencrypt.client import CONFIG
|
|
||||||
from letsencrypt.client import display
|
from letsencrypt.client import display
|
||||||
from letsencrypt.client import errors
|
from letsencrypt.client import errors
|
||||||
from letsencrypt.client import interfaces
|
from letsencrypt.client import interfaces
|
||||||
@@ -14,12 +13,19 @@ from letsencrypt.client import le_util
|
|||||||
|
|
||||||
|
|
||||||
class Reverter(object):
|
class Reverter(object):
|
||||||
"""Reverter Class - save and revert configuration checkpoints"""
|
"""Reverter Class - save and revert configuration checkpoints."""
|
||||||
def __init__(self, direc=None):
|
|
||||||
if not direc:
|
def __init__(self, config, direc=None):
|
||||||
direc = {'backup': CONFIG.BACKUP_DIR,
|
"""Initialize Reverter.
|
||||||
'temp': CONFIG.TEMP_CHECKPOINT_DIR,
|
|
||||||
'progress': CONFIG.IN_PROGRESS_DIR}
|
:param config: Configuration.
|
||||||
|
:type config: :class:`letsencrypt.client.interfaces.IConfig`
|
||||||
|
|
||||||
|
"""
|
||||||
|
if direc is None:
|
||||||
|
direc = {'backup': config.BACKUP_DIR,
|
||||||
|
'temp': config.TEMP_CHECKPOINT_DIR,
|
||||||
|
'progress': config.IN_PROGRESS_DIR}
|
||||||
self.direc = direc
|
self.direc = direc
|
||||||
|
|
||||||
def revert_temporary_config(self):
|
def revert_temporary_config(self):
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import M2Crypto
|
|||||||
import zope.component
|
import zope.component
|
||||||
|
|
||||||
from letsencrypt.client import acme
|
from letsencrypt.client import acme
|
||||||
from letsencrypt.client import CONFIG
|
|
||||||
from letsencrypt.client import crypto_util
|
from letsencrypt.client import crypto_util
|
||||||
from letsencrypt.client import display
|
from letsencrypt.client import display
|
||||||
from letsencrypt.client import interfaces
|
from letsencrypt.client import interfaces
|
||||||
@@ -16,10 +15,17 @@ from letsencrypt.client import network
|
|||||||
|
|
||||||
|
|
||||||
class Revoker(object):
|
class Revoker(object):
|
||||||
"""A revocation class for LE."""
|
"""A revocation class for LE.
|
||||||
def __init__(self, server, installer):
|
|
||||||
|
:param config: Configuration.
|
||||||
|
:type config: :class:`letsencrypt.client.interfaces.IConfig`
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, server, installer, config):
|
||||||
self.network = network.Network(server)
|
self.network = network.Network(server)
|
||||||
self.installer = installer
|
self.installer = installer
|
||||||
|
self.config = config
|
||||||
|
|
||||||
def acme_revocation(self, cert):
|
def acme_revocation(self, cert):
|
||||||
"""Handle ACME "revocation" phase.
|
"""Handle ACME "revocation" phase.
|
||||||
@@ -48,7 +54,7 @@ class Revoker(object):
|
|||||||
|
|
||||||
def list_certs_keys(self):
|
def list_certs_keys(self):
|
||||||
"""List trusted Let's Encrypt certificates."""
|
"""List trusted Let's Encrypt certificates."""
|
||||||
list_file = os.path.join(CONFIG.CERT_KEY_BACKUP, "LIST")
|
list_file = os.path.join(self.config.CERT_KEY_BACKUP, "LIST")
|
||||||
certs = []
|
certs = []
|
||||||
|
|
||||||
if not os.path.isfile(list_file):
|
if not os.path.isfile(list_file):
|
||||||
@@ -69,9 +75,9 @@ class Revoker(object):
|
|||||||
for row in csvreader:
|
for row in csvreader:
|
||||||
cert = crypto_util.get_cert_info(row[1])
|
cert = crypto_util.get_cert_info(row[1])
|
||||||
|
|
||||||
b_k = os.path.join(CONFIG.CERT_KEY_BACKUP,
|
b_k = os.path.join(self.config.CERT_KEY_BACKUP,
|
||||||
os.path.basename(row[2]) + "_" + row[0])
|
os.path.basename(row[2]) + "_" + row[0])
|
||||||
b_c = os.path.join(CONFIG.CERT_KEY_BACKUP,
|
b_c = os.path.join(self.config.CERT_KEY_BACKUP,
|
||||||
os.path.basename(row[1]) + "_" + row[0])
|
os.path.basename(row[1]) + "_" + row[0])
|
||||||
|
|
||||||
cert.update({
|
cert.update({
|
||||||
@@ -118,8 +124,8 @@ class Revoker(object):
|
|||||||
:param dict cert: Cert dict used throughout revocation
|
:param dict cert: Cert dict used throughout revocation
|
||||||
|
|
||||||
"""
|
"""
|
||||||
list_file = os.path.join(CONFIG.CERT_KEY_BACKUP, "LIST")
|
list_file = os.path.join(self.config.CERT_KEY_BACKUP, "LIST")
|
||||||
list_file2 = os.path.join(CONFIG.CERT_KEY_BACKUP, "LIST.tmp")
|
list_file2 = os.path.join(self.config.CERT_KEY_BACKUP, "LIST.tmp")
|
||||||
|
|
||||||
with open(list_file, 'rb') as orgfile:
|
with open(list_file, 'rb') as orgfile:
|
||||||
csvreader = csv.reader(orgfile)
|
csvreader = csv.reader(orgfile)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
"""Class helps construct valid ACME messages for testing."""
|
"""Class helps construct valid ACME messages for testing."""
|
||||||
from letsencrypt.client import CONFIG
|
from letsencrypt.client import constants
|
||||||
|
|
||||||
|
|
||||||
CHALLENGES = {
|
CHALLENGES = {
|
||||||
@@ -62,13 +62,13 @@ CHALLENGES = {
|
|||||||
def get_dv_challenges():
|
def get_dv_challenges():
|
||||||
"""Returns all auth challenges."""
|
"""Returns all auth challenges."""
|
||||||
return [chall for typ, chall in CHALLENGES.iteritems()
|
return [chall for typ, chall in CHALLENGES.iteritems()
|
||||||
if typ in CONFIG.DV_CHALLENGES]
|
if typ in constants.DV_CHALLENGES]
|
||||||
|
|
||||||
|
|
||||||
def get_client_challenges():
|
def get_client_challenges():
|
||||||
"""Returns all client challenges."""
|
"""Returns all client challenges."""
|
||||||
return [chall for typ, chall in CHALLENGES.iteritems()
|
return [chall for typ, chall in CHALLENGES.iteritems()
|
||||||
if typ in CONFIG.CLIENT_CHALLENGES]
|
if typ in constants.CLIENT_CHALLENGES]
|
||||||
|
|
||||||
|
|
||||||
def get_challenges():
|
def get_challenges():
|
||||||
@@ -83,7 +83,7 @@ def gen_combos(challs):
|
|||||||
combos = []
|
combos = []
|
||||||
|
|
||||||
for i, chall in enumerate(challs):
|
for i, chall in enumerate(challs):
|
||||||
if chall["type"] in CONFIG.DV_CHALLENGES:
|
if chall["type"] in constants.DV_CHALLENGES:
|
||||||
dv_chall.append(i)
|
dv_chall.append(i)
|
||||||
else:
|
else:
|
||||||
renewal_chall.append(i)
|
renewal_chall.append(i)
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import shutil
|
|||||||
import mock
|
import mock
|
||||||
|
|
||||||
from letsencrypt.client import challenge_util
|
from letsencrypt.client import challenge_util
|
||||||
|
from letsencrypt.client import constants
|
||||||
from letsencrypt.client import client
|
from letsencrypt.client import client
|
||||||
from letsencrypt.client import CONFIG
|
|
||||||
|
|
||||||
from letsencrypt.client.tests.apache import util
|
from letsencrypt.client.tests.apache import util
|
||||||
|
|
||||||
@@ -157,12 +157,14 @@ class DvsniPerformTest(util.ApacheTest):
|
|||||||
if vhost.addrs == set(v_addr1):
|
if vhost.addrs == set(v_addr1):
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
vhost.names,
|
vhost.names,
|
||||||
set([str(self.challs[0].nonce + CONFIG.INVALID_EXT)]))
|
set([str(self.challs[0].nonce +
|
||||||
|
constants.DVSNI_DOMAIN_SUFFIX)]))
|
||||||
else:
|
else:
|
||||||
self.assertEqual(vhost.addrs, set(v_addr2))
|
self.assertEqual(vhost.addrs, set(v_addr2))
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
vhost.names,
|
vhost.names,
|
||||||
set([str(self.challs[1].nonce + CONFIG.INVALID_EXT)]))
|
set([str(self.challs[1].nonce +
|
||||||
|
constants.DVSNI_DOMAIN_SUFFIX)]))
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import unittest
|
|||||||
|
|
||||||
import mock
|
import mock
|
||||||
|
|
||||||
from letsencrypt.client import CONFIG
|
from letsencrypt.client import constants
|
||||||
from letsencrypt.client.apache import configurator
|
from letsencrypt.client.apache import configurator
|
||||||
from letsencrypt.client.apache import obj
|
from letsencrypt.client.apache import obj
|
||||||
|
|
||||||
@@ -50,11 +50,7 @@ def dir_setup(test_dir="debian_apache_2_4/two_vhost_80"):
|
|||||||
def setup_apache_ssl_options(config_dir):
|
def setup_apache_ssl_options(config_dir):
|
||||||
"""Move the ssl_options into position and return the path."""
|
"""Move the ssl_options into position and return the path."""
|
||||||
option_path = os.path.join(config_dir, "options-ssl.conf")
|
option_path = os.path.join(config_dir, "options-ssl.conf")
|
||||||
temp_options = pkg_resources.resource_filename(
|
shutil.copyfile(constants.APACHE_MOD_SSL_CONF, option_path)
|
||||||
"letsencrypt.client.apache", os.path.basename(CONFIG.OPTIONS_SSL_CONF))
|
|
||||||
shutil.copyfile(
|
|
||||||
temp_options, option_path)
|
|
||||||
|
|
||||||
return option_path
|
return option_path
|
||||||
|
|
||||||
|
|
||||||
@@ -69,7 +65,9 @@ def get_apache_configurator(
|
|||||||
# This just states that the ssl module is already loaded
|
# This just states that the ssl module is already loaded
|
||||||
mock_popen().communicate.return_value = ("ssl_module", "")
|
mock_popen().communicate.return_value = ("ssl_module", "")
|
||||||
config = configurator.ApacheConfigurator(
|
config = configurator.ApacheConfigurator(
|
||||||
config_path,
|
mock.MagicMock(APACHE_SERVER_ROOT=config_path,
|
||||||
|
APACHE_MOD_SSL_CONF=ssl_options,
|
||||||
|
LE_VHOST_EXT="-le-ssl.conf"),
|
||||||
{
|
{
|
||||||
"backup": backups,
|
"backup": backups,
|
||||||
"temp": os.path.join(work_dir, "temp_checkpoint"),
|
"temp": os.path.join(work_dir, "temp_checkpoint"),
|
||||||
@@ -77,7 +75,6 @@ def get_apache_configurator(
|
|||||||
"config": config_dir,
|
"config": config_dir,
|
||||||
"work": work_dir,
|
"work": work_dir,
|
||||||
},
|
},
|
||||||
ssl_options,
|
|
||||||
version)
|
version)
|
||||||
|
|
||||||
return config
|
return config
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ import unittest
|
|||||||
import M2Crypto
|
import M2Crypto
|
||||||
|
|
||||||
from letsencrypt.client import challenge_util
|
from letsencrypt.client import challenge_util
|
||||||
|
from letsencrypt.client import constants
|
||||||
from letsencrypt.client import client
|
from letsencrypt.client import client
|
||||||
from letsencrypt.client import CONFIG
|
|
||||||
from letsencrypt.client import le_util
|
from letsencrypt.client import le_util
|
||||||
|
|
||||||
|
|
||||||
@@ -37,11 +37,11 @@ class DvsniGenCertTest(unittest.TestCase):
|
|||||||
dns_regex = r"DNS:([^, $]*)"
|
dns_regex = r"DNS:([^, $]*)"
|
||||||
cert = M2Crypto.X509.load_cert_string(pem)
|
cert = M2Crypto.X509.load_cert_string(pem)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
cert.get_subject().CN, nonce + CONFIG.INVALID_EXT)
|
cert.get_subject().CN, nonce + constants.DVSNI_DOMAIN_SUFFIX)
|
||||||
|
|
||||||
sans = cert.get_ext("subjectAltName").get_value()
|
sans = cert.get_ext("subjectAltName").get_value()
|
||||||
|
|
||||||
exp_sans = set([nonce + CONFIG.INVALID_EXT, domain, ext])
|
exp_sans = set([nonce + constants.DVSNI_DOMAIN_SUFFIX, domain, ext])
|
||||||
act_sans = set(re.findall(dns_regex, sans))
|
act_sans = set(re.findall(dns_regex, sans))
|
||||||
|
|
||||||
self.assertEqual(exp_sans, act_sans)
|
self.assertEqual(exp_sans, act_sans)
|
||||||
|
|||||||
@@ -6,10 +6,11 @@ import mock
|
|||||||
|
|
||||||
class PerformTest(unittest.TestCase):
|
class PerformTest(unittest.TestCase):
|
||||||
"""Test client perform function."""
|
"""Test client perform function."""
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
from letsencrypt.client.client_authenticator import ClientAuthenticator
|
from letsencrypt.client.client_authenticator import ClientAuthenticator
|
||||||
|
|
||||||
self.auth = ClientAuthenticator("demo_server.org")
|
self.auth = ClientAuthenticator("demo_server.org", mock.MagicMock())
|
||||||
self.auth.rec_token.perform = mock.MagicMock(
|
self.auth.rec_token.perform = mock.MagicMock(
|
||||||
name="rec_token_perform", side_effect=gen_client_resp)
|
name="rec_token_perform", side_effect=gen_client_resp)
|
||||||
|
|
||||||
@@ -45,10 +46,11 @@ class PerformTest(unittest.TestCase):
|
|||||||
|
|
||||||
class CleanupTest(unittest.TestCase):
|
class CleanupTest(unittest.TestCase):
|
||||||
"""Test the Authenticator cleanup function."""
|
"""Test the Authenticator cleanup function."""
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
from letsencrypt.client.client_authenticator import ClientAuthenticator
|
from letsencrypt.client.client_authenticator import ClientAuthenticator
|
||||||
|
|
||||||
self.auth = ClientAuthenticator("demo_server.org")
|
self.auth = ClientAuthenticator("demo_server.org", mock.MagicMock())
|
||||||
self.mock_cleanup = mock.MagicMock(name="rec_token_cleanup")
|
self.mock_cleanup = mock.MagicMock(name="rec_token_cleanup")
|
||||||
self.auth.rec_token.cleanup = self.mock_cleanup
|
self.auth.rec_token.cleanup = self.mock_cleanup
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ class RollbackTest(unittest.TestCase):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def _call(cls, checkpoints):
|
def _call(cls, checkpoints):
|
||||||
from letsencrypt.client.client import rollback
|
from letsencrypt.client.client import rollback
|
||||||
rollback(checkpoints)
|
rollback(checkpoints, mock.MagicMock())
|
||||||
|
|
||||||
@mock.patch("letsencrypt.client.client.determine_installer")
|
@mock.patch("letsencrypt.client.client.determine_installer")
|
||||||
def test_no_problems(self, mock_det):
|
def test_no_problems(self, mock_det):
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ class ReverterCheckpointLocalTest(unittest.TestCase):
|
|||||||
|
|
||||||
self.work_dir, self.direc = setup_work_direc()
|
self.work_dir, self.direc = setup_work_direc()
|
||||||
|
|
||||||
self.reverter = Reverter(self.direc)
|
self.reverter = Reverter(mock.MagicMock(), self.direc)
|
||||||
|
|
||||||
tup = setup_test_files()
|
tup = setup_test_files()
|
||||||
self.config1, self.config2, self.dir1, self.dir2, self.sets = tup
|
self.config1, self.config2, self.dir1, self.dir2, self.sets = tup
|
||||||
@@ -241,7 +241,7 @@ class TestFullCheckpointsReverter(unittest.TestCase):
|
|||||||
logging.disable(logging.CRITICAL)
|
logging.disable(logging.CRITICAL)
|
||||||
|
|
||||||
self.work_dir, self.direc = setup_work_direc()
|
self.work_dir, self.direc = setup_work_direc()
|
||||||
self.reverter = Reverter(self.direc)
|
self.reverter = Reverter(mock.MagicMock(), self.direc)
|
||||||
|
|
||||||
tup = setup_test_files()
|
tup = setup_test_files()
|
||||||
self.config1, self.config2, self.dir1, self.dir2, self.sets = tup
|
self.config1, self.config2, self.dir1, self.dir2, self.sets = tup
|
||||||
@@ -387,14 +387,14 @@ class TestFullCheckpointsReverter(unittest.TestCase):
|
|||||||
class QuickInitReverterTest(unittest.TestCase):
|
class QuickInitReverterTest(unittest.TestCase):
|
||||||
# pylint: disable=too-few-public-methods
|
# pylint: disable=too-few-public-methods
|
||||||
"""Quick test of init."""
|
"""Quick test of init."""
|
||||||
|
|
||||||
def test_init(self):
|
def test_init(self):
|
||||||
from letsencrypt.client.reverter import Reverter
|
from letsencrypt.client.reverter import Reverter
|
||||||
rev = Reverter()
|
config = mock.MagicMock()
|
||||||
|
rev = Reverter(config)
|
||||||
# Verify direc is set
|
self.assertEqual(rev.direc['backup'], config.BACKUP_DIR)
|
||||||
self.assertTrue(rev.direc['backup'])
|
self.assertEqual(rev.direc['temp'], config.TEMP_CHECKPOINT_DIR)
|
||||||
self.assertTrue(rev.direc['temp'])
|
self.assertEqual(rev.direc['progress'], config.IN_PROGRESS_DIR)
|
||||||
self.assertTrue(rev.direc['progress'])
|
|
||||||
|
|
||||||
|
|
||||||
def setup_work_direc():
|
def setup_work_direc():
|
||||||
|
|||||||
@@ -82,15 +82,15 @@ def main(): # pylint: disable=too-many-statements,too-many-branches
|
|||||||
zope.component.provideUtility(displayer)
|
zope.component.provideUtility(displayer)
|
||||||
|
|
||||||
if args.view_config_changes:
|
if args.view_config_changes:
|
||||||
client.view_config_changes()
|
client.view_config_changes(CONFIG)
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
|
||||||
if args.revoke:
|
if args.revoke:
|
||||||
client.revoke(args.server)
|
client.revoke(args.server, CONFIG)
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
|
||||||
if args.rollback > 0:
|
if args.rollback > 0:
|
||||||
client.rollback(args.rollback)
|
client.rollback(args.rollback, CONFIG)
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
|
||||||
if not args.eula:
|
if not args.eula:
|
||||||
@@ -99,7 +99,7 @@ def main(): # pylint: disable=too-many-statements,too-many-branches
|
|||||||
# Make sure we actually get an installer that is functioning properly
|
# Make sure we actually get an installer that is functioning properly
|
||||||
# before we begin to try to use it.
|
# before we begin to try to use it.
|
||||||
try:
|
try:
|
||||||
installer = client.determine_installer()
|
installer = client.determine_installer(CONFIG)
|
||||||
except errors.LetsEncryptMisconfigurationError as err:
|
except errors.LetsEncryptMisconfigurationError as err:
|
||||||
logging.fatal("Please fix your configuration before proceeding. "
|
logging.fatal("Please fix your configuration before proceeding. "
|
||||||
"The Installer exited with the following message: "
|
"The Installer exited with the following message: "
|
||||||
@@ -110,17 +110,17 @@ def main(): # pylint: disable=too-many-statements,too-many-branches
|
|||||||
if interfaces.IAuthenticator.providedBy(installer): # pylint: disable=no-member
|
if interfaces.IAuthenticator.providedBy(installer): # pylint: disable=no-member
|
||||||
auth = installer
|
auth = installer
|
||||||
else:
|
else:
|
||||||
auth = client.determine_authenticator()
|
auth = client.determine_authenticator(CONFIG)
|
||||||
|
|
||||||
domains = choose_names(installer) if args.domains is None else args.domains
|
domains = choose_names(installer) if args.domains is None else args.domains
|
||||||
|
|
||||||
# Prepare for init of Client
|
# Prepare for init of Client
|
||||||
if args.privkey is None:
|
if args.privkey is None:
|
||||||
privkey = client.init_key(args.key_size)
|
privkey = client.init_key(args.key_size, CONFIG.KEY_DIR)
|
||||||
else:
|
else:
|
||||||
privkey = client.Client.Key(args.privkey[0], args.privkey[1])
|
privkey = client.Client.Key(args.privkey[0], args.privkey[1])
|
||||||
|
|
||||||
acme = client.Client(args.server, privkey, auth, installer)
|
acme = client.Client(args.server, privkey, auth, installer, CONFIG)
|
||||||
|
|
||||||
# Validate the key and csr
|
# Validate the key and csr
|
||||||
client.validate_key_csr(privkey)
|
client.validate_key_csr(privkey)
|
||||||
|
|||||||
Reference in New Issue
Block a user