diff --git a/.pylintrc b/.pylintrc index 825699036..5302133ad 100644 --- a/.pylintrc +++ b/.pylintrc @@ -38,7 +38,7 @@ load-plugins=linter_plugin # --enable=similarities". If you want to run only the classes checker, but have # no Warning level messages displayed, use"--disable=all --enable=classes # --disable=W" -disable=fixme,locally-disabled,abstract-class-not-used,bad-continuation,too-few-public-methods +disable=fixme,locally-disabled,abstract-class-not-used # abstract-class-not-used cannot be disabled locally (at least in pylint 1.4.1) @@ -101,7 +101,7 @@ function-rgx=[a-z_][a-z0-9_]{2,40}$ function-name-hint=[a-z_][a-z0-9_]{2,40}$ # Regular expression matching correct variable names -variable-rgx=[a-z_][a-z0-9_]{1,30}$ +variable-rgx=[a-z_][a-z0-9_]{2,30}$ # Naming hint for variable names variable-name-hint=[a-z_][a-z0-9_]{2,30}$ @@ -228,8 +228,7 @@ max-module-lines=1250 indent-string=' ' # Number of spaces of indent required inside a hanging or continued line. -# This does something silly/broken... -#indent-after-paren=4 +indent-after-paren=4 [TYPECHECK] diff --git a/bootstrap/_deb_common.sh b/bootstrap/_deb_common.sh index 653daca53..a4dff6b1c 100755 --- a/bootstrap/_deb_common.sh +++ b/bootstrap/_deb_common.sh @@ -9,11 +9,14 @@ # - 6.0.10 "squeeze" (x64) # - 7.8 "wheezy" (x64) # - 8.0 "jessie" (x64) +# - Raspbian: +# - 7.8 (armhf) # virtualenv binary can be found in different packages depending on # distro version (#346) newer () { + apt-get install -y lsb-release --no-install-recommends distro=$(lsb_release -si) # 6.0.10 => 60, 14.04 => 1404 # TODO: in sid version==unstable @@ -29,6 +32,8 @@ newer () { fi } +apt-get update + # you can force newer if lsb_release is not available (e.g. Docker # debian:jessie base image) if [ "$1" = "newer" ] || newer @@ -43,7 +48,6 @@ fi # #276, https://github.com/martinpaljak/M2Crypto/issues/62, # M2Crypto setup.py:add_multiarch_paths -apt-get update apt-get install -y --no-install-recommends \ git-core \ python \ diff --git a/letsencrypt/cli.py b/letsencrypt/cli.py index bdc287370..b2ecd4887 100644 --- a/letsencrypt/cli.py +++ b/letsencrypt/cli.py @@ -150,7 +150,7 @@ def run(args, config, plugins): def auth(args, config, plugins): - """Authenticate & obtain cert, but do not install it""" + """Authenticate & obtain cert, but do not install it.""" # XXX: Update for renewer / RenewableCert acc = _account_init(args, config) if acc is None: @@ -169,13 +169,13 @@ def auth(args, config, plugins): # TODO: Handle errors from _common_run? acme, doms = _common_run( args, config, acc, authenticator=authenticator, installer=installer) - if not acme.obtain_and_enroll_certificate(doms, authenticator, installer, - plugins): + if not acme.obtain_and_enroll_certificate( + doms, authenticator, installer, plugins): return "Certificate could not be obtained" def install(args, config, plugins): - """Install a previously obtained cert in a server""" + """Install a previously obtained cert in a server.""" # XXX: Update for renewer/RenewableCert acc = _account_init(args, config) if acc is None: @@ -192,7 +192,7 @@ def install(args, config, plugins): def revoke(args, unused_config, unused_plugins): - """Revoke a previously obtained certificate""" + """Revoke a previously obtained certificate.""" if args.rev_cert is None and args.rev_key is None: return "At least one of --certificate or --key is required" @@ -204,7 +204,7 @@ def revoke(args, unused_config, unused_plugins): def rollback(args, config, plugins): - """Rollback server configuration changes made during install""" + """Rollback server configuration changes made during install.""" client.rollback(args.installer, args.checkpoints, config, plugins) @@ -218,7 +218,7 @@ def config_changes(unused_args, config, unused_plugins): def plugins_cmd(args, config, plugins): # TODO: Use IDiplay rathern than print - """List server software plugins""" + """List server software plugins.""" logging.debug("Expected interfaces: %s", args.ifaces) ifaces = [] if args.ifaces is None else args.ifaces @@ -264,6 +264,7 @@ def flag_default(name): """Default value for CLI flag.""" return constants.CLI_DEFAULTS[name] + def config_help(name, hidden=False): """Help message for `.IConfig` attribute.""" if hidden: @@ -271,10 +272,15 @@ def config_help(name, hidden=False): else: return interfaces.IConfig[name].__doc__ -class SilentParser(object): - """An a mini parser wrapper that doesn't print help for its - arguments... this one is just needed to the use of callbacks to define - arguments within plugins""" + +class SilentParser(object): # pylint: disable=too-few-public-methods + """Silent wrapper around argparse. + + A mini parser wrapper that doesn't print help for its + arguments. This is needed for the use of callbacks to define + arguments within plugins. + + """ def __init__(self, parser): self.parser = parser def add_argument(self, *args, **kwargs): @@ -282,12 +288,18 @@ class SilentParser(object): kwargs["help"] = argparse.SUPPRESS self.parser.add_argument(*args, **kwargs) -HELP_TOPICS = ["all", "security", "paths", "automation", "testing"] -class HelpfulArgumentParser(object): - """This class wraps argparse, adding the ability to make --help less - verbose, and request help on specific subcategories at a time, eg - 'letsencrypt --help security' for security options.""" +HELP_TOPICS = ["all", "security", "paths", "automation", "testing"] + + +class HelpfulArgumentParser(object): + """Argparse Wrapper. + + This class wraps argparse, adding the ability to make --help less + verbose, and request help on specific subcategories at a time, eg + 'letsencrypt --help security' for security options. + + """ def __init__(self, args, plugins): self.args = args plugin_names = [name for name, _p in plugins.iteritems()] @@ -316,11 +328,15 @@ class HelpfulArgumentParser(object): self.add_plugin_args(plugins) def prescan_for_flag(self, flag, possible_arguments): - """check for a flag, which accepts a fixed set of possible arguments, in + """Checks cli input for flags. + + Check for a flag, which accepts a fixed set of possible arguments, in the command line; we will use this information to configure argparse's help correctly. Return the flag's argument, if it has one that matches the sequence @possible_arguments; otherwise return whether the flag is - present""" + present. + + """ if flag not in self.args: return False pos = self.args.index(flag) @@ -333,10 +349,12 @@ class HelpfulArgumentParser(object): return True def add(self, topic, *args, **kwargs): - """Add a new command line argument. @topic is required, to indicate - which part of the help will document it, but can be None for `always - documented'.""" + """Add a new command line argument. + @topic is required, to indicate which part of the help will document + it, but can be None for `always documented'. + + """ if topic and self.visible_topics[topic]: group = self.groups[topic] group.add_argument(*args, **kwargs) @@ -345,10 +363,14 @@ class HelpfulArgumentParser(object): self.parser.add_argument(*args, **kwargs) def add_group(self, topic, **kwargs): - """This has to be called once for every topic; but we leave those calls + """ + + This has to be called once for every topic; but we leave those calls next to the argument definitions for clarity. Return something arguments can be added to if necessary, either the parser or an argument - group.""" + group. + + """ if self.visible_topics[topic]: #print "Adding visible group " + topic group = self.parser.add_argument_group(topic, **kwargs) @@ -359,8 +381,12 @@ class HelpfulArgumentParser(object): return self.silent_parser def add_plugin_args(self, plugins): - """Let each of the plugins add its own command line arguments, which - may or may not be displayed as help topics.""" + """ + + Let each of the plugins add its own command line arguments, which + may or may not be displayed as help topics. + + """ # TODO: plugin_parser should be called for every detected plugin for name, plugin_ep in plugins.iteritems(): parser_or_group = self.add_group(name, description=plugin_ep.description) @@ -368,9 +394,14 @@ class HelpfulArgumentParser(object): plugin_ep.plugin_cls.inject_parser_options(parser_or_group, name) def determine_help_topics(self, chosen_topic): - """The user may have requested help on a topic, return a dict of which - topics to dislpay. @chosen_topic has prescan_for_flag's return type""" + """ + The user may have requested help on a topic, return a dict of which + topics to display. @chosen_topic has prescan_for_flag's return type + + :returns: dict + + """ # topics maps each topic to whether it should be documented by # argparse on the command line if chosen_topic == "all": @@ -392,7 +423,8 @@ def create_parser(plugins, args): "e.g. -vvv.") # --help is automatically provided by argparse - helpful.add_group("automation", + helpful.add_group( + "automation", description="Arguments for automating execution & other tweaks") helpful.add( "automation", "--version", action="version", @@ -423,9 +455,10 @@ def create_parser(plugins, args): help=config_help("dvsni_port")) helpful.add("testing", "--no-simple-http-tls", action="store_true", - help=config_help("no_simple_http_tls")) + help=config_help("no_simple_http_tls")) subparsers = helpful.parser.add_subparsers(metavar="SUBCOMMAND") + def add_subparser(name, func): # pylint: disable=missing-docstring subparser = subparsers.add_parser( name, help=func.__doc__.splitlines()[0], description=func.__doc__) @@ -460,16 +493,17 @@ def create_parser(plugins, args): helpful.add(None, "-d", "--domains", metavar="DOMAIN", action="append") helpful.add(None, "-k", "--accountkey", type=read_file, - help="Path to the account key file") + help="Path to the account key file") helpful.add(None, "-m", "--email", help=config_help("email")) helpful.add_group( "security", description="Security parameters & server settings") - helpful.add("security", "-B", "--rsa-key-size", type=int, metavar="N", - default=flag_default("rsa_key_size"), - help=config_help("rsa_key_size")) + helpful.add( + "security", "-B", "--rsa-key-size", type=int, metavar="N", + default=flag_default("rsa_key_size"), help=config_help("rsa_key_size")) # TODO: resolve - assumes binary logic while client.py assumes ternary. - helpful.add("security", "-r", "--redirect", action="store_true", + helpful.add( + "security", "-r", "--redirect", action="store_true", help="Automatically redirect all HTTP traffic to HTTPS for the newly " "authenticated vhost.") @@ -487,7 +521,6 @@ def create_parser(plugins, args): _paths_parser(helpful) - return helpful.parser @@ -515,6 +548,7 @@ def _paths_parser(helpful): add("paths", "-s", "--server", default=flag_default("server"), help=config_help("server")) + def main(args=sys.argv[1:]): """Command line argument parsing and main script execution.""" # note: arg parser internally handles --help (and exits afterwards) diff --git a/letsencrypt/client.py b/letsencrypt/client.py index 279813640..b0cabbb6c 100644 --- a/letsencrypt/client.py +++ b/letsencrypt/client.py @@ -257,8 +257,8 @@ class Client(object): :param certr: ACME "certificate" resource. :type certr: :class:`acme.messages.Certificate` - :param str cert_path: Path to attempt to save the cert file - :param str chain_path: Path to attempt to save the chain file + :param str cert_path: Candidate path to a certificate. + :param str chain_path: Candidate path to a certificate chain. :returns: cert_path, chain_path (absolute paths to the actual files) :rtype: `tuple` of `str` diff --git a/letsencrypt/configuration.py b/letsencrypt/configuration.py index 6a808a6a9..d6b29bd73 100644 --- a/letsencrypt/configuration.py +++ b/letsencrypt/configuration.py @@ -17,10 +17,15 @@ class NamespaceConfig(object): :attr:`~letsencrypt.interfaces.IConfig.work_dir` and relative paths defined in :py:mod:`letsencrypt.constants`: - - ``temp_checkpoint_dir`` - - ``in_progress_dir`` - - ``cert_key_backup`` - - ``rec_token_dir`` + - `accounts_dir` + - `account_keys_dir` + - `cert_dir` + - `cert_key_backup` + - `in_progress_dir` + - `key_dir` + - `rec_token_dir` + - `renewer_config_file` + - `temp_checkpoint_dir` :ivar namespace: Namespace typically produced by :meth:`argparse.ArgumentParser.parse_args`. @@ -35,27 +40,12 @@ class NamespaceConfig(object): def __getattr__(self, name): return getattr(self.namespace, name) - @property - def temp_checkpoint_dir(self): # pylint: disable=missing-docstring - return os.path.join( - self.namespace.work_dir, constants.TEMP_CHECKPOINT_DIR) - - @property - def in_progress_dir(self): # pylint: disable=missing-docstring - return os.path.join(self.namespace.work_dir, constants.IN_PROGRESS_DIR) - @property def server_path(self): """File path based on ``server``.""" parsed = urlparse.urlparse(self.namespace.server) return (parsed.netloc + parsed.path).replace('/', os.path.sep) - @property - def cert_key_backup(self): # pylint: disable=missing-docstring - return os.path.join( - self.namespace.work_dir, constants.CERT_KEY_BACKUP_DIR, - self.server_path) - @property def accounts_dir(self): #pylint: disable=missing-docstring return os.path.join( @@ -63,11 +53,40 @@ class NamespaceConfig(object): @property def account_keys_dir(self): #pylint: disable=missing-docstring - return os.path.join( - self.namespace.config_dir, constants.ACCOUNTS_DIR, - self.server_path, constants.ACCOUNT_KEYS_DIR) + return os.path.join(self.accounts_dir, constants.ACCOUNT_KEYS_DIR) + + @property + def backup_dir(self): # pylint: disable=missing-docstring + return os.path.join(self.namespace.work_dir, constants.BACKUP_DIR) + + @property + def cert_key_backup(self): # pylint: disable=missing-docstring + return os.path.join(self.namespace.work_dir, + constants.CERT_KEY_BACKUP_DIR, self.server_path) + + @property + def cert_dir(self): # pylint: disable=missing-docstring + return os.path.join(self.namespace.config_dir, constants.CERT_DIR) + + @property + def in_progress_dir(self): # pylint: disable=missing-docstring + return os.path.join(self.namespace.work_dir, constants.IN_PROGRESS_DIR) + + @property + def key_dir(self): # pylint: disable=missing-docstring + return os.path.join(self.namespace.config_dir, constants.KEY_DIR) # TODO: This should probably include the server name @property def rec_token_dir(self): # pylint: disable=missing-docstring return os.path.join(self.namespace.work_dir, constants.REC_TOKEN_DIR) + + @property + def renewer_config_file(self): # pylint: disable=missing-docstring + return os.path.join( + self.namespace.config_dir, constants.RENEWER_CONFIG_FILENAME) + + @property + def temp_checkpoint_dir(self): # pylint: disable=missing-docstring + return os.path.join( + self.namespace.work_dir, constants.TEMP_CHECKPOINT_DIR) diff --git a/letsencrypt/constants.py b/letsencrypt/constants.py index 356b8ed14..df41fbe5b 100644 --- a/letsencrypt/constants.py +++ b/letsencrypt/constants.py @@ -7,7 +7,6 @@ from acme import challenges SETUPTOOLS_PLUGINS_ENTRY_POINT = "letsencrypt.plugins" """Setuptools entry point group name for plugins.""" - CLI_DEFAULTS = dict( config_files=["/etc/letsencrypt/cli.ini"], verbose_count=-(logging.WARNING / 10), @@ -16,14 +15,16 @@ CLI_DEFAULTS = dict( rollback_checkpoints=0, config_dir="/etc/letsencrypt", work_dir="/var/lib/letsencrypt", - backup_dir="/var/lib/letsencrypt/backups", - key_dir="/etc/letsencrypt/keys", - certs_dir="/etc/letsencrypt/certs", - cert_path="/etc/letsencrypt/certs/cert-letsencrypt.pem", - chain_path="/etc/letsencrypt/certs/chain-letsencrypt.pem", - renewer_config_file="/etc/letsencrypt/renewer.conf", no_verify_ssl=False, dvsni_port=challenges.DVSNI.PORT, + + # TODO: blocked by #485, values ignored + backup_dir="not used", + key_dir="not used", + certs_dir="not used", + cert_path="not used", + chain_path="not used", + renewer_config_file="not used", ) """Defaults for CLI flags and `.IConfig` attributes.""" @@ -61,23 +62,36 @@ List of expected options parameters: CONFIG_DIRS_MODE = 0o755 """Directory mode for ``.IConfig.config_dir`` et al.""" -TEMP_CHECKPOINT_DIR = "temp_checkpoint" -"""Temporary checkpoint directory (relative to IConfig.work_dir).""" - -IN_PROGRESS_DIR = "IN_PROGRESS" -"""Directory used before a permanent checkpoint is finalized (relative to -IConfig.work_dir).""" - -CERT_KEY_BACKUP_DIR = "keys-certs" -"""Directory where all certificates and keys are stored (relative to -IConfig.work_dir. Used for easy revocation.""" - ACCOUNTS_DIR = "accounts" """Directory where all accounts are saved.""" ACCOUNT_KEYS_DIR = "keys" -"""Directory where account keys are saved. Relative to ACCOUNTS_DIR.""" +"""Directory where account keys are saved. Relative to `ACCOUNTS_DIR`.""" + +BACKUP_DIR = "backups" +"""Directory (relative to `IConfig.work_dir`) where backups are kept.""" + +CERT_KEY_BACKUP_DIR = "keys-certs" +"""Directory where all certificates and keys are stored (relative to +`IConfig.work_dir`). Used for easy revocation.""" + +CERT_DIR = "certs" +"""See `.IConfig.cert_dir`.""" + +IN_PROGRESS_DIR = "IN_PROGRESS" +"""Directory used before a permanent checkpoint is finalized (relative to +`IConfig.work_dir`).""" + +KEY_DIR = "keys" +"""Directory (relative to `IConfig.config_dir`) where keys are saved.""" + +TEMP_CHECKPOINT_DIR = "temp_checkpoint" +"""Temporary checkpoint directory (relative to `IConfig.work_dir`).""" REC_TOKEN_DIR = "recovery_tokens" """Directory where all recovery tokens are saved (relative to -IConfig.work_dir).""" +`IConfig.work_dir`).""" + + +RENEWER_CONFIG_FILENAME = "renewer.conf" +"""Renewer config file name (relative to `IConfig.config_dir`).""" diff --git a/letsencrypt/crypto_util.py b/letsencrypt/crypto_util.py index 7e1bb58fb..31df697c0 100644 --- a/letsencrypt/crypto_util.py +++ b/letsencrypt/crypto_util.py @@ -55,7 +55,7 @@ def init_save_key(key_size, key_dir, keyname="key-letsencrypt.pem"): return le_util.Key(key_path, key_pem) -def init_save_csr(privkey, names, cert_dir, csrname="csr-letsencrypt.pem"): +def init_save_csr(privkey, names, path, csrname="csr-letsencrypt.pem"): """Initialize a CSR with the given private key. :param privkey: Key to include in the CSR @@ -63,7 +63,7 @@ def init_save_csr(privkey, names, cert_dir, csrname="csr-letsencrypt.pem"): :param set names: `str` names to include in the CSR - :param str cert_dir: Certificate save directory. + :param str path: Certificate save directory. :returns: CSR :rtype: :class:`letsencrypt.le_util.CSR` @@ -72,9 +72,9 @@ def init_save_csr(privkey, names, cert_dir, csrname="csr-letsencrypt.pem"): csr_pem, csr_der = make_csr(privkey.pem, names) # Save CSR - le_util.make_or_verify_dir(cert_dir, 0o755, os.geteuid()) + le_util.make_or_verify_dir(path, 0o755, os.geteuid()) csr_f, csr_filename = le_util.unique_file( - os.path.join(cert_dir, csrname), 0o644) + os.path.join(path, csrname), 0o644) csr_f.write(csr_pem) csr_f.close() diff --git a/letsencrypt/interfaces.py b/letsencrypt/interfaces.py index d529127e9..3c71fc1fa 100644 --- a/letsencrypt/interfaces.py +++ b/letsencrypt/interfaces.py @@ -155,32 +155,29 @@ class IConfig(zope.interface.Interface): config_dir = zope.interface.Attribute("Configuration directory.") work_dir = zope.interface.Attribute("Working directory.") - backup_dir = zope.interface.Attribute("Configuration backups directory.") - temp_checkpoint_dir = zope.interface.Attribute( - "Temporary checkpoint directory.") - in_progress_dir = zope.interface.Attribute( - "Directory used before a permanent checkpoint is finalized.") - cert_key_backup = zope.interface.Attribute( - "Directory where all certificates and keys are stored. " - "Used for easy revocation.") + accounts_dir = zope.interface.Attribute( "Directory where all account information is stored.") account_keys_dir = zope.interface.Attribute( "Directory where all account keys are stored.") + backup_dir = zope.interface.Attribute("Configuration backups directory.") + cert_dir = zope.interface.Attribute( + "Directory where newly generated Certificate Signing Requests " + "(CSRs) and certificates not enrolled in the renewer are saved.") + cert_key_backup = zope.interface.Attribute( + "Directory where all certificates and keys are stored. " + "Used for easy revocation.") + in_progress_dir = zope.interface.Attribute( + "Directory used before a permanent checkpoint is finalized.") + key_dir = zope.interface.Attribute("Keys storage.") rec_token_dir = zope.interface.Attribute( "Directory where all recovery tokens are saved.") - key_dir = zope.interface.Attribute("Keys storage.") - cert_dir = zope.interface.Attribute("Certificates storage.") - - le_vhost_ext = zope.interface.Attribute( - "SSL vhost configuration extension.") + temp_checkpoint_dir = zope.interface.Attribute( + "Temporary checkpoint directory.") renewer_config_file = zope.interface.Attribute( "Location of renewal configuration file.") - cert_path = zope.interface.Attribute("Let's Encrypt certificate file path.") - chain_path = zope.interface.Attribute("Let's Encrypt chain file path.") - no_verify_ssl = zope.interface.Attribute( "Disable SSL certificate verification.") dvsni_port = zope.interface.Attribute( @@ -191,6 +188,11 @@ class IConfig(zope.interface.Interface): no_simple_http_tls = zope.interface.Attribute( "Do not use TLS when solving SimpleHTTP challenges.") + # TODO: the following are not used, but blocked by #485 + le_vhost_ext = zope.interface.Attribute("not used") + cert_path = zope.interface.Attribute("not used") + chain_path = zope.interface.Attribute("not used") + class IInstaller(IPlugin): """Generic Let's Encrypt Installer Interface. diff --git a/letsencrypt/plugins/common.py b/letsencrypt/plugins/common.py index 90296c5c7..03ddf7df7 100644 --- a/letsencrypt/plugins/common.py +++ b/letsencrypt/plugins/common.py @@ -175,10 +175,10 @@ class Dvsni(object): # test utils -def setup_ssl_options(config_dir, mod_ssl_conf): +def setup_ssl_options(config_dir, src, dest): """Move the ssl_options into position and return the path.""" - option_path = os.path.join(config_dir, "options-ssl.conf") - shutil.copyfile(mod_ssl_conf, option_path) + option_path = os.path.join(config_dir, dest) + shutil.copyfile(src, option_path) return option_path diff --git a/letsencrypt/tests/configuration_test.py b/letsencrypt/tests/configuration_test.py index d25368feb..d5e9296dd 100644 --- a/letsencrypt/tests/configuration_test.py +++ b/letsencrypt/tests/configuration_test.py @@ -30,23 +30,31 @@ class NamespaceConfigTest(unittest.TestCase): @mock.patch('letsencrypt.configuration.constants') def test_dynamic_dirs(self, constants): - constants.TEMP_CHECKPOINT_DIR = 't' - constants.IN_PROGRESS_DIR = '../p' - constants.CERT_KEY_BACKUP_DIR = 'c/' - constants.REC_TOKEN_DIR = '/r' constants.ACCOUNTS_DIR = 'acc' constants.ACCOUNT_KEYS_DIR = 'keys' + constants.BACKUP_DIR = 'backups' + constants.CERT_KEY_BACKUP_DIR = 'c/' + constants.CERT_DIR = 'certs' + constants.IN_PROGRESS_DIR = '../p' + constants.KEY_DIR = 'keys' + constants.REC_TOKEN_DIR = '/r' + constants.RENEWER_CONFIG_FILENAME = 'r.conf' + constants.TEMP_CHECKPOINT_DIR = 't' - self.assertEqual(self.config.temp_checkpoint_dir, '/tmp/foo/t') - self.assertEqual(self.config.in_progress_dir, '/tmp/foo/../p') - self.assertEqual( - self.config.cert_key_backup, '/tmp/foo/c/acme-server.org:443/new') - self.assertEqual(self.config.rec_token_dir, '/r') self.assertEqual( self.config.accounts_dir, '/tmp/config/acc/acme-server.org:443/new') self.assertEqual( self.config.account_keys_dir, '/tmp/config/acc/acme-server.org:443/new/keys') + self.assertEqual(self.config.backup_dir, '/tmp/foo/backups') + self.assertEqual(self.config.cert_dir, '/tmp/config/certs') + self.assertEqual( + self.config.cert_key_backup, '/tmp/foo/c/acme-server.org:443/new') + self.assertEqual(self.config.in_progress_dir, '/tmp/foo/../p') + self.assertEqual(self.config.key_dir, '/tmp/config/keys') + self.assertEqual(self.config.rec_token_dir, '/r') + self.assertEqual(self.config.renewer_config_file, '/tmp/config/r.conf') + self.assertEqual(self.config.temp_checkpoint_dir, '/tmp/foo/t') if __name__ == '__main__': diff --git a/letsencrypt_apache/configurator.py b/letsencrypt_apache/configurator.py index 2a0bf518b..ee522d781 100644 --- a/letsencrypt_apache/configurator.py +++ b/letsencrypt_apache/configurator.py @@ -89,8 +89,6 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): def add_parser_arguments(cls, add): add("server-root", default=constants.CLI_DEFAULTS["server_root"], help="Apache server root directory.") - add("mod-ssl-conf", default=constants.CLI_DEFAULTS["mod_ssl_conf"], - help="Contains standard Apache SSL directives.") add("ctl", default=constants.CLI_DEFAULTS["ctl"], help="Path to the 'apache2ctl' binary, used for 'configtest' and " "retrieving Apache2 version number.") @@ -99,6 +97,9 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): add("init-script", default=constants.CLI_DEFAULTS["init_script"], help="Path to the Apache init script (used for server " "reload/restart).") + add("le-vhost-ext", default=constants.CLI_DEFAULTS["le_vhost_ext"], + help="SSL vhost configuration extension.") + def __init__(self, *args, **kwargs): """Initialize an Apache Configurator. @@ -125,10 +126,15 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): self.vhosts = None self._enhance_func = {"redirect": self._enable_redirect} + @property + def mod_ssl_conf(self): + """Full absolute path to SSL configuration file.""" + return os.path.join(self.config.config_dir, constants.MOD_SSL_CONF_DEST) + def prepare(self): """Prepare the authenticator/installer.""" self.parser = parser.ApacheParser( - self.aug, self.conf('server-root'), self.conf('mod-ssl-conf')) + self.aug, self.conf('server-root'), self.mod_ssl_conf) # Check for errors in parsing files with Augeas self.check_parsing_errors("httpd.aug") @@ -146,7 +152,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): # on initialization self._prepare_server_https() - temp_install(self.conf('mod-ssl-conf')) + temp_install(self.mod_ssl_conf) def deploy_cert(self, domain, cert_path, key_path, chain_path=None): """Deploys certificate to specified virtual host. @@ -445,7 +451,8 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): """Makes an ssl_vhost version of a nonssl_vhost. Duplicates vhost and adds default ssl options - New vhost will reside as (nonssl_vhost.path) + ``IConfig.le_vhost_ext`` + New vhost will reside as (nonssl_vhost.path) + + ``letsencrypt_apache.constants.CLI_DEFAULTS["le_vhost_ext"]`` .. note:: This function saves the configuration @@ -459,9 +466,9 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): avail_fp = nonssl_vhost.filep # Get filepath of new ssl_vhost if avail_fp.endswith(".conf"): - ssl_fp = avail_fp[:-(len(".conf"))] + self.config.le_vhost_ext + ssl_fp = avail_fp[:-(len(".conf"))] + self.conf("le_vhost_ext") else: - ssl_fp = avail_fp + self.config.le_vhost_ext + ssl_fp = avail_fp + self.conf("le_vhost_ext") # First register the creation so that it is properly removed if # configuration is rolled back @@ -1161,4 +1168,4 @@ def temp_install(options_ssl): # Check to make sure options-ssl.conf is installed if not os.path.isfile(options_ssl): - shutil.copyfile(constants.MOD_SSL_CONF, options_ssl) + shutil.copyfile(constants.MOD_SSL_CONF_SRC, options_ssl) diff --git a/letsencrypt_apache/constants.py b/letsencrypt_apache/constants.py index bcc3a09bd..cb75276b2 100644 --- a/letsencrypt_apache/constants.py +++ b/letsencrypt_apache/constants.py @@ -4,15 +4,17 @@ import pkg_resources CLI_DEFAULTS = dict( server_root="/etc/apache2", - mod_ssl_conf="/etc/letsencrypt/options-ssl-apache.conf", ctl="apache2ctl", enmod="a2enmod", init_script="/etc/init.d/apache2", + le_vhost_ext="-le-ssl.conf", ) """CLI defaults.""" +MOD_SSL_CONF_DEST = "options-ssl-apache.conf" +"""Name of the mod_ssl config file as saved in `IConfig.config_dir`.""" -MOD_SSL_CONF = pkg_resources.resource_filename( +MOD_SSL_CONF_SRC = pkg_resources.resource_filename( "letsencrypt_apache", "options-ssl-apache.conf") """Path to the Apache mod_ssl config file found in the Let's Encrypt distribution.""" diff --git a/letsencrypt_apache/tests/configurator_test.py b/letsencrypt_apache/tests/configurator_test.py index 3cdabe0b1..cfd8c0574 100644 --- a/letsencrypt_apache/tests/configurator_test.py +++ b/letsencrypt_apache/tests/configurator_test.py @@ -32,8 +32,7 @@ class TwoVhost80Test(util.ApacheTest): "mod_loaded") as mock_load: mock_load.return_value = True self.config = util.get_apache_configurator( - self.config_path, self.config_dir, self.work_dir, - self.ssl_options) + self.config_path, self.config_dir, self.work_dir) self.vh_truth = util.get_vh_truth( self.temp_dir, "debian_apache_2_4/two_vhost_80") diff --git a/letsencrypt_apache/tests/dvsni_test.py b/letsencrypt_apache/tests/dvsni_test.py index bf43bc359..27e9b2584 100644 --- a/letsencrypt_apache/tests/dvsni_test.py +++ b/letsencrypt_apache/tests/dvsni_test.py @@ -24,8 +24,7 @@ class DvsniPerformTest(util.ApacheTest): "mod_loaded") as mock_load: mock_load.return_value = True config = util.get_apache_configurator( - self.config_path, self.config_dir, self.work_dir, - self.ssl_options) + self.config_path, self.config_dir, self.work_dir) from letsencrypt_apache import dvsni self.sni = dvsni.ApacheDvsni(config) diff --git a/letsencrypt_apache/tests/util.py b/letsencrypt_apache/tests/util.py index 071c13ef0..0769f6050 100644 --- a/letsencrypt_apache/tests/util.py +++ b/letsencrypt_apache/tests/util.py @@ -22,7 +22,8 @@ class ApacheTest(unittest.TestCase): # pylint: disable=too-few-public-methods pkg="letsencrypt_apache.tests") self.ssl_options = common.setup_ssl_options( - self.config_dir, constants.MOD_SSL_CONF) + self.config_dir, constants.MOD_SSL_CONF_SRC, + constants.MOD_SSL_CONF_DEST) self.config_path = os.path.join( self.temp_dir, "debian_apache_2_4/two_vhost_80/apache2") @@ -34,7 +35,7 @@ class ApacheTest(unittest.TestCase): # pylint: disable=too-few-public-methods def get_apache_configurator( - config_path, config_dir, work_dir, ssl_options, version=(2, 4, 7)): + config_path, config_dir, work_dir, version=(2, 4, 7)): """Create an Apache Configurator with the specified options.""" backups = os.path.join(work_dir, "backups") @@ -46,8 +47,7 @@ def get_apache_configurator( config = configurator.ApacheConfigurator( config=mock.MagicMock( apache_server_root=config_path, - apache_mod_ssl_conf=ssl_options, - le_vhost_ext="-le-ssl.conf", + apache_le_vhost_ext=constants.CLI_DEFAULTS["le_vhost_ext"], backup_dir=backups, config_dir=config_dir, temp_checkpoint_dir=os.path.join(work_dir, "temp_checkpoints"), diff --git a/letsencrypt_nginx/configurator.py b/letsencrypt_nginx/configurator.py index 8490e7183..29fa8cd16 100644 --- a/letsencrypt_nginx/configurator.py +++ b/letsencrypt_nginx/configurator.py @@ -56,8 +56,6 @@ class NginxConfigurator(common.Plugin): def add_parser_arguments(cls, add): add("server-root", default=constants.CLI_DEFAULTS["server_root"], help="Nginx server root directory.") - add("mod-ssl-conf", default=constants.CLI_DEFAULTS["mod_ssl_conf"], - help="Contains standard nginx SSL directives.") add("ctl", default=constants.CLI_DEFAULTS["ctl"], help="Path to the " "'nginx' binary, used for 'configtest' and retrieving nginx " "version number.") @@ -91,18 +89,22 @@ class NginxConfigurator(common.Plugin): self.reverter = reverter.Reverter(self.config) self.reverter.recovery_routine() + @property + def mod_ssl_conf(self): + """Full absolute path to SSL configuration file.""" + return os.path.join(self.config.config_dir, constants.MOD_SSL_CONF_DEST) + # This is called in determine_authenticator and determine_installer def prepare(self): """Prepare the authenticator/installer.""" self.parser = parser.NginxParser( - self.conf('server-root'), - self.conf('mod-ssl-conf')) + self.conf('server-root'), self.mod_ssl_conf) # Set Version if self.version is None: self.version = self.get_version() - temp_install(self.conf('mod-ssl-conf')) + temp_install(self.mod_ssl_conf) # Entry point in main.py for installing cert def deploy_cert(self, domain, cert_path, key_path, chain_path=None): @@ -583,4 +585,4 @@ def temp_install(options_ssl): # Check to make sure options-ssl.conf is installed if not os.path.isfile(options_ssl): - shutil.copyfile(constants.MOD_SSL_CONF, options_ssl) + shutil.copyfile(constants.MOD_SSL_CONF_SRC, options_ssl) diff --git a/letsencrypt_nginx/constants.py b/letsencrypt_nginx/constants.py index 73b80c809..08b205d2a 100644 --- a/letsencrypt_nginx/constants.py +++ b/letsencrypt_nginx/constants.py @@ -4,13 +4,15 @@ import pkg_resources CLI_DEFAULTS = dict( server_root="/etc/nginx", - mod_ssl_conf="/etc/letsencrypt/options-ssl-nginx.conf", ctl="nginx", ) """CLI defaults.""" -MOD_SSL_CONF = pkg_resources.resource_filename( +MOD_SSL_CONF_DEST = "options-ssl-nginx.conf" +"""Name of the mod_ssl config file as saved in `IConfig.config_dir`.""" + +MOD_SSL_CONF_SRC = pkg_resources.resource_filename( "letsencrypt_nginx", "options-ssl-nginx.conf") -"""Path to the Nginx mod_ssl config file found in the Let's Encrypt +"""Path to the nginx mod_ssl config file found in the Let's Encrypt distribution.""" diff --git a/letsencrypt_nginx/tests/configurator_test.py b/letsencrypt_nginx/tests/configurator_test.py index 4accfa8ea..e134605e9 100644 --- a/letsencrypt_nginx/tests/configurator_test.py +++ b/letsencrypt_nginx/tests/configurator_test.py @@ -21,8 +21,7 @@ class NginxConfiguratorTest(util.NginxTest): super(NginxConfiguratorTest, self).setUp() self.config = util.get_nginx_configurator( - self.config_path, self.config_dir, self.work_dir, - self.ssl_options) + self.config_path, self.config_dir, self.work_dir) def tearDown(self): shutil.rmtree(self.temp_dir) diff --git a/letsencrypt_nginx/tests/dvsni_test.py b/letsencrypt_nginx/tests/dvsni_test.py index ef28e6918..5b46e8da6 100644 --- a/letsencrypt_nginx/tests/dvsni_test.py +++ b/letsencrypt_nginx/tests/dvsni_test.py @@ -51,8 +51,7 @@ class DvsniPerformTest(util.NginxTest): super(DvsniPerformTest, self).setUp() config = util.get_nginx_configurator( - self.config_path, self.config_dir, self.work_dir, - self.ssl_options) + self.config_path, self.config_dir, self.work_dir) from letsencrypt_nginx import dvsni self.sni = dvsni.NginxDvsni(config) diff --git a/letsencrypt_nginx/tests/util.py b/letsencrypt_nginx/tests/util.py index 6522abef1..77c2ea198 100644 --- a/letsencrypt_nginx/tests/util.py +++ b/letsencrypt_nginx/tests/util.py @@ -20,7 +20,8 @@ class NginxTest(unittest.TestCase): # pylint: disable=too-few-public-methods "etc_nginx", "letsencrypt_nginx.tests") self.ssl_options = common.setup_ssl_options( - self.config_dir, constants.MOD_SSL_CONF) + self.config_dir, constants.MOD_SSL_CONF_SRC, + constants.MOD_SSL_CONF_DEST) self.config_path = os.path.join(self.temp_dir, "etc_nginx") @@ -38,18 +39,21 @@ def get_data_filename(filename): def get_nginx_configurator( - config_path, config_dir, work_dir, ssl_options, version=(1, 6, 2)): + config_path, config_dir, work_dir, version=(1, 6, 2)): """Create an Nginx Configurator with the specified options.""" backups = os.path.join(work_dir, "backups") config = configurator.NginxConfigurator( config=mock.MagicMock( - nginx_server_root=config_path, nginx_mod_ssl_conf=ssl_options, - le_vhost_ext="-le-ssl.conf", backup_dir=backups, - config_dir=config_dir, work_dir=work_dir, + nginx_server_root=config_path, + le_vhost_ext="-le-ssl.conf", + config_dir=config_dir, + work_dir=work_dir, + backup_dir=backups, temp_checkpoint_dir=os.path.join(work_dir, "temp_checkpoints"), - in_progress_dir=os.path.join(backups, "IN_PROGRESS")), + in_progress_dir=os.path.join(backups, "IN_PROGRESS"), + ), name="nginx", version=version) config.prepare()