mirror of
https://github.com/certbot/certbot.git
synced 2026-08-01 16:19:13 +02:00
Merge branch 'simplefs'
This commit is contained in:
@@ -0,0 +1,5 @@
|
|||||||
|
:mod:`letsencrypt.plugins.webroot`
|
||||||
|
----------------------------------
|
||||||
|
|
||||||
|
.. automodule:: letsencrypt.plugins.webroot
|
||||||
|
:members:
|
||||||
@@ -46,6 +46,10 @@ class Plugin(object):
|
|||||||
"""ArgumentParser options namespace (prefix of all options)."""
|
"""ArgumentParser options namespace (prefix of all options)."""
|
||||||
return option_namespace(self.name)
|
return option_namespace(self.name)
|
||||||
|
|
||||||
|
def option_name(self, name):
|
||||||
|
"""Option name (include plugin namespace)."""
|
||||||
|
return self.option_namespace + name
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def dest_namespace(self):
|
def dest_namespace(self):
|
||||||
"""ArgumentParser dest namespace (prefix of all destinations)."""
|
"""ArgumentParser dest namespace (prefix of all destinations)."""
|
||||||
|
|||||||
@@ -51,6 +51,9 @@ class PluginTest(unittest.TestCase):
|
|||||||
def test_option_namespace(self):
|
def test_option_namespace(self):
|
||||||
self.assertEqual("mock-", self.plugin.option_namespace)
|
self.assertEqual("mock-", self.plugin.option_namespace)
|
||||||
|
|
||||||
|
def test_option_name(self):
|
||||||
|
self.assertEqual("mock-foo_bar", self.plugin.option_name("foo_bar"))
|
||||||
|
|
||||||
def test_dest_namespace(self):
|
def test_dest_namespace(self):
|
||||||
self.assertEqual("mock_", self.plugin.dest_namespace)
|
self.assertEqual("mock_", self.plugin.dest_namespace)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
"""Webroot plugin."""
|
||||||
|
import errno
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
|
||||||
|
import zope.interface
|
||||||
|
|
||||||
|
from acme import challenges
|
||||||
|
|
||||||
|
from letsencrypt import errors
|
||||||
|
from letsencrypt import interfaces
|
||||||
|
from letsencrypt.plugins import common
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class Authenticator(common.Plugin):
|
||||||
|
"""Webroot Authenticator."""
|
||||||
|
zope.interface.implements(interfaces.IAuthenticator)
|
||||||
|
zope.interface.classProvides(interfaces.IPluginFactory)
|
||||||
|
|
||||||
|
description = "Webroot Authenticator"
|
||||||
|
|
||||||
|
MORE_INFO = """\
|
||||||
|
Authenticator plugin that performs SimpleHTTP challenge by saving
|
||||||
|
necessary validation resources to appropriate paths on the file
|
||||||
|
system. It expects that there is some other HTTP server configured
|
||||||
|
to serve all files under specified web root ({0})."""
|
||||||
|
|
||||||
|
def more_info(self): # pylint: disable=missing-docstring,no-self-use
|
||||||
|
return self.MORE_INFO.format(self.conf("path"))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def add_parser_arguments(cls, add):
|
||||||
|
add("path", help="public_html / webroot path")
|
||||||
|
|
||||||
|
def get_chall_pref(self, domain): # pragma: no cover
|
||||||
|
# pylint: disable=missing-docstring,no-self-use,unused-argument
|
||||||
|
return [challenges.SimpleHTTP]
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(Authenticator, self).__init__(*args, **kwargs)
|
||||||
|
self.full_root = None
|
||||||
|
|
||||||
|
def prepare(self): # pylint: disable=missing-docstring
|
||||||
|
path = self.conf("path")
|
||||||
|
if path is None:
|
||||||
|
raise errors.PluginError("--{0} must be set".format(
|
||||||
|
self.option_name("path")))
|
||||||
|
if not os.path.isdir(path):
|
||||||
|
raise errors.PluginError(
|
||||||
|
path + " does not exist or is not a directory")
|
||||||
|
self.full_root = os.path.join(
|
||||||
|
path, challenges.SimpleHTTPResponse.URI_ROOT_PATH)
|
||||||
|
|
||||||
|
logger.debug("Creating root challenges validation dir at %s",
|
||||||
|
self.full_root)
|
||||||
|
try:
|
||||||
|
os.makedirs(self.full_root)
|
||||||
|
except OSError as exception:
|
||||||
|
if exception.errno != errno.EEXIST:
|
||||||
|
raise errors.PluginError(
|
||||||
|
"Couldn't create root for SimpleHTTP "
|
||||||
|
"challenge responses: {0}", exception)
|
||||||
|
|
||||||
|
def perform(self, achalls): # pylint: disable=missing-docstring
|
||||||
|
assert self.full_root is not None
|
||||||
|
return [self._perform_single(achall) for achall in achalls]
|
||||||
|
|
||||||
|
def _path_for_achall(self, achall):
|
||||||
|
return os.path.join(self.full_root, achall.chall.encode("token"))
|
||||||
|
|
||||||
|
def _perform_single(self, achall):
|
||||||
|
response, validation = achall.gen_response_and_validation(
|
||||||
|
tls=(not self.config.no_simple_http_tls))
|
||||||
|
path = self._path_for_achall(achall)
|
||||||
|
logger.debug("Attempting to save validation to %s", path)
|
||||||
|
with open(path, "w") as validation_file:
|
||||||
|
validation_file.write(validation.json_dumps())
|
||||||
|
return response
|
||||||
|
|
||||||
|
def cleanup(self, achalls): # pylint: disable=missing-docstring
|
||||||
|
for achall in achalls:
|
||||||
|
path = self._path_for_achall(achall)
|
||||||
|
logger.debug("Removing %s", path)
|
||||||
|
os.remove(path)
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
"""Tests for letsencrypt.plugins.webroot."""
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import tempfile
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
import mock
|
||||||
|
|
||||||
|
from acme import jose
|
||||||
|
|
||||||
|
from letsencrypt import achallenges
|
||||||
|
from letsencrypt import errors
|
||||||
|
|
||||||
|
from letsencrypt.tests import acme_util
|
||||||
|
from letsencrypt.tests import test_util
|
||||||
|
|
||||||
|
|
||||||
|
KEY = jose.JWKRSA.load(test_util.load_vector("rsa512_key.pem"))
|
||||||
|
|
||||||
|
|
||||||
|
class AuthenticatorTest(unittest.TestCase):
|
||||||
|
"""Tests for letsencrypt.plugins.webroot.Authenticator."""
|
||||||
|
|
||||||
|
achall = achallenges.SimpleHTTP(
|
||||||
|
challb=acme_util.SIMPLE_HTTP_P, domain=None, account_key=KEY)
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
from letsencrypt.plugins.webroot import Authenticator
|
||||||
|
self.path = tempfile.mkdtemp()
|
||||||
|
self.validation_path = os.path.join(
|
||||||
|
self.path, ".well-known", "acme-challenge",
|
||||||
|
"ZXZhR3hmQURzNnBTUmIyTEF2OUlaZjE3RHQzanV4R0orUEN0OTJ3citvQQ")
|
||||||
|
self.config = mock.MagicMock(webroot_path=self.path)
|
||||||
|
self.auth = Authenticator(self.config, "webroot")
|
||||||
|
self.auth.prepare()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
shutil.rmtree(self.path)
|
||||||
|
|
||||||
|
def test_more_info(self):
|
||||||
|
more_info = self.auth.more_info()
|
||||||
|
self.assertTrue(isinstance(more_info, str))
|
||||||
|
self.assertTrue(self.path in more_info)
|
||||||
|
|
||||||
|
def test_add_parser_arguments(self):
|
||||||
|
add = mock.MagicMock()
|
||||||
|
self.auth.add_parser_arguments(add)
|
||||||
|
self.assertEqual(1, add.call_count)
|
||||||
|
|
||||||
|
def test_prepare_bad_root(self):
|
||||||
|
self.config.webroot_path = os.path.join(self.path, "null")
|
||||||
|
self.assertRaises(errors.PluginError, self.auth.prepare)
|
||||||
|
|
||||||
|
def test_prepare_missing_root(self):
|
||||||
|
self.config.webroot_path = None
|
||||||
|
self.assertRaises(errors.PluginError, self.auth.prepare)
|
||||||
|
|
||||||
|
def test_prepare_full_root_exists(self):
|
||||||
|
# prepare() has already been called once in setUp()
|
||||||
|
self.auth.prepare() # shouldn't raise any exceptions
|
||||||
|
|
||||||
|
def test_prepare_reraises_other_errors(self):
|
||||||
|
self.auth.full_path = os.path.join(self.path, "null")
|
||||||
|
os.chmod(self.path, 0o000)
|
||||||
|
self.assertRaises(errors.PluginError, self.auth.prepare)
|
||||||
|
os.chmod(self.path, 0o700)
|
||||||
|
|
||||||
|
def test_perform_cleanup(self):
|
||||||
|
responses = self.auth.perform([self.achall])
|
||||||
|
self.assertEqual(1, len(responses))
|
||||||
|
self.assertTrue(os.path.exists(self.validation_path))
|
||||||
|
with open(self.validation_path) as validation_f:
|
||||||
|
validation = jose.JWS.json_loads(validation_f.read())
|
||||||
|
self.assertTrue(responses[0].check_validation(
|
||||||
|
validation, self.achall.chall, KEY.public_key()))
|
||||||
|
|
||||||
|
self.auth.cleanup([self.achall])
|
||||||
|
self.assertFalse(os.path.exists(self.validation_path))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main() # pragma: no cover
|
||||||
@@ -132,6 +132,7 @@ setup(
|
|||||||
'manual = letsencrypt.plugins.manual:Authenticator',
|
'manual = letsencrypt.plugins.manual:Authenticator',
|
||||||
'null = letsencrypt.plugins.null:Installer',
|
'null = letsencrypt.plugins.null:Installer',
|
||||||
'standalone = letsencrypt.plugins.standalone:Authenticator',
|
'standalone = letsencrypt.plugins.standalone:Authenticator',
|
||||||
|
'webroot = letsencrypt.plugins.webroot:Authenticator',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user