From 7aee94e2216d48a53c045f9a6e494b28495adc34 Mon Sep 17 00:00:00 2001 From: signop Date: Tue, 15 May 2018 14:53:42 -0700 Subject: [PATCH] WIP commit of adding a --source-address flag. --- acme/acme/client.py | 1 + acme/acme/client_test.py | 10 ++++++---- certbot/cli.py | 3 +++ certbot/client.py | 3 ++- certbot/interfaces.py | 2 ++ certbot/tests/cli_test.py | 14 ++++++++++++++ certbot/tests/main_test.py | 2 +- certbot/tests/util.py | 1 + 8 files changed, 30 insertions(+), 6 deletions(-) diff --git a/acme/acme/client.py b/acme/acme/client.py index bdc07fb1c..a88c85743 100644 --- a/acme/acme/client.py +++ b/acme/acme/client.py @@ -878,6 +878,7 @@ class ClientNetwork(object): # pylint: disable=too-many-instance-attributes if source_address is not None: adapter = SourceAddressAdapter(source_address) + logger.debug("Set source address for client to %s", source_address) self.session.mount("http://", adapter) self.session.mount("https://", adapter) diff --git a/acme/acme/client_test.py b/acme/acme/client_test.py index f3018ed81..2bc8ea3c5 100644 --- a/acme/acme/client_test.py +++ b/acme/acme/client_test.py @@ -1134,13 +1134,15 @@ class ClientNetworkSourceAddressBindingTest(unittest.TestCase): used the provided source address.""" def setUp(self): - self.source_address = "8.8.8.8" + self.source_addresses = ["8.8.8.8", "2001:db8:8:4::2", + "2001:0000:4136:e378:8000:63bf:3fff:fdd2"] def test_source_address_set(self): from acme.client import ClientNetwork - net = ClientNetwork(key=None, alg=None, source_address=self.source_address) - for adapter in net.session.adapters.values(): - self.assertTrue(self.source_address in adapter.source_address) + for source_address in self.source_addresses: + net = ClientNetwork(key=None, alg=None, source_address=source_address) + for adapter in net.session.adapters.values(): + self.assertTrue(source_address in adapter.source_address) def test_behavior_assumption(self): """This is a test that guardrails the HTTPAdapter behavior so that if the default for diff --git a/certbot/cli.py b/certbot/cli.py index b71d60055..e8fc62d6e 100644 --- a/certbot/cli.py +++ b/certbot/cli.py @@ -1237,6 +1237,9 @@ def _create_subparsers(helpful): help="Add a comment to the default user agent string. May be used when repackaging Certbot " "or calling it from another tool to allow additional statistical data to be collected." " Ignored if --user-agent is set. (Example: Foo-Wrapper/1.0)") + helpful.add( + None, "--source-address", default=None, type=str, + help="Specify a source IP address for the client.") helpful.add("certonly", "--csr", default=flag_default("csr"), type=read_file, help="Path to a Certificate Signing Request (CSR) in DER or PEM format." diff --git a/certbot/client.py b/certbot/client.py index 45dc9c63b..fb1247d81 100644 --- a/certbot/client.py +++ b/certbot/client.py @@ -42,7 +42,8 @@ def acme_from_config_key(config, key, regr=None): "Wrangle ACME client construction" # TODO: Allow for other alg types besides RS256 net = acme_client.ClientNetwork(key, account=regr, verify_ssl=(not config.no_verify_ssl), - user_agent=determine_user_agent(config)) + user_agent=determine_user_agent(config), + source_address=config.source_address) return acme_client.BackwardsCompatibleClientV2(net, key, config.server) diff --git a/certbot/interfaces.py b/certbot/interfaces.py index c96f6bd51..bc5b333fb 100644 --- a/certbot/interfaces.py +++ b/certbot/interfaces.py @@ -231,6 +231,8 @@ class IConfig(zope.interface.Interface): "A conforming ACME server will still attempt to connect on port 443.") tls_sni_01_address = zope.interface.Attribute( "The address the server listens to during tls-sni-01 challenge.") + source_address = zope.interface.Attribute( + "The address the client binds to when connecting to the ACME server.") http01_port = zope.interface.Attribute( "Port used in the http-01 challenge. " diff --git a/certbot/tests/cli_test.py b/certbot/tests/cli_test.py index 1bba6991a..f52822dca 100644 --- a/certbot/tests/cli_test.py +++ b/certbot/tests/cli_test.py @@ -430,6 +430,20 @@ class ParseTest(unittest.TestCase): # pylint: disable=too-many-public-methods self.assertRaises(errors.Error, self.parse, "--allow-subset-of-names -d *.example.org".split()) + def test_source_address_flag_v4(self): + namespace = self.parse(["--source-address=8.8.8.8"]) + self.assertEqual(namespace.source_address, "8.8.8.8") + + def test_source_address_flag_v6(self): + ipv6_addrs = ["2001:db8:8:4::2", "2001:0000:4136:e378:8000:63bf:3fff:fdd2"] + for addr in ipv6_addrs: + namespace = self.parse(["--source-address={}".format(addr)]) + self.assertEqual(namespace.source_address, addr) + + def test_source_address_flag_not_set(self): + namespace = self.parse([]) + self.assertIsNone(namespace.source_address) + class DefaultTest(unittest.TestCase): """Tests for certbot.cli._Default.""" diff --git a/certbot/tests/main_test.py b/certbot/tests/main_test.py index 22653ca3a..46a222b29 100644 --- a/certbot/tests/main_test.py +++ b/certbot/tests/main_test.py @@ -692,7 +692,7 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met args += ["--user-agent", ua] self._call_no_clientmock(args) acme_net.assert_called_once_with(mock.ANY, account=mock.ANY, verify_ssl=True, - user_agent=ua) + user_agent=ua, source_address=mock.ANY) @mock.patch('certbot.main.plug_sel.record_chosen_plugins') @mock.patch('certbot.main.plug_sel.pick_installer') diff --git a/certbot/tests/util.py b/certbot/tests/util.py index 8434d11de..4d8bb0557 100644 --- a/certbot/tests/util.py +++ b/certbot/tests/util.py @@ -336,6 +336,7 @@ class ConfigTestCase(TempDirTestCase): self.config.fullchain_path = constants.CLI_DEFAULTS['auth_chain_path'] self.config.chain_path = constants.CLI_DEFAULTS['auth_chain_path'] self.config.server = "https://example.com" + self.config.source_address = None def lock_and_call(func, lock_path): """Grab a lock for lock_path and call func.