Initial attempt at API modification and associated changes

This commit is contained in:
James Kasten 2015-01-15 01:43:54 -08:00
parent d22ce3128c
commit 4b44befefa
8 changed files with 276 additions and 184 deletions

View file

@ -78,6 +78,33 @@ NONCE_SIZE = 16
RSA_KEY_SIZE = 2048
"""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
"""
# ENHANCEMENTS = [
# {
# "type": "redirect",
# "description": ("Please choose whether HTTPS access is required or "
# "optional."),
# "options": [
# ("Easy", "Allow both HTTP and HTTPS access to thses sites"),
# ("Secure", "Make all requests redirect to secure HTTPS access"),
# ],
# },
# {
# "type": ""
# }
# ]
# Config Optimizations
REWRITE_HTTPS_ARGS = [
"^.*$", "https://%{SERVER_NAME}%{REQUEST_URI}", "[L,R=permanent]"]

View file

@ -127,7 +127,9 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
# on initialization
self._prepare_server_https()
def deploy_cert(self, vhost, cert, key, cert_chain=None):
self.enhance_func = {"redirect": self._enable_redirect}
def deploy_cert(self, domain, cert, key, cert_chain=None):
"""Deploys certificate to specified virtual host.
Currently tries to find the last directives to deploy the cert in
@ -141,17 +143,13 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
.. todo:: Might be nice to remove chain directive if none exists
This shouldn't happen within letsencrypt though
:param vhost: ssl vhost to deploy certificate
:type vhost: :class:`letsencrypt.client.apache.obj.VirtualHost`
:param str domain: domain to deploy certificate
:param str cert: certificate filename
:param str key: private key filename
:param str cert_chain: certificate chain filename
:returns: Success
:rtype: bool
"""
vhost = self.choose_vhost(domain)
path = {}
path["cert_file"] = self.parser.find_dir(parser.case_i(
@ -190,9 +188,9 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
if cert_chain:
self.save_notes += "\tSSLCertificateChainFile %s\n" % cert_chain
# This is a significant operation, make a checkpoint
return self.save()
# return self.save()
def choose_virtual_host(self, target_name):
def choose_vhost(self, target_name):
""" Chooses a virtual host based on the given domain name.
.. todo:: This should maybe return list if no obvious answer
@ -206,24 +204,27 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
"""
# Allows for domain names to be associated with a virtual host
# Client isn't using create_dn_server_assoc(self, dn, vh) yet
for domain, vhost in self.assoc:
if domain == target_name:
return vhost
if target_name in self.assoc:
return self.assoc[target_name]
# Check for servernames/aliases for ssl hosts
for vhost in self.vhosts:
if vhost.ssl and target_name in vhost.names:
self.assoc[target_name] = vhost
return vhost
# Checking for domain name in vhost address
# This technique is not recommended by Apache but is technically valid
target_addr = obj.Addr((target_name, "443"))
for vhost in self.vhosts:
if target_addr in vhost.addrs:
self.assoc[target_name] = vhost
return vhost
# Check for non ssl vhosts with servernames/aliases == 'name'
for vhost in self.vhosts:
if not vhost.ssl and target_name in vhost.names:
return self.make_vhost_ssl(vhost)
vhost = self.make_vhost_ssl(vhost)
self.assoc[target_name] = vhost
return vhost
# No matches, search for the default
for vhost in self.vhosts:
@ -381,7 +382,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
is appropriately listening on port 443.
"""
if not check_ssl_loaded():
if not mod_loaded("ssl_module"):
logging.info("Loading mod_ssl into Apache Server")
enable_mod("ssl")
@ -427,6 +428,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
Duplicates vhost and adds default ssl options
New vhost will reside as (nonssl_vhost.path) + CONFIG.LE_VHOST_EXT
.. note:: This function saves the configuration
:param nonssl_vhost: Valid VH that doesn't have SSLEngine on
:type nonssl_vhost: :class:`letsencrypt.client.apache.obj.VirtualHost`
@ -517,24 +519,54 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
return ssl_vhost
def enable_redirect(self, ssl_vhost):
def supported_enhancements():
"""Returns currently supported enhancements."""
return ["redirect"]
def enhance(self, domain, enhancement, options=None):
"""Enhance configuration.
:param str domain: domain to enhance
:param str enhancement: enhancement type defined in
:class:`letsencrypt.client.CONFIG.ENHANCEMENTS
:param options: options for the enhancement
:type options: See :class:`letsencrypt.client.CONFIG.ENHANCEMENTS`
documentation for appropriate parameter.
"""
try:
return self.enhance_func[enhancement](
self.choose_vhost(domain), options)
except ValueError:
raise errors.LetsEncryptConfiguratorError(
"Unsupported enhancement: {}".format(enhancement))
except errors.LetsEncryptConfiguratorError:
logging.warn("Failed %s for %s", enhancement, domain)
def _enable_redirect(self, ssl_vhost, options):
"""Redirect all equivalent HTTP traffic to ssl_vhost.
.. todo:: This enhancement should be rewritten and will unfortunately
require lots of debugging by hand.
Adds Redirect directive to the port 80 equivalent of ssl_vhost
First the function attempts to find the vhost with equivalent
ip addresses that serves on non-ssl ports
The function then adds the directive
.. note:: This function saves the configuration
:param ssl_vhost: Destination of traffic, an ssl enabled vhost
:type ssl_vhost: :class:`letsencrypt.client.apache.obj.VirtualHost`
:param options: Not currently used
:type options: Not Available
:returns: Success, general_vhost (HTTP vhost)
:rtype: (bool, :class:`letsencrypt.client.apache.obj.VirtualHost`)
"""
# TODO: Enable check to see if it is already there
# to avoid the extra restart
enable_mod("rewrite")
if not mod_loaded("rewrite_module"):
enable_mod("rewrite")
general_v = self._general_vhost(ssl_vhost)
if general_v is None:
@ -544,14 +576,18 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
return self.create_redirect_vhost(ssl_vhost)
else:
# Check if redirection already exists
exists, code = self.existing_redirect(general_v)
exists, code = self._existing_redirect(general_v)
if exists:
if code == 0:
logging.debug("Redirect already added")
return True, general_v
logging.info(
"Configuration is already redirecting traffic to HTTPS")
return
else:
logging.debug("Unknown redirect exists for this vhost")
return False, general_v
logging.info("Unknown redirect exists for this vhost")
raise errors.LetsEncryptConfiguratorError(
"Unknown redirect already exists "
"in {}".format(general_v.filep))
# Add directives to server
self.parser.add_dir(general_v.path, "RewriteEngine", "On")
self.parser.add_dir(
@ -559,9 +595,11 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
self.save_notes += ('Redirecting host in %s to ssl vhost in %s\n' %
(general_v.filep, ssl_vhost.filep))
self.save()
return True, general_v
def existing_redirect(self, vhost):
logging.info("Redirecting vhost in %s to ssl vhost in %s",
general_v.filep, ssl_vhost.filep)
def _existing_redirect(self, vhost):
"""Checks to see if existing redirect is in place.
Checks to see if virtualhost already contains a rewrite or redirect
@ -616,7 +654,9 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
# Make sure adding the vhost will be safe
conflict, host_or_addrs = self._conflicting_host(ssl_vhost)
if conflict:
return False, host_or_addrs
raise errors.LetsEncryptConfiguratorError(
"Unable to create a redirection vhost "
"- {}".format(host_or_addrs))
redirect_addrs = host_or_addrs
@ -678,8 +718,6 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
'ssl vhost %s\n' %
(new_vhost.filep, ssl_vhost.filep))
return True, new_vhost
def _conflicting_host(self, ssl_vhost):
"""Checks for conflicting HTTP vhost for ssl_vhost.
@ -759,21 +797,15 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
return vhost
return None
# TODO: Handle this as outlined in Interfaces.
def enable_ocsp_stapling(self, ssl_vhost):
"""Enable OCSP Stapling."""
return False
def enable_hsts(self, ssl_vhost):
"""Enable HSTS."""
return False
def get_all_certs_keys(self):
"""Find all existing keys, certs from configuration.
Retrieve all certs and keys set in VirtualHosts on the Apache server
:returns: list of tuples with form [(cert, key, path)]
cert - str path to certificate file
key - str path to associated key file
path - File path to configuration file.
:rtype: list
"""
@ -865,7 +897,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
['sudo', '/usr/sbin/apache2ctl', 'configtest'],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
text = proc.communicate()
stdout, stderr = proc.communicate()
except (OSError, ValueError):
logging.fatal("Unable to run /usr/sbin/apache2ctl configtest")
sys.exit(1)
@ -873,8 +905,8 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
if proc.returncode != 0:
# Enter recovery routine...
logging.error("Configtest failed")
logging.error(text[0])
logging.error(text[1])
logging.error(stdout)
logging.error(stderr)
return False
return True
@ -992,6 +1024,7 @@ def enable_mod(mod_name):
"""
try:
# Use check_output so the command will finish before reloading
# TODO: a2enmod is debian specific...
subprocess.check_call(["sudo", "a2enmod", mod_name],
stdout=open("/dev/null", 'w'),
stderr=open("/dev/null", 'w'))
@ -1005,31 +1038,28 @@ def enable_mod(mod_name):
sys.exit(1)
def check_ssl_loaded():
def mod_loaded(module):
"""Checks to see if mod_ssl is loaded
Currently uses apache2ctl to get loaded module list
.. todo:: This function is likely fragile to versions/distros
Uses CONFIG.APACHE_CTL to get loaded module list
:returns: If ssl_module is included and active in Apache
:rtype: bool
"""
try:
# p=subprocess.check_output(['sudo', '/usr/sbin/apache2ctl', '-M'],
# stderr=open("/dev/null", 'w'))
proc = subprocess.Popen([CONFIG.APACHE_CTL, '-M'],
stdout=subprocess.PIPE,
stderr=open(
"/dev/null", 'w')).communicate()[0]
proc = subprocess.Popen(
[CONFIG.APACHE_CTL, '-M'],
stdout=subprocess.PIPE,
stderr=open("/dev/null", 'w')).communicate()[0]
except (OSError, ValueError):
logging.error(
"Error accessing %s for loaded modules!", CONFIG.APACHE_CTL)
logging.error("This may be caused by an Apache Configuration Error")
return False
if "ssl_module" in proc:
if module in proc:
return True
return False

View file

@ -64,7 +64,7 @@ class ApacheDvsni(object):
addresses = []
default_addr = "*:443"
for chall in self.dvsni_chall:
vhost = self.config.choose_virtual_host(chall.domain)
vhost = self.config.choose_vhost(chall.domain)
if vhost is None:
logging.error(
"No vhost exists with servername or alias of: %s",

View file

@ -187,7 +187,7 @@ class AugeasConfigurator(object):
self.aug.load()
def display_checkpoints(self):
def show_config_changes(self):
"""Displays all saved checkpoints.
All checkpoints are printed to the console.

View file

@ -34,8 +34,6 @@ class Client(object):
:ivar network: Network object for sending and receiving messages
:type network: :class:`letsencrypt.client.network.Network`
:ivar list names: Domain names (:class:`list` of :class:`str`).
:ivar authkey: Authorization Key
:type authkey: :class:`letsencrypt.client.client.Client.Key`
@ -52,7 +50,7 @@ class Client(object):
Key = collections.namedtuple("Key", "file pem")
CSR = collections.namedtuple("CSR", "file data form")
def __init__(self, server, names, authkey, dv_auth, installer):
def __init__(self, server, authkey, dv_auth, installer):
"""Initialize a client.
:param str server: CA server to contact
@ -62,10 +60,9 @@ class Client(object):
"""
self.network = network.Network(server)
self.names = names
self.authkey = authkey
sanity_check_names([server] + names)
# sanity_check_names([server] + names)
self.installer = installer
@ -73,14 +70,12 @@ class Client(object):
self.auth_handler = auth_handler.AuthHandler(
dv_auth, client_auth, self.network)
def obtain_certificate(self, csr,
def obtain_certificate(self, domains,
cert_path=CONFIG.CERT_PATH,
chain_path=CONFIG.CHAIN_PATH):
"""Obtains a certificate from the ACME server.
:param csr: A valid CSR in DER format for the certificate the client
intends to receive.
:type csr: :class:`CSR`
:param str domains: list of domains to get a certificate
:param str cert_path: Full desired path to end certificate.
:param str chain_path: Full desired path to end chain file.
@ -90,13 +85,16 @@ class Client(object):
"""
# Request Challenges
for name in self.names:
for name in domains:
self.auth_handler.add_chall_msg(
name, self.acme_challenge(name), self.authkey)
# Perform Challenges/Get Authorizations
self.auth_handler.get_authorizations()
# Create CSR from names
csr = init_csr(self.authkey, domains)
# Retrieve certificate
certificate_dict = self.acme_certificate(csr.data)
@ -166,44 +164,44 @@ class Client(object):
return os.path.abspath(cert_file), cert_chain_abspath
def deploy_certificate(self, privkey, cert_file, chain_file):
def deploy_certificate(self, domains, privkey, cert_file, chain_file=None):
"""Install certificate
:returns: Path to a certificate file.
:rtype: str
:param list domains: list of domains to install the certificate
:param privkey: private key for certificate
:type privkey: :class:`Key`
:param str cert_file: certificate file path
:param str chain_file: chain file path
"""
# Find set of virtual hosts to deploy certificates to
vhost = self.get_virtual_hosts(self.names)
# vhost = self.get_virtual_hosts(self.names)
chain = None if chain_file is None else os.path.abspath(chain_file)
for host in vhost:
self.installer.deploy_cert(host,
for dom in domains:
self.installer.deploy_cert(dom,
os.path.abspath(cert_file),
os.path.abspath(privkey.file),
chain)
# Enable any vhost that was issued to, but not enabled
if not host.enabled:
logging.info("Enabling Site %s", host.filep)
self.installer.enable_site(host)
# if not host.enabled:
# logging.info("Enabling Site %s", host.filep)
# self.installer.enable_site(host)
self.installer.save("Deployed Let's Encrypt Certificate")
# sites may have been enabled / final cleanup
self.installer.restart()
zope.component.getUtility(
interfaces.IDisplay).success_installation(self.names)
interfaces.IDisplay).success_installation(domains)
return vhost
def enhance_config(self, domains, redirect=None):
"""Enhance the configuration.
def optimize_config(self, vhost, redirect=None):
"""Optimize the configuration.
.. todo:: Handle multiple vhosts
:param vhost: vhost to optimize
:type vhost: :class:`letsencrypt.client.apache.obj.VirtualHost`
:param list domains: list of domains to configure
:param redirect: If traffic should be forwarded from HTTP to HTTPS.
:type redirect: bool or None
@ -214,8 +212,7 @@ class Client(object):
interfaces.IDisplay).redirect_by_default()
if redirect:
self.redirect_to_ssl(vhost)
self.installer.restart()
self.redirect_to_ssl(domains)
# if self.ocsp_stapling is None:
# q = ("Would you like to protect the privacy of your users "
@ -228,7 +225,7 @@ class Client(object):
# continue
def store_cert_key(self, cert_file, encrypt=False):
"""Store certificate key.
"""Store certificate key. (Used to allow quick revocation)
:param str cert_file: Path to a certificate file.
@ -273,43 +270,49 @@ class Client(object):
return True
def redirect_to_ssl(self, vhost):
def redirect_to_ssl(self, domains):
"""Redirect all traffic from HTTP to HTTPS
:param vhost: list of ssl_vhosts
:type vhost: :class:`letsencrypt.client.interfaces.IInstaller`
"""
for ssl_vh in vhost:
success, redirect_vhost = self.installer.enable_redirect(ssl_vh)
logging.info(
"\nRedirect vhost: %s - %s ", redirect_vhost.filep, success)
for dom in domains:
# TODO: change this to try/catch
self.installer.enhance(dom, "redirect")
self.installer.save("Add Redirects")
self.installer.restart()
# If successful, make sure redirect site is enabled
if success:
self.installer.enable_site(redirect_vhost)
# if success:
# self.installer.enable_site(redirect_vhost)
def get_virtual_hosts(self, domains):
"""Retrieve the appropriate virtual host for the domain
# def get_virtual_hosts(self, domains):
# """Retrieve the appropriate virtual host for the domain
:param list domains: Domains to find ssl vhosts for
# :param list domains: Domains to find ssl vhosts for
:returns: associated vhosts
:rtype: :class:`letsencrypt.client.apache.obj.VirtualHost`
# :returns: associated vhosts
# :rtype: :class:`letsencrypt.client.apache.obj.VirtualHost`
"""
vhost = set()
for name in domains:
host = self.installer.choose_virtual_host(name)
if host is not None:
vhost.add(host)
return vhost
# """
# vhost = set()
# for name in domains:
# host = self.installer.choose_virtual_host(name)
# if host is not None:
# vhost.add(host)
# return vhost
def validate_key_csr(privkey, csr):
"""Validate CSR and key files.
def validate_key_csr(privkey, csr=None):
"""Validate Key and CSR files.
Verifies that the client key and csr arguments are valid and correspond to
one another. This does not currently check the names in the CSR.
one another. This does not currently check the names in the CSR due to
the inability to read SANs from CSRs in python crypto libraries.
If csr is left as None, only the key will be validated.
:param privkey: Key associated with CSR
:type privkey: :class:`letsencrypt.client.client.Client.Key`
@ -324,27 +327,28 @@ def validate_key_csr(privkey, csr):
# The client can eventually do things like prompt the user
# and allow the user to take more appropriate actions
if csr.form == "der":
csr_obj = M2Crypto.X509.load_request_der_string(csr.data)
csr = Client.CSR(csr.file, csr_obj.as_pem(), "der")
# If CSR is provided, it must be readable and valid.
if csr.data and not crypto_util.valid_csr(csr.data):
raise errors.LetsEncryptClientError(
"The provided CSR is not a valid CSR")
# If key is provided, it must be readable and valid.
# Key must be readable and valid.
if privkey.pem and not crypto_util.valid_privkey(privkey.pem):
raise errors.LetsEncryptClientError(
"The provided key is not a valid key")
# If CSR and key are provided, the key must be the same key used
# in the CSR.
if csr.data and privkey.pem:
if not crypto_util.csr_matches_pubkey(
csr.data, privkey.pem):
if csr:
if csr.form == "der":
csr_obj = M2Crypto.X509.load_request_der_string(csr.data)
csr = Client.CSR(csr.file, csr_obj.as_pem(), "der")
# If CSR is provided, it must be readable and valid.
if csr.data and not crypto_util.valid_csr(csr.data):
raise errors.LetsEncryptClientError(
"The key and CSR do not match")
"The provided CSR is not a valid CSR")
# If both CSR and key are provided, the key must be the same key used
# in the CSR.
if csr.data and privkey.pem:
if not crypto_util.csr_matches_pubkey(
csr.data, privkey.pem):
raise errors.LetsEncryptClientError(
"The key and CSR do not match")
def init_key():

View file

@ -53,48 +53,70 @@ class IInstaller(zope.interface.Interface):
"""Generic Let's Encrypt Installer Interface.
Represents any server that an X509 certificate can be placed.
With a focus on HTTPS optimizations.
.. todo:: All optimizations should be of the form .enable("hsts")
This will make it general towards any optimization... we should also
define a function to glean what optimizations are available.
Perhaps with text that describes the optimizations...
"""
def get_all_names():
"""Returns all names that may be authenticated."""
def deploy_cert(vhost, cert, key, cert_chain=None):
def deploy_cert(domain, cert, key, cert_chain=None):
"""Deploy certificate.
:param vhost
:param str cert: CSR
:param str key: Private key
:param str domain: domain to deploy certificate
:param str cert: certificate filename
:param str key: private key filename
"""
def choose_virtual_host(name):
"""Chooses a virtual host based on a given domain name."""
# def choose_virtual_host(domain):
# """Chooses a virtual host based on a given domain name."""
def enable_redirect(ssl_vhost):
"""Redirect all traffic to the given ssl_vhost (port 80 => 443)."""
# def enable_redirect(ssl_vhost):
# """Redirect all traffic to the given ssl_vhost (port 80 => 443)."""
def enable_hsts(ssl_vhost):
"""Enable HSTS on the given ssl_vhost."""
# def enable_hsts(ssl_vhost):
# """Enable HSTS on the given ssl_vhost."""
def enable_ocsp_stapling(ssl_vhost):
"""Enable OCSP stapling on given ssl_vhost."""
# def enable_ocsp_stapling(ssl_vhost):
# """Enable OCSP stapling on given ssl_vhost."""
def enhance(domain, enhancment, options=None):
"""Peform a configuration enhancment.
:param str domain: domain for which to provide enhancement
:param str enhancement: An enhancement as defined in CONFIG.ENHANCEMENTS
:param options: flexible options parameter for enhancement
:type options: Check documentation of
:class:`letsencrypt.client.CONFIG.ENHANCEMENTS` for expected options
for each enhancement.
"""
def supported_enhancements():
"""Returns a list of supported enhancments.
:returns: supported enhancments which should be a subset of the
enhancments in :class:`letsencrypt.client.CONFIG.ENHANCEMENTS`
:rtype: `list` of `str`
"""
def get_all_certs_keys():
"""Retrieve all certs and keys set in configuration.
:returns: List of tuples with form [(cert, key, path)].
:returns: list of tuples with form [(cert, key, path)]
cert - str path to certificate file
key - str path to associated key file
path - file path to configuration file
:rtype: list
"""
def enable_site(vhost):
"""Enable the site at the given vhost."""
# def enable_site(vhost):
# """Enable the site at the given vhost.
# :param vhost: domain
# """
def save(title=None, temporary=False):
"""Saves all changes to the configuration files.
@ -109,13 +131,14 @@ class IInstaller(zope.interface.Interface):
:param bool temporary: Indicates whether the changes made will
be quickly reversed in the future (challenges)
"""
def rollback_checkpoints(rollback=1):
"""Revert `rollback` number of configuration checkpoints."""
def display_checkpoints():
"""Display the saved configuration checkpoints."""
def view_config_changes():
"""Display all of the LE config changes."""
def config_test():
"""Make sure the configuration is valid."""

View file

@ -68,9 +68,12 @@ class TwoVhost80Test(unittest.TestCase):
self.assertTrue(self.config.is_site_enabled(self.vh_truth[3].filep))
def test_deploy_cert(self):
# Get the default 443 vhost
self.config.assoc["random.demo"] = self.vh_truth[1]
self.config.deploy_cert(
self.vh_truth[1],
"random.demo",
"example/cert.pem", "example/key.pem", "example/cert_chain.pem")
self.config.save()
loc_cert = self.config.parser.find_dir(
parser.case_i("sslcertificatefile"),

View file

@ -34,17 +34,18 @@ def main():
parser.add_argument("-p", "--privkey", dest="privkey", type=read_file,
help="Path to the private key file for certificate "
"generation.")
parser.add_argument("-c", "--csr", dest="csr", type=read_file,
help="Path to the certificate signing request file "
"corresponding to the private key file. The "
"private key file argument is required if this "
"argument is specified.")
# parser.add_argument("-c", "--csr", dest="csr", type=read_file,
# help="Path to the certificate signing request file "
# "corresponding to the private key file. The "
# "private key file argument is required if this "
# "argument is specified.")
parser.add_argument("-b", "--rollback", dest="rollback", type=int,
default=0, metavar="N",
help="Revert configuration N number of checkpoints.")
parser.add_argument("-k", "--revoke", dest="revoke", action="store_true",
help="Revoke a certificate.")
parser.add_argument("-v", "--view-checkpoints", dest="view_checkpoints",
parser.add_argument("-v", "--view-config-changes",
dest="view_config_changes",
action="store_true",
help="View checkpoints and associated configuration "
"changes.")
@ -56,7 +57,7 @@ def main():
action="store_const", const=False,
help="Skip the HTTPS redirect question, allowing both "
"HTTP and HTTPS.")
parser.add_argument("-e", "--agree-eula", dest="eula", action="store_true",
parser.add_argument("-e", "--agree-tos", dest="eula", action="store_true",
help="Skip the end user license agreement screen.")
parser.add_argument("-t", "--text", dest="use_curses", action="store_false",
help="Use the text output instead of the curses UI.")
@ -87,46 +88,46 @@ def main():
rollback(installer, args.rollback)
sys.exit()
if args.view_checkpoints:
view_checkpoints(installer)
if args.view_config_changes:
view_config_changes(installer)
sys.exit()
if not args.eula:
display_eula()
# Use the same object if possible
if interfaces.IAuthenticator.providedBy(installer):
auth = installer
else:
auth = determine_authenticator()
if not args.eula:
display_eula()
domains = choose_names(installer) if args.domains is None else args.domains
# Enforce '--privkey' is set along with '--csr'.
if args.csr and not args.privkey:
parser.error("private key file (--privkey) must be specified along{0} "
"with the certificate signing request file (--csr)"
.format(os.linesep))
# if args.csr and not args.privkey:
# parser.error("private key file (--privkey) must be specified along{0} "
# "with the certificate signing request file (--csr)"
# .format(os.linesep))
# Prepare for init of Client
if args.privkey is None:
privkey = client.init_key()
else:
privkey = client.Client.Key(args.privkey[0], args.privkey[1])
if args.csr is None:
csr = client.init_csr(privkey, domains)
else:
csr = client.csr_pem_to_der(
client.Client.CSR(args.csr[0], args.csr[1], "pem"))
# if args.csr is None:
# csr = client.init_csr(privkey, domains)
# else:
# csr = client.csr_pem_to_der(
# client.Client.CSR(args.csr[0], args.csr[1], "pem"))
acme = client.Client(server, domains, privkey, auth, installer)
acme = client.Client(server, privkey, auth, installer)
# Validate the key and csr
client.validate_key_csr(privkey, csr)
client.validate_key_csr(privkey)
cert_file, chain_file = acme.obtain_certificate(csr)
vhost = acme.deploy_certificate(privkey, cert_file, chain_file)
acme.optimize_config(vhost, args.redirect)
cert_file, chain_file = acme.obtain_certificate(domains)
acme.deploy_certificate(domains, privkey, cert_file, chain_file)
acme.enhance_config(domains, args.redirect)
def display_eula():
@ -177,17 +178,21 @@ def get_all_names(installer):
# This should be controlled by commandline parameters
def determine_authenticator():
"""Returns a valid authenticator."""
"""Returns a valid IAuthenticator."""
try:
return configurator.ApacheConfigurator()
if interfaces.IAuthenticator.implementedBy(
configurator.ApacheConfigurator):
return configurator.ApacheConfigurator()
except errors.LetsEncryptConfiguratorError:
logging.info("Unable to find a way to authenticate.")
logging.info("Unable to determine a way to authenticate the server")
def determine_installer():
"""Returns a valid installer if one exists."""
try:
return configurator.ApacheConfigurator()
if interfaces.IInstaller.implementedBy(
configurator.ApacheConfigurator):
return configurator.ApacheConfigurator()
except errors.LetsEncryptConfiguratorError:
logging.info("Unable to find a way to install the certificate.")
@ -209,27 +214,27 @@ def read_file(filename):
raise argparse.ArgumentTypeError(exc.strerror)
def rollback(config, checkpoints):
def rollback(installer, checkpoints):
"""Revert configuration the specified number of checkpoints.
:param config: Configurator object
:type config: :class:`ApacheConfigurator`
:param installer: Installer object
:type installer: :class:`letsencrypt.client.interfaces.IInstaller`
:param int checkpoints: Number of checkpoints to revert.
"""
config.rollback_checkpoints(checkpoints)
config.restart()
installer.rollback_checkpoints(checkpoints)
installer.restart()
def view_checkpoints(config):
def view_config_changes(installer):
"""View checkpoints and associated configuration changes.
:param config: Configurator object
:type config: :class:`ApacheConfigurator`
:param installer: Installer object
:type installer: :class:`letsencrypt.client.interfaces.IInstaller`
"""
config.display_checkpoints()
installer.view_config_changes()
if __name__ == "__main__":
main()