diff --git a/letsencrypt/plugins/manual.py b/letsencrypt/plugins/manual.py index f7717064b..61a5acdb3 100644 --- a/letsencrypt/plugins/manual.py +++ b/letsencrypt/plugins/manual.py @@ -46,6 +46,15 @@ If you don't have HTTP server configured, you can run the following command on the target server (as root): {command} +""" + + # a disclaimer about your current IP being transmitted to Let's Encrypt's servers. + IP_DISCLAIMER = """\ +NOTE: The IP of this machine will be publicly logged as having requested this certificate. \ +If you're running letsencrypt in manual mode on a machine that is not your server, \ +please ensure you're okay with that. + +Are you OK with your IP being logged? """ # "cd /tmp/letsencrypt" makes sure user doesn't serve /root, @@ -151,6 +160,10 @@ binary for temporary key/certificate generation.""".replace("\n", "") if self._httpd.poll() is not None: raise errors.Error("Couldn't execute manual command") else: + if not zope.component.getUtility(interfaces.IDisplay).yesno( + self.IP_DISCLAIMER, "Yes", "No"): + raise errors.PluginError("Must agree to IP logging to proceed") + self._notify_and_wait(self.MESSAGE_TEMPLATE.format( validation=validation.json_dumps(), response=response, uri=response.uri(achall.domain, achall.challb.chall), diff --git a/letsencrypt/plugins/manual_test.py b/letsencrypt/plugins/manual_test.py index 8cfff1cc5..a52129635 100644 --- a/letsencrypt/plugins/manual_test.py +++ b/letsencrypt/plugins/manual_test.py @@ -43,11 +43,13 @@ class AuthenticatorTest(unittest.TestCase): def test_perform_empty(self): self.assertEqual([], self.auth.perform([])) + @mock.patch("letsencrypt.plugins.manual.zope.component.getUtility") @mock.patch("letsencrypt.plugins.manual.sys.stdout") @mock.patch("acme.challenges.SimpleHTTPResponse.simple_verify") @mock.patch("__builtin__.raw_input") - def test_perform(self, mock_raw_input, mock_verify, mock_stdout): + def test_perform(self, mock_raw_input, mock_verify, mock_stdout, mock_interaction): mock_verify.return_value = True + mock_interaction().yesno.return_value = True resp = challenges.SimpleHTTPResponse(tls=False) self.assertEqual([resp], self.auth.perform(self.achalls)) @@ -61,6 +63,15 @@ class AuthenticatorTest(unittest.TestCase): mock_verify.return_value = False self.assertEqual([None], self.auth.perform(self.achalls)) + @mock.patch("letsencrypt.plugins.manual.zope.component.getUtility") + @mock.patch("letsencrypt.plugins.manual.Authenticator._notify_and_wait") + def test_disagree_with_ip_logging(self, mock_notify, mock_interaction): + mock_interaction().yesno.return_value = False + mock_notify.side_effect = errors.Error("Exception not raised, \ + continued execution even after disagreeing with IP logging") + + self.assertRaises(errors.PluginError, self.auth.perform, self.achalls) + @mock.patch("letsencrypt.plugins.manual.subprocess.Popen", autospec=True) def test_perform_test_command_oserror(self, mock_popen): mock_popen.side_effect = OSError