From 5914b7a59ab5c3084f984672e3420d8105b7b529 Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Sat, 9 May 2015 19:19:34 +0000 Subject: [PATCH 1/5] --server with scheme:// --- letsencrypt/client/configuration.py | 12 ++++-------- letsencrypt/client/constants.py | 2 +- letsencrypt/client/tests/configuration_test.py | 13 +++++++------ 3 files changed, 12 insertions(+), 15 deletions(-) diff --git a/letsencrypt/client/configuration.py b/letsencrypt/client/configuration.py index 14c7b23cd..c091aaeed 100644 --- a/letsencrypt/client/configuration.py +++ b/letsencrypt/client/configuration.py @@ -1,5 +1,7 @@ """Let's Encrypt user-supplied configuration.""" import os +import urlparse + import zope.interface from letsencrypt.client import constants @@ -28,8 +30,6 @@ class NamespaceConfig(object): zope.interface.implements(interfaces.IConfig) def __init__(self, namespace): - assert not namespace.server.startswith('https://') - assert not namespace.server.startswith('http://') self.namespace = namespace def __getattr__(self, name): @@ -47,12 +47,8 @@ class NamespaceConfig(object): @property def server_path(self): """File path based on ``server``.""" - return self.namespace.server.replace('/', os.path.sep) - - @property - def server_url(self): - """Full server URL (including HTTPS scheme).""" - return 'https://' + self.namespace.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 diff --git a/letsencrypt/client/constants.py b/letsencrypt/client/constants.py index 3f8cf4f05..f0fb38088 100644 --- a/letsencrypt/client/constants.py +++ b/letsencrypt/client/constants.py @@ -11,7 +11,7 @@ SETUPTOOLS_PLUGINS_ENTRY_POINT = "letsencrypt.plugins" CLI_DEFAULTS = dict( config_files=["/etc/letsencrypt/cli.ini"], verbose_count=-(logging.WARNING / 10), - server="www.letsencrypt-demo.org/acme/new-reg", + server="https://www.letsencrypt-demo.org/acme/new-reg", rsa_key_size=2048, rollback_checkpoints=0, config_dir="/etc/letsencrypt", diff --git a/letsencrypt/client/tests/configuration_test.py b/letsencrypt/client/tests/configuration_test.py index cbbcd57ba..dbaa2ee84 100644 --- a/letsencrypt/client/tests/configuration_test.py +++ b/letsencrypt/client/tests/configuration_test.py @@ -10,10 +10,10 @@ class NamespaceConfigTest(unittest.TestCase): def setUp(self): from letsencrypt.client.configuration import NamespaceConfig - namespace = mock.MagicMock( + self.namespace = mock.MagicMock( config_dir='/tmp/config', work_dir='/tmp/foo', foo='bar', - server='acme-server.org:443/new') - self.config = NamespaceConfig(namespace) + server='https://acme-server.org:443/new') + self.config = NamespaceConfig(self.namespace) def test_proxy_getattr(self): self.assertEqual(self.config.foo, 'bar') @@ -23,9 +23,10 @@ class NamespaceConfigTest(unittest.TestCase): self.assertEqual(['acme-server.org:443', 'new'], self.config.server_path.split(os.path.sep)) - def test_server_url(self): - self.assertEqual( - self.config.server_url, 'https://acme-server.org:443/new') + self.namespace.server = ('http://user:pass@acme.server:443' + '/p/a/t/h;parameters?query#fragment') + self.assertEqual(['user:pass@acme.server:443', 'p', 'a', 't', 'h'], + self.config.server_path.split(os.path.sep)) @mock.patch('letsencrypt.client.configuration.constants') def test_dynamic_dirs(self, constants): From 87c00ab2594d4f427daaf3818227d75191b175a6 Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Sat, 9 May 2015 19:37:04 +0000 Subject: [PATCH 2/5] Test mode (no certificate validation). --- letsencrypt/client/cli.py | 2 ++ letsencrypt/client/client.py | 3 ++- letsencrypt/client/constants.py | 1 + letsencrypt/client/interfaces.py | 3 +++ letsencrypt/client/network2.py | 6 +++++- letsencrypt/client/tests/network2_test.py | 20 +++++++++++++++++++- 6 files changed, 32 insertions(+), 3 deletions(-) diff --git a/letsencrypt/client/cli.py b/letsencrypt/client/cli.py index a83c0f767..29161540f 100644 --- a/letsencrypt/client/cli.py +++ b/letsencrypt/client/cli.py @@ -238,6 +238,8 @@ def create_parser(plugins): help="Skip the end user license agreement screen.") add("-t", "--text", dest="text_mode", action="store_true", help="Use the text output instead of the curses UI.") + 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 diff --git a/letsencrypt/client/client.py b/letsencrypt/client/client.py index 6622ea8de..1e0c6b955 100644 --- a/letsencrypt/client/client.py +++ b/letsencrypt/client/client.py @@ -62,7 +62,8 @@ class Client(object): # TODO: Allow for other alg types besides RS256 self.network = network2.Network( - config.server_url, jwk.JWKRSA.load(self.account.key.pem)) + config.server_url, jwk.JWKRSA.load(self.account.key.pem), + verify_ssl=(not config.test_mode)) self.config = config diff --git a/letsencrypt/client/constants.py b/letsencrypt/client/constants.py index f0fb38088..399a2f003 100644 --- a/letsencrypt/client/constants.py +++ b/letsencrypt/client/constants.py @@ -21,6 +21,7 @@ CLI_DEFAULTS = dict( certs_dir="/etc/letsencrypt/certs", cert_path="/etc/letsencrypt/certs/cert-letsencrypt.pem", chain_path="/etc/letsencrypt/certs/chain-letsencrypt.pem", + test_mode=False, ) """Defaults for CLI flags and `.IConfig` attributes.""" diff --git a/letsencrypt/client/interfaces.py b/letsencrypt/client/interfaces.py index b005eb02d..346c3cc7e 100644 --- a/letsencrypt/client/interfaces.py +++ b/letsencrypt/client/interfaces.py @@ -178,6 +178,9 @@ class IConfig(zope.interface.Interface): cert_path = zope.interface.Attribute("Let's Encrypt certificate file path.") chain_path = zope.interface.Attribute("Let's Encrypt chain file path.") + test_mode = zope.interface.Attribute( + "Test mode. Disables certificate verification.") + class IInstaller(IPlugin): """Generic Let's Encrypt Installer Interface. diff --git a/letsencrypt/client/network2.py b/letsencrypt/client/network2.py index eaa485a8d..6b6914c97 100644 --- a/letsencrypt/client/network2.py +++ b/letsencrypt/client/network2.py @@ -29,6 +29,7 @@ class Network(object): :ivar str new_reg_uri: Location of new-reg :ivar key: `.JWK` (private) :ivar alg: `.JWASignature` + :ivar bool verify_ssl: Verify SSL certificates? """ @@ -36,10 +37,11 @@ class Network(object): JSON_CONTENT_TYPE = 'application/json' JSON_ERROR_CONTENT_TYPE = 'application/problem+json' - def __init__(self, new_reg_uri, key, alg=jose.RS256): + def __init__(self, new_reg_uri, key, alg=jose.RS256, verify_ssl=True): self.new_reg_uri = new_reg_uri self.key = key self.alg = alg + self.verify_ssl = verify_ssl def _wrap_in_jws(self, obj): """Wrap `JSONDeSerializable` object in JWS. @@ -115,6 +117,7 @@ class Network(object): :rtype: `requests.Response` """ + kwargs.setdefault('verify', self.verify_ssl) try: response = requests.get(uri, **kwargs) except requests.exceptions.RequestException as error: @@ -134,6 +137,7 @@ class Network(object): """ logging.debug('Sending POST data: %s', data) + kwargs.setdefault('verify', self.verify_ssl) try: response = requests.post(uri, data=data, **kwargs) except requests.exceptions.RequestException as error: diff --git a/letsencrypt/client/tests/network2_test.py b/letsencrypt/client/tests/network2_test.py index d14d27f6a..44b30a930 100644 --- a/letsencrypt/client/tests/network2_test.py +++ b/letsencrypt/client/tests/network2_test.py @@ -39,9 +39,10 @@ class NetworkTest(unittest.TestCase): def setUp(self): from letsencrypt.client.network2 import Network + self.verify_ssl = mock.MagicMock() self.net = Network( new_reg_uri='https://www.letsencrypt-demo.org/acme/new-reg', - key=KEY, alg=jose.RS256) + key=KEY, alg=jose.RS256, verify_ssl=self.verify_ssl) self.response = mock.MagicMock(ok=True, status_code=httplib.OK) self.response.headers = {} self.response.links = {} @@ -84,6 +85,9 @@ class NetworkTest(unittest.TestCase): self.net._post = mock.MagicMock(return_value=self.response) self.net._get = mock.MagicMock(return_value=self.response) + def test_init(self): + self.assertTrue(self.net.verify_ssl is self.verify_ssl) + def test_wrap_in_jws(self): class MockJSONDeSerializable(jose.JSONDeSerializable): # pylint: disable=missing-docstring @@ -172,6 +176,20 @@ class NetworkTest(unittest.TestCase): self.net._check_response.assert_called_once_with( requests_mock.post('uri', 'data'), content_type='ct') + @mock.patch('letsencrypt.client.network2.requests') + def test_get_post_verify_ssl(self, requests_mock): + # pylint: disable=protected-access + self.net._check_response = mock.MagicMock() + + for verify_ssl in [True, False]: + self.net.verify_ssl = verify_ssl + self.net._get('uri') + self.net._post('uri', 'data') + requests_mock.get.assert_called_once_with('uri', verify=verify_ssl) + requests_mock.post.assert_called_once_with( + 'uri', data='data', verify=verify_ssl) + requests_mock.reset_mock() + def test_register(self): self.response.status_code = httplib.CREATED self.response.json.return_value = self.regr.body.to_json() From da53813770afc26565f71c25f73d42420ca0fa05 Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Tue, 12 May 2015 21:18:45 +0000 Subject: [PATCH 3/5] Bump coverage --- tox.cover.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.cover.sh b/tox.cover.sh index 273dea04e..80b6474d7 100755 --- a/tox.cover.sh +++ b/tox.cover.sh @@ -18,5 +18,5 @@ cover () { # don't use sequential composition (;), if letsencrypt_nginx returns # 0, coveralls submit will be triggered (c.f. .travis.yml, # after_success) -cover letsencrypt 94 && cover acme 100 && \ +cover letsencrypt 95 && cover acme 100 && \ cover letsencrypt_apache 78 && cover letsencrypt_nginx 96 From e06c0cbbf7d67f134150dd2d5dc26c1b3206b640 Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Tue, 12 May 2015 22:09:45 +0000 Subject: [PATCH 4/5] Revert "Bump coverage" This reverts commit da53813770afc26565f71c25f73d42420ca0fa05. --- tox.cover.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.cover.sh b/tox.cover.sh index 80b6474d7..273dea04e 100755 --- a/tox.cover.sh +++ b/tox.cover.sh @@ -18,5 +18,5 @@ cover () { # don't use sequential composition (;), if letsencrypt_nginx returns # 0, coveralls submit will be triggered (c.f. .travis.yml, # after_success) -cover letsencrypt 95 && cover acme 100 && \ +cover letsencrypt 94 && cover acme 100 && \ cover letsencrypt_apache 78 && cover letsencrypt_nginx 96 From 8dac7daf87a9704874a1924d4a14a50373cd7c2e Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Fri, 22 May 2015 06:21:53 +0000 Subject: [PATCH 5/5] config.server_url -> config.server --- letsencrypt/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/letsencrypt/client.py b/letsencrypt/client.py index 8b6bbf2e2..ae1667dfa 100644 --- a/letsencrypt/client.py +++ b/letsencrypt/client.py @@ -62,7 +62,7 @@ class Client(object): # TODO: Allow for other alg types besides RS256 self.network = network2.Network( - config.server_url, jwk.JWKRSA.load(self.account.key.pem), + config.server, jwk.JWKRSA.load(self.account.key.pem), verify_ssl=(not config.test_mode)) self.config = config