diff --git a/letsencrypt/cli.py b/letsencrypt/cli.py index 118eb6e39..311c212e8 100644 --- a/letsencrypt/cli.py +++ b/letsencrypt/cli.py @@ -193,6 +193,7 @@ def auth(args, config, plugins): domains, authenticator, installer, plugins): return "Certificate could not be obtained" + def install(args, config, plugins): """Install a previously obtained cert in a server.""" # XXX: Update for renewer/RenewableCert @@ -621,6 +622,16 @@ def main(cli_args=sys.argv[1:]): args = create_parser(plugins, cli_args).parse_args(cli_args) config = configuration.NamespaceConfig(args) + # Setup logging ASAP, otherwise "No handlers could be found for + # logger ..." TODO: this should be done before plugins discovery + for directory in config.config_dir, config.work_dir: + le_util.make_or_verify_dir( + directory, constants.CONFIG_DIRS_MODE, os.geteuid()) + # TODO: logs might contain sensitive data such as contents of the + # private key! #525 + le_util.make_or_verify_dir(args.logs_dir, 0o700, os.geteuid()) + _setup_logging(args) + # Displayer if args.text_mode: displayer = display_util.FileDisplay(sys.stdout) @@ -628,14 +639,6 @@ def main(cli_args=sys.argv[1:]): displayer = display_util.NcursesDisplay() zope.component.provideUtility(displayer) - for directory in config.config_dir, config.work_dir: - le_util.make_or_verify_dir( - directory, constants.CONFIG_DIRS_MODE, os.geteuid()) - # TODO: logs might contain sensitive data such as contents of the - # private key! #525 - le_util.make_or_verify_dir(args.logs_dir, 0o700, os.geteuid()) - - _setup_logging(args) # do not log `args`, as it contains sensitive data (e.g. revoke --key)! logger.debug("Arguments: %r", cli_args) logger.debug("Discovered plugins: %r", plugins) diff --git a/letsencrypt/interfaces.py b/letsencrypt/interfaces.py index ce12c4a56..3e0b8e686 100644 --- a/letsencrypt/interfaces.py +++ b/letsencrypt/interfaces.py @@ -83,6 +83,8 @@ class IPlugin(zope.interface.Interface): Should describe the steps taken and any relevant info to help the user decide which plugin to use. + :rtype str: + """ @@ -200,7 +202,11 @@ class IInstaller(IPlugin): """ def get_all_names(): - """Returns all names that may be authenticated.""" + """Returns all names that may be authenticated. + + :rtype: `list` of `str` + + """ def deploy_cert(domain, cert_path, key_path, chain_path=None): """Deploy certificate. diff --git a/letsencrypt/plugins/disco.py b/letsencrypt/plugins/disco.py index 8c6777d81..059913e3b 100644 --- a/letsencrypt/plugins/disco.py +++ b/letsencrypt/plugins/disco.py @@ -71,11 +71,11 @@ class PluginEntryPoint(object): for iface in ifaces: # zope.interface.providedBy(plugin) try: zope.interface.verify.verifyObject(iface, self.init()) - except zope.interface.exceptions.BrokenImplementation: + except zope.interface.exceptions.BrokenImplementation as error: if iface.implementedBy(self.plugin_cls): logger.debug( - "%s implements %s but object does " - "not verify", self.plugin_cls, iface.__name__) + "%s implements %s but object does not verify: %s", + self.plugin_cls, iface.__name__, error) return False return True diff --git a/letsencrypt/plugins/manual.py b/letsencrypt/plugins/manual.py index b16665581..23dff649e 100644 --- a/letsencrypt/plugins/manual.py +++ b/letsencrypt/plugins/manual.py @@ -11,19 +11,20 @@ from acme import challenges from acme import jose from letsencrypt import interfaces -from letsencrypt.plugins import common +from letsencrypt.plugins import null logger = logging.getLogger(__name__) -class ManualAuthenticator(common.Plugin): +class ManualAuthenticator(null.Installer): """Manual Authenticator. .. todo:: Support for `~.challenges.DVSNI`. """ - zope.interface.implements(interfaces.IAuthenticator) + zope.interface.implements( + interfaces.IAuthenticator, interfaces.IInstaller) zope.interface.classProvides(interfaces.IPluginFactory) description = "Manual Authenticator" diff --git a/letsencrypt/plugins/null.py b/letsencrypt/plugins/null.py new file mode 100644 index 000000000..bc9565e5a --- /dev/null +++ b/letsencrypt/plugins/null.py @@ -0,0 +1,57 @@ +"""Null plugin.""" +import logging + +import zope.component +import zope.interface + +from letsencrypt import interfaces +from letsencrypt.plugins import common + + +logger = logging.getLogger(__name__) + + +class Installer(common.Plugin): + """Null installer.""" + zope.interface.implements(interfaces.IInstaller) + zope.interface.classProvides(interfaces.IPluginFactory) + + description = "Null Installer" + + # pylint: disable=missing-docstring,no-self-use + + def prepare(self): + pass # pragma: no cover + + def more_info(self): + return "Installer that doesn't do anything (for testing)." + + def get_all_names(self): + return [] + + def deploy_cert(self, domain, cert_path, key_path, chain_path=None): + pass # pragma: no cover + + def enhance(self, domain, enhancement, options=None): + pass # pragma: no cover + + def supported_enhancements(self): + return [] + + def get_all_certs_keys(self): + return [] + + def save(self, title=None, temporary=False): + pass # pragma: no cover + + def rollback_checkpoints(self, rollback=1): + pass # pragma: no cover + + def view_config_changes(self): + pass # pragma: no cover + + def config_test(self): + pass # pragma: no cover + + def restart(self): + pass # pragma: no cover diff --git a/letsencrypt/plugins/null_test.py b/letsencrypt/plugins/null_test.py new file mode 100644 index 000000000..008bb0381 --- /dev/null +++ b/letsencrypt/plugins/null_test.py @@ -0,0 +1,22 @@ +"""Tests for letsencrypt.plugins.null.""" +import unittest + +import mock + + +class InstallerTest(unittest.TestCase): + """Tests for letsencrypt.plugins.null.Installer.""" + + def setUp(self): + from letsencrypt.plugins.null import Installer + self.installer = Installer(config=mock.MagicMock(), name="null") + + def test_it(self): + self.assertTrue(isinstance(self.installer.more_info(), str)) + self.assertEqual([], self.installer.get_all_names()) + self.assertEqual([], self.installer.supported_enhancements()) + self.assertEqual([], self.installer.get_all_certs_keys()) + + +if __name__ == "__main__": + unittest.main() # pragma: no cover diff --git a/setup.py b/setup.py index e2dd6f88e..520802b9f 100644 --- a/setup.py +++ b/setup.py @@ -181,6 +181,8 @@ setup( ], 'letsencrypt.plugins': [ 'manual = letsencrypt.plugins.manual:ManualAuthenticator', + # TODO: null should probably not be presented to the user + 'null = letsencrypt.plugins.null:Installer', 'standalone = letsencrypt.plugins.standalone.authenticator' ':StandaloneAuthenticator', diff --git a/tests/boulder-integration.sh b/tests/boulder-integration.sh index 32255039b..0b5113215 100755 --- a/tests/boulder-integration.sh +++ b/tests/boulder-integration.sh @@ -19,13 +19,16 @@ common() { --agree-eula \ --email "" \ --authenticator standalone \ + --installer null \ -vvvvvvv "$@" } -common --domains le.wtf auth +common --domains le1.wtf auth +common --domains le2.wtf run +common --domains le3.wtf install export CSR_PATH="${root}/csr.der" OPENSSL_CNF=examples/openssl.cnf -./examples/generate-csr.sh le.wtf +./examples/generate-csr.sh le4.wtf common auth --csr "$CSR_PATH" \ --cert-path "${root}/csr/cert.pem" \ --chain-path "${root}/csr/chain.pem" @@ -39,10 +42,10 @@ renew_before_expiry = 10 years deploy_before_expiry = 10 years EOF letsencrypt-renewer $store_flags -dir="$root/conf/archive/le.wtf" +dir="$root/conf/archive/le1.wtf" for x in cert chain fullchain privkey; do latest="$(ls -1t $dir/ | grep -e "^${x}" | head -n1)" - live="$(readlink -f "$root/conf/live/le.wtf/${x}.pem")" + live="$(readlink -f "$root/conf/live/le1.wtf/${x}.pem")" #[ "${dir}/${latest}" = "$live" ] # renewer fails this test done