From ae6c7cb9363fa97d30d000516782968c82bdfea0 Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Fri, 22 May 2015 07:28:21 +0000 Subject: [PATCH 01/16] --le-vhost-ext -> --apache-le-vhost-ext --- letsencrypt/cli.py | 4 +--- letsencrypt_apache/configurator.py | 10 +++++++--- letsencrypt_apache/constants.py | 1 + letsencrypt_apache/tests/util.py | 2 +- letsencrypt_nginx/tests/util.py | 3 +-- 5 files changed, 11 insertions(+), 9 deletions(-) diff --git a/letsencrypt/cli.py b/letsencrypt/cli.py index defa7633d..ecf6764f7 100644 --- a/letsencrypt/cli.py +++ b/letsencrypt/cli.py @@ -318,13 +318,11 @@ def _paths_parser(parser): help=config_help("work_dir")) add("--backup-dir", default=flag_default("backup_dir"), help=config_help("backup_dir")) + add("--key-dir", default=flag_default("key_dir"), help=config_help("key_dir")) add("--cert-dir", default=flag_default("certs_dir"), help=config_help("cert_dir")) - - add("--le-vhost-ext", default="-le-ssl.conf", - help=config_help("le_vhost_ext")) add("--cert-path", default=flag_default("cert_path"), help=config_help("cert_path")) add("--chain-path", default=flag_default("chain_path"), diff --git a/letsencrypt_apache/configurator.py b/letsencrypt_apache/configurator.py index 102718e13..24ccb0859 100644 --- a/letsencrypt_apache/configurator.py +++ b/letsencrypt_apache/configurator.py @@ -97,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. @@ -448,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 @@ -462,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 diff --git a/letsencrypt_apache/constants.py b/letsencrypt_apache/constants.py index b40e2ac65..865741823 100644 --- a/letsencrypt_apache/constants.py +++ b/letsencrypt_apache/constants.py @@ -8,6 +8,7 @@ CLI_DEFAULTS = dict( ctl="apache2ctl", enmod="a2enmod", init_script="/etc/init.d/apache2", + le_vhost_ext="-le-ssl.conf", ) """CLI defaults.""" diff --git a/letsencrypt_apache/tests/util.py b/letsencrypt_apache/tests/util.py index fc2dcfc79..e637b0890 100644 --- a/letsencrypt_apache/tests/util.py +++ b/letsencrypt_apache/tests/util.py @@ -75,7 +75,7 @@ def get_apache_configurator( 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/tests/util.py b/letsencrypt_nginx/tests/util.py index c53250f1b..45d1fa184 100644 --- a/letsencrypt_nginx/tests/util.py +++ b/letsencrypt_nginx/tests/util.py @@ -46,8 +46,7 @@ def get_nginx_configurator( 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, + backup_dir=backups, config_dir=config_dir, work_dir=work_dir, temp_checkpoint_dir=os.path.join(work_dir, "temp_checkpoints"), in_progress_dir=os.path.join(backups, "IN_PROGRESS")), name="nginx", From f00b674131aab1fa86c50027b65c6665e3f4003a Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Fri, 22 May 2015 07:55:22 +0000 Subject: [PATCH 02/16] Move --cert-path and --chain-path from global IConfig to subparsers. --- letsencrypt/cli.py | 52 +++++++++++++++++++++++++-------------- letsencrypt/client.py | 17 +++++++------ letsencrypt/constants.py | 18 ++++++++------ letsencrypt/interfaces.py | 8 ++---- 4 files changed, 56 insertions(+), 39 deletions(-) diff --git a/letsencrypt/cli.py b/letsencrypt/cli.py index ecf6764f7..8cd8d9955 100644 --- a/letsencrypt/cli.py +++ b/letsencrypt/cli.py @@ -98,8 +98,9 @@ def run(args, config, plugins): return "Configurator could not be determined" acme, doms = _common_run(args, config, acc, authenticator, installer) - cert_key, cert_path, chain_path = acme.obtain_certificate(doms) - acme.deploy_certificate(doms, cert_key, cert_path, chain_path) + cert_key, act_cert_path, act_chain_path = acme.obtain_certificate( + doms, args.cert_path, args.chain_path) + acme.deploy_certificate(doms, cert_key, act_cert_path, act_chain_path) acme.enhance_config(doms, args.redirect) @@ -121,7 +122,7 @@ def auth(args, config, plugins): acme, doms = _common_run( args, config, acc, authenticator=authenticator, installer=installer) - acme.obtain_certificate(doms) + acme.obtain_certificate(doms, args.cert_path, args.chain_path) def install(args, config, plugins): @@ -135,21 +136,21 @@ def install(args, config, plugins): return "Installer could not be determined" acme, doms = _common_run( args, config, acc, authenticator=None, installer=installer) - assert args.cert_path is not None + assert args.cert_path is not None # required=True in the subparser acme.deploy_certificate(doms, acc.key, args.cert_path, args.chain_path) acme.enhance_config(doms, args.redirect) def revoke(args, unused_config, unused_plugins): """Revoke.""" - if args.rev_cert is None and args.rev_key is None: - return "At least one of --certificate or --key is required" + if args.cert_path is None and args.key_path is None: + return "At least one of --cert-path or --key-path is required" # This depends on the renewal config and cannot be completed yet. zope.component.getUtility(interfaces.IDisplay).notification( "Revocation is not available with the new Boulder server yet.") #client.revoke(args.installer, config, plugins, args.no_confirm, - # args.rev_cert, args.rev_key) + # args.cert_path, args.key_path) def rollback(args, config, plugins): @@ -248,13 +249,29 @@ def create_parser(plugins): subparser.set_defaults(func=func) return subparser - add_subparser("run", run) - add_subparser("auth", auth) - add_subparser("install", install) - parser_revoke = add_subparser("revoke", revoke) - parser_rollback = add_subparser("rollback", rollback) + parser_run = add_subparser("run", run) + parser_auth = add_subparser("auth", auth) add_subparser("config_changes", config_changes) + for subparser in (parser_run, parser_auth): + subparser.add_argument( + "--cert-path", default=flag_default("cert_path"), + help="Candidate path where a freshly issued certificate will " + "be saved to. If a file already exists at the provided " + "path, dirpath/0001_filename.ext will be attempted " + "(securely).") + subparser.add_argument( + "--chain-path", default=flag_default("chain_path"), + help="Candidate path (see --cert-path help) where an " + "accompanying certificate chain will be saved.") + + parser_install = add_subparser("install", install) + parser_install.add_argument( + "--cert-path", required=True, help="Path to a certificate that " + "is going to be installed.") + parser_install.add_argument( + "--chain-path", help="Accompanying path to a certificate chain.") + parser_plugins = add_subparser("plugins", plugins_cmd) parser_plugins.add_argument("--init", action="store_true") parser_plugins.add_argument("--prepare", action="store_true") @@ -287,13 +304,14 @@ def create_parser(plugins): help="Automatically redirect all HTTP traffic to HTTPS for the newly " "authenticated vhost.") + parser_revoke = add_subparser("revoke", revoke) parser_revoke.add_argument( - "--certificate", dest="rev_cert", type=read_file, metavar="CERT_PATH", - help="Revoke a specific certificate.") + "--cert-path", type=read_file, help="Revoke a specific certificate.") parser_revoke.add_argument( - "--key", dest="rev_key", type=read_file, metavar="KEY_PATH", + "--key-path", type=read_file, help="Revoke all certs generated by the provided authorized key.") + parser_rollback = add_subparser("rollback", rollback) parser_rollback.add_argument( "--checkpoints", type=int, metavar="N", default=flag_default("rollback_checkpoints"), @@ -323,10 +341,6 @@ def _paths_parser(parser): help=config_help("key_dir")) add("--cert-dir", default=flag_default("certs_dir"), help=config_help("cert_dir")) - add("--cert-path", default=flag_default("cert_path"), - help=config_help("cert_path")) - add("--chain-path", default=flag_default("chain_path"), - help=config_help("chain_path")) return parser diff --git a/letsencrypt/client.py b/letsencrypt/client.py index ae1667dfa..4362592f8 100644 --- a/letsencrypt/client.py +++ b/letsencrypt/client.py @@ -95,7 +95,7 @@ class Client(object): self.account.save() - def obtain_certificate(self, domains, csr=None): + def obtain_certificate(self, domains, cert_path, chain_path, csr=None): """Obtains a certificate from the ACME server. :meth:`.register` must be called before :meth:`.obtain_certificate` @@ -104,6 +104,9 @@ class Client(object): :param set domains: domains to get a certificate + :param str cert_path: Candidate path to a certificate. + :param str chain_path: Candidate path to a certificate chain. + :param csr: CSR must contain requested domains, the key used to generate this CSR can be different than self.authkey :type csr: :class:`CSR` @@ -137,13 +140,13 @@ class Client(object): authzr) # Save Certificate - cert_path, chain_path = self.save_certificate( - certr, self.config.cert_path, self.config.chain_path) + act_cert_path, act_chain_path = self.save_certificate( + certr, cert_path, chain_path) revoker.Revoker.store_cert_key( - cert_path, self.account.key.file, self.config) + act_cert_path, self.account.key.file, self.config) - return cert_key, cert_path, chain_path + return cert_key, act_cert_path, act_chain_path def save_certificate(self, certr, cert_path, chain_path): # pylint: disable=no-self-use @@ -152,8 +155,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/constants.py b/letsencrypt/constants.py index 9ff0b128c..29365797a 100644 --- a/letsencrypt/constants.py +++ b/letsencrypt/constants.py @@ -1,5 +1,6 @@ """Let's Encrypt constants.""" import logging +import os from acme import challenges @@ -8,19 +9,22 @@ SETUPTOOLS_PLUGINS_ENTRY_POINT = "letsencrypt.plugins" """Setuptools entry point group name for plugins.""" +_CLI_DEFAULT_CONFIG_DIR = "/etc/letsencrypt" +_CLI_DEFAULT_WORK_DIR = "/var/lib/letsencrypt" +_CLI_DEFAULT_CERT_DIR = os.path.join(_CLI_DEFAULT_CONFIG_DIR, "certs") CLI_DEFAULTS = dict( config_files=["/etc/letsencrypt/cli.ini"], verbose_count=-(logging.WARNING / 10), server="https://www.letsencrypt-demo.org/acme/new-reg", rsa_key_size=2048, 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", + config_dir=_CLI_DEFAULT_CONFIG_DIR, + work_dir=_CLI_DEFAULT_CONFIG_DIR, + backup_dir=os.path.join(_CLI_DEFAULT_WORK_DIR, "backups"), + key_dir=os.path.join(_CLI_DEFAULT_CONFIG_DIR, "keys"), + certs_dir=_CLI_DEFAULT_CERT_DIR, + cert_path=os.path.join(_CLI_DEFAULT_CERT_DIR, "cert-letsencrypt.pem"), + chain_path=os.path.join(_CLI_DEFAULT_CERT_DIR, "chain-letsencrypt.pem"), test_mode=False, ) """Defaults for CLI flags and `.IConfig` attributes.""" diff --git a/letsencrypt/interfaces.py b/letsencrypt/interfaces.py index 609b9410a..68d442227 100644 --- a/letsencrypt/interfaces.py +++ b/letsencrypt/interfaces.py @@ -170,13 +170,9 @@ class IConfig(zope.interface.Interface): "Directory where all account keys are stored.") 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.") - cert_path = zope.interface.Attribute("Let's Encrypt certificate file path.") - chain_path = zope.interface.Attribute("Let's Encrypt chain file path.") + key_dir = zope.interface.Attribute("Keys storage.") + cert_dir = zope.interface.Attribute("Certificates and CSRs storage.") test_mode = zope.interface.Attribute( "Test mode. Disables certificate verification.") From 53d65d005e20906e4af9da985f85d2a10f6ec6ba Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Fri, 22 May 2015 08:43:58 +0000 Subject: [PATCH 03/16] CLI: improve plugins help messages, refactor code --- letsencrypt/cli.py | 140 +++++++++++++++++++++++++++------------------ 1 file changed, 85 insertions(+), 55 deletions(-) diff --git a/letsencrypt/cli.py b/letsencrypt/cli.py index 8cd8d9955..5218af06a 100644 --- a/letsencrypt/cli.py +++ b/letsencrypt/cli.py @@ -242,50 +242,6 @@ def create_parser(plugins): add("--test-mode", action="store_true", help=config_help("test_mode"), default=flag_default("test_mode")) - subparsers = 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__) - subparser.set_defaults(func=func) - return subparser - - parser_run = add_subparser("run", run) - parser_auth = add_subparser("auth", auth) - add_subparser("config_changes", config_changes) - - for subparser in (parser_run, parser_auth): - subparser.add_argument( - "--cert-path", default=flag_default("cert_path"), - help="Candidate path where a freshly issued certificate will " - "be saved to. If a file already exists at the provided " - "path, dirpath/0001_filename.ext will be attempted " - "(securely).") - subparser.add_argument( - "--chain-path", default=flag_default("chain_path"), - help="Candidate path (see --cert-path help) where an " - "accompanying certificate chain will be saved.") - - parser_install = add_subparser("install", install) - parser_install.add_argument( - "--cert-path", required=True, help="Path to a certificate that " - "is going to be installed.") - parser_install.add_argument( - "--chain-path", help="Accompanying path to a certificate chain.") - - parser_plugins = add_subparser("plugins", plugins_cmd) - parser_plugins.add_argument("--init", action="store_true") - parser_plugins.add_argument("--prepare", action="store_true") - parser_plugins.add_argument( - "--authenticators", action="append_const", dest="ifaces", - const=interfaces.IAuthenticator) - parser_plugins.add_argument( - "--installers", action="append_const", dest="ifaces", - const=interfaces.IInstaller) - - parser.add_argument("--configurator") - parser.add_argument("-a", "--authenticator") - parser.add_argument("-i", "--installer") - # positional arg shadows --domains, instead of appending, and # --domains is useful, because it can be stored in config #for subparser in parser_run, parser_auth, parser_install: @@ -304,29 +260,75 @@ def create_parser(plugins): help="Automatically redirect all HTTP traffic to HTTPS for the newly " "authenticated vhost.") + _paths_parser(parser.add_argument_group("paths")) + # _plugins_parsing should be the last thing to act upon the main + # parser (--help should display plugin-specific options last) + _plugins_parsing(parser, plugins) + + _create_subparsers(parser) + + return parser + + +def _create_subparsers(parser): + subparsers = 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__) + subparser.set_defaults(func=func) + return subparser + + # the order of add_subparser() calls is important: it defines the + # order in which subparser names will be displayed in --help + parser_run = add_subparser("run", run) + parser_auth = add_subparser("auth", auth) + parser_install = add_subparser("install", install) + parser_plugins = add_subparser("plugins", plugins_cmd) parser_revoke = add_subparser("revoke", revoke) + parser_rollback = add_subparser("rollback", rollback) + add_subparser("config_changes", config_changes) + + for subparser in (parser_run, parser_auth): + subparser.add_argument( + "--cert-path", default=flag_default("cert_path"), + help="Candidate path where a freshly issued certificate will " + "be saved to. If a file already exists at the provided " + "path, dirpath/0001_filename.ext will be attempted " + "(securely).") + subparser.add_argument( + "--chain-path", default=flag_default("chain_path"), + help="Candidate path (see --cert-path help) where an " + "accompanying certificate chain will be saved.") + + parser_install.add_argument( + "--cert-path", required=True, help="Path to a certificate that " + "is going to be installed.") + parser_install.add_argument( + "--chain-path", help="Accompanying path to a certificate chain.") + + parser_plugins.add_argument( + "--init", action="store_true", help="Initialize plugins.") + parser_plugins.add_argument("--prepare", action="store_true", + help="Initialize and prepare plugins.") + parser_plugins.add_argument( + "--authenticators", action="append_const", dest="ifaces", + const=interfaces.IAuthenticator, + help="Limit to authenticator plugins only.") + parser_plugins.add_argument( + "--installers", action="append_const", dest="ifaces", + const=interfaces.IInstaller, help="Limit to installer plugins only.") + parser_revoke.add_argument( "--cert-path", type=read_file, help="Revoke a specific certificate.") parser_revoke.add_argument( "--key-path", type=read_file, help="Revoke all certs generated by the provided authorized key.") - parser_rollback = add_subparser("rollback", rollback) parser_rollback.add_argument( "--checkpoints", type=int, metavar="N", default=flag_default("rollback_checkpoints"), help="Revert configuration N number of checkpoints.") - _paths_parser(parser.add_argument_group("paths")) - - # TODO: plugin_parser should be called for every detected plugin - for name, plugin_ep in plugins.iteritems(): - plugin_ep.plugin_cls.inject_parser_options( - parser.add_argument_group( - name, description=plugin_ep.description), name) - - return parser - def _paths_parser(parser): add = parser.add_argument @@ -345,6 +347,34 @@ def _paths_parser(parser): return parser +def _plugins_parsing(parser, plugins): + plugins_group = parser.add_argument_group( + "plugins", description="Let's Encrypt client supports an extensible " + "plugins architecture. See '%(prog)s plugins' for a list of all " + "available plugins and their names. You can force a particular " + "plugin by setting options provided below. Futher down this help " + "message you will find plugin-specific options (prefixed by " + "--{plugin_name}.") + plugins_group.add_argument( + "-a", "--authenticator", help="Authenticator plugin name.") + plugins_group.add_argument( + "-i", "--installer", help="Installer plugin name.") + plugins_group.add_argument( + "--configurator", help="Name of the plugin that is both " + "an authenticator and an installer. Should not be used together " + "with --authenticator or --installer.") + + # things should not be reorder past/pre this comment: + # plugins_group should be displayed in --help before plugin + # specific groups (so that plugins_group.description makes sense) + + for name, plugin_ep in plugins.iteritems(): + plugin_ep.plugin_cls.inject_parser_options( + parser.add_argument_group( + "plugins: {0}".format(name), + description=plugin_ep.description), name) + + def main(args=sys.argv[1:]): """Command line argument parsing and main script execution.""" # note: arg parser internally handles --help (and exits afterwards) From 71aa1a5348051487844e69d5ca3a277f8a213817 Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Thu, 28 May 2015 20:52:59 +0000 Subject: [PATCH 04/16] Fix merge problems and pylint --- letsencrypt/cli.py | 4 ++-- letsencrypt/client.py | 5 +---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/letsencrypt/cli.py b/letsencrypt/cli.py index f2b4f9d4f..bbff3411b 100644 --- a/letsencrypt/cli.py +++ b/letsencrypt/cli.py @@ -288,8 +288,8 @@ def _create_subparsers(parser): # the order of add_subparser() calls is important: it defines the # order in which subparser names will be displayed in --help - parser_run = add_subparser("run", run) - parser_auth = add_subparser("auth", auth) + add_subparser("run", run) + add_subparser("auth", auth) parser_install = add_subparser("install", install) parser_plugins = add_subparser("plugins", plugins_cmd) parser_revoke = add_subparser("revoke", revoke) diff --git a/letsencrypt/client.py b/letsencrypt/client.py index 34a5aefdd..bd467b13d 100644 --- a/letsencrypt/client.py +++ b/letsencrypt/client.py @@ -100,7 +100,7 @@ class Client(object): self.account.save() - def obtain_certificate(self, domains, cert_path, chain_path, csr=None): + def obtain_certificate(self, domains, csr=None): """Obtains a certificate from the ACME server. :meth:`.register` must be called before :meth:`.obtain_certificate` @@ -109,9 +109,6 @@ class Client(object): :param set domains: domains to get a certificate - :param str cert_path: Candidate path to a certificate. - :param str chain_path: Candidate path to a certificate chain. - :param csr: CSR must contain requested domains, the key used to generate this CSR can be different than self.authkey :type csr: :class:`CSR` From 3fefd280802e8c88d949314b1e246ad31d663bed Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Mon, 1 Jun 2015 23:25:57 +0000 Subject: [PATCH 05/16] Only configure --config-dir/--work-dir (rest dynamic). --- letsencrypt/cli.py | 10 ---- letsencrypt/configuration.py | 63 ++++++++++++++++--------- letsencrypt/constants.py | 55 +++++++++++---------- letsencrypt/interfaces.py | 22 ++++----- letsencrypt/tests/configuration_test.py | 26 ++++++---- letsencrypt_apache/tests/util.py | 3 -- letsencrypt_nginx/tests/util.py | 4 +- 7 files changed, 99 insertions(+), 84 deletions(-) diff --git a/letsencrypt/cli.py b/letsencrypt/cli.py index 03367e2cb..45fe271bb 100644 --- a/letsencrypt/cli.py +++ b/letsencrypt/cli.py @@ -332,16 +332,6 @@ def _paths_parser(parser): help=config_help("config_dir")) add("--work-dir", default=flag_default("work_dir"), help=config_help("work_dir")) - add("--backup-dir", default=flag_default("backup_dir"), - help=config_help("backup_dir")) - - add("--key-dir", default=flag_default("key_dir"), - help=config_help("key_dir")) - add("--cert-dir", default=flag_default("certs_dir"), - help=config_help("cert_dir")) - - add("--renewer-config-file", default=flag_default("renewer_config_file"), - help=config_help("renewer_config_file")) return parser diff --git a/letsencrypt/configuration.py b/letsencrypt/configuration.py index 6a808a6a9..00b45040a 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_dir(self): # pylint: disable=missing-docstring + return os.path.join(self.namespace.config_dir, constants.CERT_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 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 5b7c3af29..6e2355252 100644 --- a/letsencrypt/constants.py +++ b/letsencrypt/constants.py @@ -1,6 +1,5 @@ """Let's Encrypt constants.""" import logging -import os from acme import challenges @@ -8,24 +7,14 @@ from acme import challenges SETUPTOOLS_PLUGINS_ENTRY_POINT = "letsencrypt.plugins" """Setuptools entry point group name for plugins.""" - -_CLI_DEFAULT_CONFIG_DIR = "/etc/letsencrypt" -_CLI_DEFAULT_WORK_DIR = "/var/lib/letsencrypt" -_CLI_DEFAULT_CERT_DIR = os.path.join(_CLI_DEFAULT_CONFIG_DIR, "certs") CLI_DEFAULTS = dict( config_files=["/etc/letsencrypt/cli.ini"], verbose_count=-(logging.WARNING / 10), server="https://www.letsencrypt-demo.org/acme/new-reg", rsa_key_size=2048, rollback_checkpoints=0, - config_dir=_CLI_DEFAULT_CONFIG_DIR, - work_dir=_CLI_DEFAULT_CONFIG_DIR, - backup_dir=os.path.join(_CLI_DEFAULT_WORK_DIR, "backups"), - key_dir=os.path.join(_CLI_DEFAULT_CONFIG_DIR, "keys"), - certs_dir=_CLI_DEFAULT_CERT_DIR, - cert_path=os.path.join(_CLI_DEFAULT_CERT_DIR, "cert-letsencrypt.pem"), - chain_path=os.path.join(_CLI_DEFAULT_CERT_DIR, "chain-letsencrypt.pem"), - renewer_config_file=os.path.join(_CLI_DEFAULT_CONFIG_DIR, "renewer.conf"), + config_dir="/etc/letsencrypt", + work_dir="/var/lib/letsencrypt", test_mode=False, ) """Defaults for CLI flags and `.IConfig` attributes.""" @@ -64,26 +53,40 @@ 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" +"""Directory (relative to `IConfig.config_dir`) where CSRs are saved.""" + +IN_PROGRESS_DIR = "IN_PROGRESS" +"""Directory used before a permanent checkpoint is finalized (relative to +`IConfig.work_dir`).""" + +KEYS_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`).""" + NETSTAT = "/bin/netstat" """Location of netstat binary for checking whether a listener is already diff --git a/letsencrypt/interfaces.py b/letsencrypt/interfaces.py index 365b9c182..421c10402 100644 --- a/letsencrypt/interfaces.py +++ b/letsencrypt/interfaces.py @@ -156,23 +156,23 @@ 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("Certificates and CSRs storage.") + 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 and CSRs storage.") + temp_checkpoint_dir = zope.interface.Attribute( + "Temporary checkpoint directory.") renewer_config_file = zope.interface.Attribute( "Location of renewal configuration file.") diff --git a/letsencrypt/tests/configuration_test.py b/letsencrypt/tests/configuration_test.py index d25368feb..345e3abbc 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_DIR = 'certs' + constants.CERT_KEY_BACKUP_DIR = 'c/' + 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/tests/util.py b/letsencrypt_apache/tests/util.py index e637b0890..a5e700682 100644 --- a/letsencrypt_apache/tests/util.py +++ b/letsencrypt_apache/tests/util.py @@ -76,10 +76,7 @@ def get_apache_configurator( apache_server_root=config_path, apache_mod_ssl_conf=ssl_options, 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"), - in_progress_dir=os.path.join(backups, "IN_PROGRESS"), work_dir=work_dir), name="apache", version=version) diff --git a/letsencrypt_nginx/tests/util.py b/letsencrypt_nginx/tests/util.py index 45d1fa184..fd1418aa3 100644 --- a/letsencrypt_nginx/tests/util.py +++ b/letsencrypt_nginx/tests/util.py @@ -46,9 +46,7 @@ def get_nginx_configurator( config = configurator.NginxConfigurator( config=mock.MagicMock( nginx_server_root=config_path, nginx_mod_ssl_conf=ssl_options, - backup_dir=backups, config_dir=config_dir, work_dir=work_dir, - temp_checkpoint_dir=os.path.join(work_dir, "temp_checkpoints"), - in_progress_dir=os.path.join(backups, "IN_PROGRESS")), + config_dir=config_dir, work_dir=work_dir), name="nginx", version=version) config.prepare() From 8fe65843361b9cd025c36632c4bb9c630448dd2c Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Tue, 2 Jun 2015 00:14:10 +0000 Subject: [PATCH 06/16] Don't allow user supplied mod_ssl conf destination (fixes #451). --- letsencrypt_apache/configurator.py | 12 +++++++----- letsencrypt_apache/constants.py | 5 +++-- letsencrypt_apache/tests/util.py | 1 - letsencrypt_nginx/configurator.py | 13 +++++++------ letsencrypt_nginx/constants.py | 8 +++++--- letsencrypt_nginx/tests/util.py | 5 ++--- 6 files changed, 24 insertions(+), 20 deletions(-) diff --git a/letsencrypt_apache/configurator.py b/letsencrypt_apache/configurator.py index b85774494..965e9cf73 100644 --- a/letsencrypt_apache/configurator.py +++ b/letsencrypt_apache/configurator.py @@ -87,8 +87,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.") @@ -126,10 +124,14 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): self.vhosts = None self._enhance_func = {"redirect": self._enable_redirect} + @property + def mod_ssl_conf(self): + 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") @@ -147,7 +149,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. @@ -1171,4 +1173,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 865741823..c78df7808 100644 --- a/letsencrypt_apache/constants.py +++ b/letsencrypt_apache/constants.py @@ -4,7 +4,6 @@ import pkg_resources CLI_DEFAULTS = dict( server_root="/etc/apache2", - mod_ssl_conf="/etc/letsencrypt/options-ssl.conf", ctl="apache2ctl", enmod="a2enmod", init_script="/etc/init.d/apache2", @@ -12,8 +11,10 @@ CLI_DEFAULTS = dict( ) """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.conf") """Path to the Apache mod_ssl config file found in the Let's Encrypt distribution.""" diff --git a/letsencrypt_apache/tests/util.py b/letsencrypt_apache/tests/util.py index a5e700682..0b0367505 100644 --- a/letsencrypt_apache/tests/util.py +++ b/letsencrypt_apache/tests/util.py @@ -74,7 +74,6 @@ def get_apache_configurator( config = configurator.ApacheConfigurator( config=mock.MagicMock( apache_server_root=config_path, - apache_mod_ssl_conf=ssl_options, apache_le_vhost_ext=constants.CLI_DEFAULTS["le_vhost_ext"], config_dir=config_dir, work_dir=work_dir), diff --git a/letsencrypt_nginx/configurator.py b/letsencrypt_nginx/configurator.py index f7b53f3fa..521a4facf 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,21 @@ class NginxConfigurator(common.Plugin): self.reverter = reverter.Reverter(self.config) self.reverter.recovery_routine() + @property + def mod_ssl_conf(self): + 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): @@ -592,4 +593,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 6c15b1664..055a35403 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.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/util.py b/letsencrypt_nginx/tests/util.py index fd1418aa3..caa9de14e 100644 --- a/letsencrypt_nginx/tests/util.py +++ b/letsencrypt_nginx/tests/util.py @@ -44,9 +44,8 @@ def get_nginx_configurator( backups = os.path.join(work_dir, "backups") config = configurator.NginxConfigurator( - config=mock.MagicMock( - nginx_server_root=config_path, nginx_mod_ssl_conf=ssl_options, - config_dir=config_dir, work_dir=work_dir), + config=mock.MagicMock(nginx_server_root=config_path, + config_dir=config_dir, work_dir=work_dir), name="nginx", version=version) config.prepare() From c440b0354da8afe4a636e5c2f51137d2e7fcc6dc Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Tue, 2 Jun 2015 00:16:24 +0000 Subject: [PATCH 07/16] Fix typo --- letsencrypt/constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/letsencrypt/constants.py b/letsencrypt/constants.py index 6e2355252..a360e6f3d 100644 --- a/letsencrypt/constants.py +++ b/letsencrypt/constants.py @@ -73,7 +73,7 @@ IN_PROGRESS_DIR = "IN_PROGRESS" """Directory used before a permanent checkpoint is finalized (relative to `IConfig.work_dir`).""" -KEYS_DIR = "keys" +KEY_DIR = "keys" """Directory (relative to `IConfig.config_dir`) where keys are saved.""" TEMP_CHECKPOINT_DIR = "temp_checkpoint" From 8fed612feff5ceb42373f5224d5c6076ce02a537 Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Tue, 2 Jun 2015 00:40:33 +0000 Subject: [PATCH 08/16] Fix apache/nginx tests. --- letsencrypt_apache/tests/util.py | 10 +++++++--- letsencrypt_nginx/tests/util.py | 14 +++++++++++--- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/letsencrypt_apache/tests/util.py b/letsencrypt_apache/tests/util.py index 0b0367505..be4ca823e 100644 --- a/letsencrypt_apache/tests/util.py +++ b/letsencrypt_apache/tests/util.py @@ -54,10 +54,11 @@ def dir_setup(test_dir="debian_apache_2_4/two_vhost_80", def setup_ssl_options( - config_dir, mod_ssl_conf=constants.MOD_SSL_CONF): + config_dir, src=constants.MOD_SSL_CONF_SRC, + dest=constants.MOD_SSL_CONF_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 @@ -75,7 +76,10 @@ def get_apache_configurator( config=mock.MagicMock( apache_server_root=config_path, 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"), + in_progress_dir=os.path.join(backups, "IN_PROGRESS"), work_dir=work_dir), name="apache", version=version) diff --git a/letsencrypt_nginx/tests/util.py b/letsencrypt_nginx/tests/util.py index caa9de14e..ea1ef0a6c 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 = apache_util.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") @@ -44,8 +45,15 @@ def get_nginx_configurator( backups = os.path.join(work_dir, "backups") config = configurator.NginxConfigurator( - config=mock.MagicMock(nginx_server_root=config_path, - config_dir=config_dir, work_dir=work_dir), + config=mock.MagicMock( + 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"), + ), name="nginx", version=version) config.prepare() From cd7a5ec24a0dcde681d683c6ecd6eea0424c5389 Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Tue, 2 Jun 2015 01:01:29 +0000 Subject: [PATCH 09/16] Fix pylint --- letsencrypt_apache/tests/configurator_test.py | 3 +-- letsencrypt_apache/tests/dvsni_test.py | 3 +-- letsencrypt_apache/tests/util.py | 2 +- letsencrypt_nginx/tests/configurator_test.py | 3 +-- letsencrypt_nginx/tests/dvsni_test.py | 3 +-- letsencrypt_nginx/tests/util.py | 2 +- 6 files changed, 6 insertions(+), 10 deletions(-) diff --git a/letsencrypt_apache/tests/configurator_test.py b/letsencrypt_apache/tests/configurator_test.py index 11b88f9e5..e732e1bce 100644 --- a/letsencrypt_apache/tests/configurator_test.py +++ b/letsencrypt_apache/tests/configurator_test.py @@ -31,8 +31,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 321dce42c..088ac9557 100644 --- a/letsencrypt_apache/tests/dvsni_test.py +++ b/letsencrypt_apache/tests/dvsni_test.py @@ -26,8 +26,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 be4ca823e..b477f0584 100644 --- a/letsencrypt_apache/tests/util.py +++ b/letsencrypt_apache/tests/util.py @@ -63,7 +63,7 @@ def setup_ssl_options( 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") diff --git a/letsencrypt_nginx/tests/configurator_test.py b/letsencrypt_nginx/tests/configurator_test.py index 82b80b9d2..a5a1c416b 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 1ea7793cc..1a49da944 100644 --- a/letsencrypt_nginx/tests/dvsni_test.py +++ b/letsencrypt_nginx/tests/dvsni_test.py @@ -23,8 +23,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) rsa256_file = pkg_resources.resource_filename( "acme.jose", "testdata/rsa256_key.pem") diff --git a/letsencrypt_nginx/tests/util.py b/letsencrypt_nginx/tests/util.py index ea1ef0a6c..f0aa56c28 100644 --- a/letsencrypt_nginx/tests/util.py +++ b/letsencrypt_nginx/tests/util.py @@ -39,7 +39,7 @@ 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") From 9ecfecbc7ab9434ae719751e23837ebae5bea658 Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Tue, 2 Jun 2015 09:19:51 +0000 Subject: [PATCH 10/16] Add missing docstring. --- letsencrypt_apache/configurator.py | 1 + letsencrypt_nginx/configurator.py | 1 + 2 files changed, 2 insertions(+) diff --git a/letsencrypt_apache/configurator.py b/letsencrypt_apache/configurator.py index 965e9cf73..078e61564 100644 --- a/letsencrypt_apache/configurator.py +++ b/letsencrypt_apache/configurator.py @@ -126,6 +126,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): @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): diff --git a/letsencrypt_nginx/configurator.py b/letsencrypt_nginx/configurator.py index 521a4facf..87beb3c8f 100644 --- a/letsencrypt_nginx/configurator.py +++ b/letsencrypt_nginx/configurator.py @@ -91,6 +91,7 @@ class NginxConfigurator(common.Plugin): @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 From 9a7ade7cba5cabd48e052322e82a6eb9ac6c53f0 Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Tue, 2 Jun 2015 17:42:23 +0000 Subject: [PATCH 11/16] Rename cert_dir to csr_dir. --- letsencrypt/client.py | 2 +- letsencrypt/configuration.py | 10 +++++----- letsencrypt/constants.py | 2 +- letsencrypt/crypto_util.py | 8 ++++---- letsencrypt/interfaces.py | 2 +- letsencrypt/tests/configuration_test.py | 4 ++-- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/letsencrypt/client.py b/letsencrypt/client.py index 02159f5d2..74ea529d2 100644 --- a/letsencrypt/client.py +++ b/letsencrypt/client.py @@ -134,7 +134,7 @@ class Client(object): cert_key = crypto_util.init_save_key( self.config.rsa_key_size, self.config.key_dir) csr = crypto_util.init_save_csr( - cert_key, domains, self.config.cert_dir) + cert_key, domains, self.config.csr_dir) # Retrieve certificate certr = self.network.request_issuance( diff --git a/letsencrypt/configuration.py b/letsencrypt/configuration.py index 00b45040a..670db0e76 100644 --- a/letsencrypt/configuration.py +++ b/letsencrypt/configuration.py @@ -19,7 +19,7 @@ class NamespaceConfig(object): - `accounts_dir` - `account_keys_dir` - - `cert_dir` + - `csr_dir` - `cert_key_backup` - `in_progress_dir` - `key_dir` @@ -59,15 +59,15 @@ class NamespaceConfig(object): def backup_dir(self): # pylint: disable=missing-docstring return os.path.join(self.namespace.work_dir, constants.BACKUP_DIR) - @property - def cert_dir(self): # pylint: disable=missing-docstring - return os.path.join(self.namespace.config_dir, constants.CERT_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 csr_dir(self): # pylint: disable=missing-docstring + return os.path.join(self.namespace.config_dir, constants.CSR_DIR) + @property def in_progress_dir(self): # pylint: disable=missing-docstring return os.path.join(self.namespace.work_dir, constants.IN_PROGRESS_DIR) diff --git a/letsencrypt/constants.py b/letsencrypt/constants.py index 9d04fb4c2..c407f8825 100644 --- a/letsencrypt/constants.py +++ b/letsencrypt/constants.py @@ -67,7 +67,7 @@ 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" +CSR_DIR = "csrs" """Directory (relative to `IConfig.config_dir`) where CSRs are saved.""" IN_PROGRESS_DIR = "IN_PROGRESS" diff --git a/letsencrypt/crypto_util.py b/letsencrypt/crypto_util.py index 1eb565289..9172fda46 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 7e9133ba3..17905149a 100644 --- a/letsencrypt/interfaces.py +++ b/letsencrypt/interfaces.py @@ -162,7 +162,7 @@ class IConfig(zope.interface.Interface): 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("Certificates and CSRs storage.") + csr_dir = zope.interface.Attribute("CSRs storage.") cert_key_backup = zope.interface.Attribute( "Directory where all certificates and keys are stored. " "Used for easy revocation.") diff --git a/letsencrypt/tests/configuration_test.py b/letsencrypt/tests/configuration_test.py index 345e3abbc..38fea140a 100644 --- a/letsencrypt/tests/configuration_test.py +++ b/letsencrypt/tests/configuration_test.py @@ -33,8 +33,8 @@ class NamespaceConfigTest(unittest.TestCase): constants.ACCOUNTS_DIR = 'acc' constants.ACCOUNT_KEYS_DIR = 'keys' constants.BACKUP_DIR = 'backups' - constants.CERT_DIR = 'certs' constants.CERT_KEY_BACKUP_DIR = 'c/' + constants.CSR_DIR = 'csrs' constants.IN_PROGRESS_DIR = '../p' constants.KEY_DIR = 'keys' constants.REC_TOKEN_DIR = '/r' @@ -47,7 +47,7 @@ class NamespaceConfigTest(unittest.TestCase): 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.csr_dir, '/tmp/config/csrs') 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') From 278bd8deb2d33328b7606644da662259132dfde2 Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Tue, 23 Jun 2015 07:34:17 +0000 Subject: [PATCH 12/16] Rename IConfig.csr_dir back to IConfig.cert_dir. This will be used in #504. --- letsencrypt/client.py | 2 +- letsencrypt/configuration.py | 6 +++--- letsencrypt/constants.py | 4 ++-- letsencrypt/interfaces.py | 4 +++- letsencrypt/tests/configuration_test.py | 4 ++-- 5 files changed, 11 insertions(+), 9 deletions(-) diff --git a/letsencrypt/client.py b/letsencrypt/client.py index e1e30b9be..30bf41975 100644 --- a/letsencrypt/client.py +++ b/letsencrypt/client.py @@ -157,7 +157,7 @@ class Client(object): cert_key = crypto_util.init_save_key( self.config.rsa_key_size, self.config.key_dir) csr = crypto_util.init_save_csr( - cert_key, domains, self.config.csr_dir) + cert_key, domains, self.config.cert_dir) # Retrieve certificate certr = self.network.request_issuance( diff --git a/letsencrypt/configuration.py b/letsencrypt/configuration.py index 670db0e76..d6b29bd73 100644 --- a/letsencrypt/configuration.py +++ b/letsencrypt/configuration.py @@ -19,7 +19,7 @@ class NamespaceConfig(object): - `accounts_dir` - `account_keys_dir` - - `csr_dir` + - `cert_dir` - `cert_key_backup` - `in_progress_dir` - `key_dir` @@ -65,8 +65,8 @@ class NamespaceConfig(object): constants.CERT_KEY_BACKUP_DIR, self.server_path) @property - def csr_dir(self): # pylint: disable=missing-docstring - return os.path.join(self.namespace.config_dir, constants.CSR_DIR) + 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 diff --git a/letsencrypt/constants.py b/letsencrypt/constants.py index 152bc224e..5433299fc 100644 --- a/letsencrypt/constants.py +++ b/letsencrypt/constants.py @@ -67,8 +67,8 @@ CERT_KEY_BACKUP_DIR = "keys-certs" """Directory where all certificates and keys are stored (relative to `IConfig.work_dir`). Used for easy revocation.""" -CSR_DIR = "csrs" -"""Directory (relative to `IConfig.config_dir`) where CSRs are saved.""" +CERT_DIR = "certs" +"""See `.IConfig.cert_dir`.""" IN_PROGRESS_DIR = "IN_PROGRESS" """Directory used before a permanent checkpoint is finalized (relative to diff --git a/letsencrypt/interfaces.py b/letsencrypt/interfaces.py index 78ad75f65..d10e29bcd 100644 --- a/letsencrypt/interfaces.py +++ b/letsencrypt/interfaces.py @@ -161,7 +161,9 @@ class IConfig(zope.interface.Interface): account_keys_dir = zope.interface.Attribute( "Directory where all account keys are stored.") backup_dir = zope.interface.Attribute("Configuration backups directory.") - csr_dir = zope.interface.Attribute("CSRs storage.") + 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.") diff --git a/letsencrypt/tests/configuration_test.py b/letsencrypt/tests/configuration_test.py index 38fea140a..d5e9296dd 100644 --- a/letsencrypt/tests/configuration_test.py +++ b/letsencrypt/tests/configuration_test.py @@ -34,7 +34,7 @@ class NamespaceConfigTest(unittest.TestCase): constants.ACCOUNT_KEYS_DIR = 'keys' constants.BACKUP_DIR = 'backups' constants.CERT_KEY_BACKUP_DIR = 'c/' - constants.CSR_DIR = 'csrs' + constants.CERT_DIR = 'certs' constants.IN_PROGRESS_DIR = '../p' constants.KEY_DIR = 'keys' constants.REC_TOKEN_DIR = '/r' @@ -47,7 +47,7 @@ class NamespaceConfigTest(unittest.TestCase): 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.csr_dir, '/tmp/config/csrs') + 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') From f1e747ac1ab7db361702f07b408aa033454e0c27 Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Tue, 23 Jun 2015 07:15:44 +0000 Subject: [PATCH 13/16] Revert CLI changes, blocked by #485. --- letsencrypt/cli.py | 141 +++++++++++++++++--------------------- letsencrypt/constants.py | 8 +++ letsencrypt/interfaces.py | 5 ++ 3 files changed, 74 insertions(+), 80 deletions(-) diff --git a/letsencrypt/cli.py b/letsencrypt/cli.py index 0217598b1..3bdf2bfc6 100644 --- a/letsencrypt/cli.py +++ b/letsencrypt/cli.py @@ -146,21 +146,21 @@ def install(args, config, plugins): return "Installer could not be determined" acme, doms = _common_run( args, config, acc, authenticator=None, installer=installer) - assert args.cert_path is not None # required=True in the subparser + assert args.cert_path is not None acme.deploy_certificate(doms, acc.key.file, args.cert_path, args.chain_path) acme.enhance_config(doms, args.redirect) def revoke(args, unused_config, unused_plugins): """Revoke.""" - if args.cert_path is None and args.key_path is None: - return "At least one of --cert-path or --key-path is required" + if args.rev_cert is None and args.rev_key is None: + return "At least one of --certificate or --key is required" # This depends on the renewal config and cannot be completed yet. zope.component.getUtility(interfaces.IDisplay).notification( "Revocation is not available with the new Boulder server yet.") #client.revoke(args.installer, config, plugins, args.no_confirm, - # args.cert_path, args.key_path) + # args.rev_cert, args.rev_key) def rollback(args, config, plugins): @@ -268,6 +268,34 @@ def create_parser(plugins): "--dvsni-port", type=int, help=config_help("dvsni_port"), default=flag_default("dvsni_port")) + subparsers = 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__) + subparser.set_defaults(func=func) + return subparser + + add_subparser("run", run) + add_subparser("auth", auth) + add_subparser("install", install) + parser_revoke = add_subparser("revoke", revoke) + parser_rollback = add_subparser("rollback", rollback) + add_subparser("config_changes", config_changes) + + parser_plugins = add_subparser("plugins", plugins_cmd) + parser_plugins.add_argument("--init", action="store_true") + parser_plugins.add_argument("--prepare", action="store_true") + parser_plugins.add_argument( + "--authenticators", action="append_const", dest="ifaces", + const=interfaces.IAuthenticator) + parser_plugins.add_argument( + "--installers", action="append_const", dest="ifaces", + const=interfaces.IInstaller) + + parser.add_argument("--configurator") + parser.add_argument("-a", "--authenticator") + parser.add_argument("-i", "--installer") + # positional arg shadows --domains, instead of appending, and # --domains is useful, because it can be stored in config #for subparser in parser_run, parser_auth, parser_install: @@ -286,56 +314,11 @@ def create_parser(plugins): help="Automatically redirect all HTTP traffic to HTTPS for the newly " "authenticated vhost.") - _paths_parser(parser.add_argument_group("paths")) - # _plugins_parsing should be the last thing to act upon the main - # parser (--help should display plugin-specific options last) - _plugins_parsing(parser, plugins) - - _create_subparsers(parser) - - return parser - - -def _create_subparsers(parser): - subparsers = 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__) - subparser.set_defaults(func=func) - return subparser - - # the order of add_subparser() calls is important: it defines the - # order in which subparser names will be displayed in --help - add_subparser("run", run) - add_subparser("auth", auth) - parser_install = add_subparser("install", install) - parser_plugins = add_subparser("plugins", plugins_cmd) - parser_revoke = add_subparser("revoke", revoke) - parser_rollback = add_subparser("rollback", rollback) - add_subparser("config_changes", config_changes) - - parser_install.add_argument( - "--cert-path", required=True, help="Path to a certificate that " - "is going to be installed.") - parser_install.add_argument( - "--chain-path", help="Accompanying path to a certificate chain.") - - parser_plugins.add_argument( - "--init", action="store_true", help="Initialize plugins.") - parser_plugins.add_argument("--prepare", action="store_true", - help="Initialize and prepare plugins.") - parser_plugins.add_argument( - "--authenticators", action="append_const", dest="ifaces", - const=interfaces.IAuthenticator, - help="Limit to authenticator plugins only.") - parser_plugins.add_argument( - "--installers", action="append_const", dest="ifaces", - const=interfaces.IInstaller, help="Limit to installer plugins only.") - parser_revoke.add_argument( - "--cert-path", type=read_file, help="Revoke a specific certificate.") + "--certificate", dest="rev_cert", type=read_file, metavar="CERT_PATH", + help="Revoke a specific certificate.") parser_revoke.add_argument( - "--key-path", type=read_file, + "--key", dest="rev_key", type=read_file, metavar="KEY_PATH", help="Revoke all certs generated by the provided authorized key.") parser_rollback.add_argument( @@ -343,6 +326,16 @@ def _create_subparsers(parser): default=flag_default("rollback_checkpoints"), help="Revert configuration N number of checkpoints.") + _paths_parser(parser.add_argument_group("paths")) + + # TODO: plugin_parser should be called for every detected plugin + for name, plugin_ep in plugins.iteritems(): + plugin_ep.plugin_cls.inject_parser_options( + parser.add_argument_group( + name, description=plugin_ep.description), name) + + return parser + def _paths_parser(parser): add = parser.add_argument @@ -350,38 +343,26 @@ def _paths_parser(parser): help=config_help("config_dir")) add("--work-dir", default=flag_default("work_dir"), help=config_help("work_dir")) + add("--backup-dir", default=flag_default("backup_dir"), + help=config_help("backup_dir")) + add("--key-dir", default=flag_default("key_dir"), + help=config_help("key_dir")) + add("--cert-dir", default=flag_default("certs_dir"), + help=config_help("cert_dir")) + + add("--le-vhost-ext", default="-le-ssl.conf", + help=config_help("le_vhost_ext")) + add("--cert-path", default=flag_default("cert_path"), + help=config_help("cert_path")) + add("--chain-path", default=flag_default("chain_path"), + help=config_help("chain_path")) + + add("--renewer-config-file", default=flag_default("renewer_config_file"), + help=config_help("renewer_config_file")) return parser -def _plugins_parsing(parser, plugins): - plugins_group = parser.add_argument_group( - "plugins", description="Let's Encrypt client supports an extensible " - "plugins architecture. See '%(prog)s plugins' for a list of all " - "available plugins and their names. You can force a particular " - "plugin by setting options provided below. Futher down this help " - "message you will find plugin-specific options (prefixed by " - "--{plugin_name}.") - plugins_group.add_argument( - "-a", "--authenticator", help="Authenticator plugin name.") - plugins_group.add_argument( - "-i", "--installer", help="Installer plugin name.") - plugins_group.add_argument( - "--configurator", help="Name of the plugin that is both " - "an authenticator and an installer. Should not be used together " - "with --authenticator or --installer.") - - # things should not be reorder past/pre this comment: - # plugins_group should be displayed in --help before plugin - # specific groups (so that plugins_group.description makes sense) - - for name, plugin_ep in plugins.iteritems(): - plugin_ep.plugin_cls.inject_parser_options( - parser.add_argument_group( - "plugins: {0}".format(name), - description=plugin_ep.description), name) - - 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/constants.py b/letsencrypt/constants.py index 5433299fc..df41fbe5b 100644 --- a/letsencrypt/constants.py +++ b/letsencrypt/constants.py @@ -17,6 +17,14 @@ CLI_DEFAULTS = dict( work_dir="/var/lib/letsencrypt", 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.""" diff --git a/letsencrypt/interfaces.py b/letsencrypt/interfaces.py index d10e29bcd..a93716d7d 100644 --- a/letsencrypt/interfaces.py +++ b/letsencrypt/interfaces.py @@ -188,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. From a1f025980a51e6869ab2cef15bbf6bd7788e0ff0 Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Tue, 23 Jun 2015 16:38:39 -0600 Subject: [PATCH 14/16] minor update for raspbian --- bootstrap/_deb_common.sh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bootstrap/_deb_common.sh b/bootstrap/_deb_common.sh index 653daca53..6bb8479c8 100755 --- a/bootstrap/_deb_common.sh +++ b/bootstrap/_deb_common.sh @@ -9,7 +9,11 @@ # - 6.0.10 "squeeze" (x64) # - 7.8 "wheezy" (x64) # - 8.0 "jessie" (x64) +# - Raspbian: +# - 7.8 (armhf) +apt-get update +apt-get install -y lsb-release # virtualenv binary can be found in different packages depending on # distro version (#346) @@ -43,7 +47,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 \ From fba2de706e2591dfcb79e9963707082f968ad2a2 Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Wed, 24 Jun 2015 13:29:26 -0600 Subject: [PATCH 15/16] move lsb install into newer with --no-install-recommends --- bootstrap/_deb_common.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/bootstrap/_deb_common.sh b/bootstrap/_deb_common.sh index 6bb8479c8..a4dff6b1c 100755 --- a/bootstrap/_deb_common.sh +++ b/bootstrap/_deb_common.sh @@ -12,12 +12,11 @@ # - Raspbian: # - 7.8 (armhf) -apt-get update -apt-get install -y lsb-release # 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 @@ -33,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 From 06d7e51f22c182abb0a453ba422c185b92e9c4f8 Mon Sep 17 00:00:00 2001 From: James Kasten Date: Wed, 24 Jun 2015 14:38:30 -0700 Subject: [PATCH 16/16] formatting and documentation --- .pylintrc | 7 ++- letsencrypt/cli.py | 104 ++++++++++++++++++++++++++++++--------------- 2 files changed, 72 insertions(+), 39 deletions(-) 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/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)