From 93310fe67c2f854593f5acb8bf74c914d6d81743 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Thu, 25 May 2017 12:16:05 -0700 Subject: [PATCH 1/9] Fixes #4719 (#4737) * Automatically delete temp log file when not used. This allows close() calls in logging.shutdown() to cause the file to be deleted when no logging output has been written to the file. * Make certbot.log.MemoryHandler.flush() a noop. This causes MemoryHandler.flush() calls in logging.shutdown to be a noop, allowing us to control when the handler is actually flushed. This prevents log records from being sent to a temporary file handler for things like `certbot --version`. * Keep reference to certbot.log.MemoryHandler.target In Python 2.7+, the logging module only keeps weak references to created logging handlers. Because of this, the MemoryHandler's target will not be properly flushed and closed when logging.shutdown() is called on program exit unless we keep a reference to it in the MemoryHandler. * Fixes #4719. This completes the changes necessary to fix #4719. Now temporary log files are not created if sys.exit() is called before logging is fully set up. These files are still created if Certbot crashes for any other reason. * Document pre_arg_parse_except_hook args. --- certbot/log.py | 97 ++++++++++++++++++++++++++++++++------- certbot/tests/log_test.py | 57 +++++++++++++++-------- 2 files changed, 120 insertions(+), 34 deletions(-) diff --git a/certbot/log.py b/certbot/log.py index c7bc867f1..889b5c50a 100644 --- a/certbot/log.py +++ b/certbot/log.py @@ -70,7 +70,8 @@ def pre_arg_parse_setup(): # close() are explicitly called util.atexit_register(logging.shutdown) sys.excepthook = functools.partial( - except_hook, debug='--debug' in sys.argv, log_path=temp_handler.path) + pre_arg_parse_except_hook, memory_handler, + debug='--debug' in sys.argv, log_path=temp_handler.path) def post_arg_parse_setup(config): @@ -103,8 +104,9 @@ def post_arg_parse_setup(config): root_logger.removeHandler(memory_handler) temp_handler = memory_handler.target memory_handler.setTarget(file_handler) + memory_handler.flush(force=True) memory_handler.close() - temp_handler.delete_and_close() + temp_handler.close() if config.quiet: level = constants.QUIET_LOGGING_LEVEL @@ -115,7 +117,7 @@ def post_arg_parse_setup(config): logger.info('Saving debug log to %s', file_path) sys.excepthook = functools.partial( - except_hook, debug=config.debug, log_path=logs_dir) + post_arg_parse_except_hook, debug=config.debug, log_path=logs_dir) def setup_log_file_handler(config, logfile, fmt): @@ -194,8 +196,7 @@ class MemoryHandler(logging.handlers.MemoryHandler): """Buffers logging messages in memory until the buffer is flushed. This differs from `logging.handlers.MemoryHandler` in that flushing - only happens when it is done explicitly by calling flush() or - close(). + only happens when flush(force=True) is called. """ def __init__(self, target=None): @@ -209,6 +210,33 @@ class MemoryHandler(logging.handlers.MemoryHandler): else: super(MemoryHandler, self).__init__(capacity, target=target) + def close(self): + """Close the memory handler, but don't set the target to None.""" + # This allows the logging module which may only have a weak + # reference to the target handler to properly flush and close it. + target = self.target + if sys.version_info < (2, 7): # pragma: no cover + logging.handlers.MemoryHandler.close(self) + else: + super(MemoryHandler, self).close() + self.target = target + + def flush(self, force=False): # pylint: disable=arguments-differ + """Flush the buffer if force=True. + + If force=False, this call is a noop. + + :param bool force: True if the buffer should be flushed. + + """ + # This method allows flush() calls in logging.shutdown to be a + # noop so we can control when this handler is flushed. + if force: + if sys.version_info < (2, 7): # pragma: no cover + logging.handlers.MemoryHandler.flush(self) + else: + super(MemoryHandler, self).flush() + def shouldFlush(self, record): """Should the buffer be automatically flushed? @@ -224,7 +252,9 @@ class MemoryHandler(logging.handlers.MemoryHandler): class TempHandler(logging.StreamHandler): """Safely logs messages to a temporary file. - The file is created with permissions 600. + The file is created with permissions 600. If no log records are sent + to this handler, the temporary file is deleted when the handler is + closed. :ivar str path: file system path to the temporary log file @@ -238,19 +268,26 @@ class TempHandler(logging.StreamHandler): else: super(TempHandler, self).__init__(stream) self.path = stream.name + self._delete = True - def delete_and_close(self): - """Close the handler and delete the temporary log file.""" - self._close(delete=True) + def emit(self, record): + """Log the specified logging record. + + :param logging.LogRecord record: Record to be formatted + + """ + self._delete = False + # logging handlers use old style classes in Python 2.6 so + # super() cannot be used + if sys.version_info < (2, 7): # pragma: no cover + logging.StreamHandler.emit(self, record) + else: + super(TempHandler, self).emit(record) def close(self): - """Close the handler and the temporary log file.""" - self._close(delete=False) - - def _close(self, delete): """Close the handler and the temporary log file. - :param bool delete: True if the log file should be deleted + The temporary log file is deleted if it wasn't used. """ self.acquire() @@ -258,8 +295,9 @@ class TempHandler(logging.StreamHandler): # StreamHandler.close() doesn't close the stream to allow a # stream like stderr to be used self.stream.close() - if delete: + if self._delete: os.remove(self.path) + self._delete = False if sys.version_info < (2, 7): # pragma: no cover logging.StreamHandler.close(self) else: @@ -268,7 +306,34 @@ class TempHandler(logging.StreamHandler): self.release() -def except_hook(exc_type, exc_value, trace, debug, log_path): +def pre_arg_parse_except_hook(memory_handler, *args, **kwargs): + """A simple wrapper around post_arg_parse_except_hook. + + The additional functionality provided by this wrapper is the memory + handler will be flushed before Certbot exits. This allows us to + write logging messages to a temporary file if we crashed before + logging was fully configured. + + Since sys.excepthook isn't called on SystemExit exceptions, the + memory handler will not be flushed in this case which prevents us + from creating temporary log files when argparse exits because a + command line argument was invalid or -h, --help, or --version was + provided on the command line. + + :param MemoryHandler memory_handler: memory handler to flush + :param tuple args: args for post_arg_parse_except_hook + :param dict kwargs: kwargs for post_arg_parse_except_hook + + """ + try: + post_arg_parse_except_hook(*args, **kwargs) + finally: + # flush() is called here so messages logged during + # post_arg_parse_except_hook are also flushed. + memory_handler.flush(force=True) + + +def post_arg_parse_except_hook(exc_type, exc_value, trace, debug, log_path): """Logs fatal exceptions and reports them to the user. If debug is True, the full exception and traceback is shown to the diff --git a/certbot/tests/log_test.py b/certbot/tests/log_test.py index 13021220b..72ff076dd 100644 --- a/certbot/tests/log_test.py +++ b/certbot/tests/log_test.py @@ -26,7 +26,7 @@ class PreArgParseSetupTest(unittest.TestCase): return pre_arg_parse_setup(*args, **kwargs) @mock.patch('certbot.log.sys') - @mock.patch('certbot.log.except_hook') + @mock.patch('certbot.log.pre_arg_parse_except_hook') @mock.patch('certbot.log.logging.getLogger') @mock.patch('certbot.log.util.atexit_register') def test_it(self, mock_register, mock_get, mock_except_hook, mock_sys): @@ -34,11 +34,6 @@ class PreArgParseSetupTest(unittest.TestCase): mock_sys.version_info = sys.version_info self._call() - mock_register.assert_called_once_with(logging.shutdown) - mock_sys.excepthook(1, 2, 3) - mock_except_hook.assert_called_once_with( - 1, 2, 3, debug=True, log_path=mock.ANY) - mock_root_logger = mock_get() mock_root_logger.setLevel.assert_called_once_with(logging.DEBUG) self.assertEqual(mock_root_logger.addHandler.call_count, 2) @@ -54,6 +49,11 @@ class PreArgParseSetupTest(unittest.TestCase): self.assertTrue( isinstance(memory_handler.target, logging.StreamHandler)) + mock_register.assert_called_once_with(logging.shutdown) + mock_sys.excepthook(1, 2, 3) + mock_except_hook.assert_called_once_with( + memory_handler, 1, 2, 3, debug=True, log_path=mock.ANY) + class PostArgParseSetupTest(test_util.TempDirTestCase): """Tests for certbot.log.post_arg_parse_setup.""" @@ -88,7 +88,8 @@ class PostArgParseSetupTest(test_util.TempDirTestCase): def test_common(self): with mock.patch('certbot.log.logging.getLogger') as mock_get_logger: mock_get_logger.return_value = self.root_logger - with mock.patch('certbot.log.except_hook') as mock_except_hook: + except_hook_path = 'certbot.log.post_arg_parse_except_hook' + with mock.patch(except_hook_path) as mock_except_hook: with mock.patch('certbot.log.sys') as mock_sys: mock_sys.version_info = sys.version_info self._call(self.config) @@ -203,12 +204,13 @@ class MemoryHandlerTest(unittest.TestCase): def test_flush(self): self._test_log_debug() - self.handler.flush() + self.handler.flush(force=True) self.assertEqual(self.stream.getvalue(), self.msg + '\n') def test_not_flushed(self): # By default, logging.ERROR messages and higher are flushed self.logger.critical(self.msg) + self.handler.flush() self.assertEqual(self.stream.getvalue(), '') def test_target_reset(self): @@ -217,7 +219,7 @@ class MemoryHandlerTest(unittest.TestCase): new_stream = six.StringIO() new_stream_handler = logging.StreamHandler(new_stream) self.handler.setTarget(new_stream_handler) - self.handler.flush() + self.handler.flush(force=True) self.assertEqual(self.stream.getvalue(), '') self.assertEqual(new_stream.getvalue(), self.msg + '\n') new_stream_handler.close() @@ -234,31 +236,50 @@ class TempHandlerTest(unittest.TestCase): self.handler = TempHandler() def tearDown(self): - if not self.closed: - self.handler.delete_and_close() + self.handler.close() def test_permissions(self): self.assertTrue( util.check_permissions(self.handler.path, 0o600, os.getuid())) def test_delete(self): - self.handler.delete_and_close() - self.closed = True + self.handler.close() self.assertFalse(os.path.exists(self.handler.path)) def test_no_delete(self): + self.handler.emit(mock.MagicMock()) self.handler.close() - self.closed = True self.assertTrue(os.path.exists(self.handler.path)) os.remove(self.handler.path) -class ExceptHookTest(unittest.TestCase): - """Tests for certbot.log.except_hook.""" +class PreArgParseExceptHookTest(unittest.TestCase): + """Tests for certbot.log.pre_arg_parse_except_hook.""" @classmethod def _call(cls, *args, **kwargs): - from certbot.log import except_hook - return except_hook(*args, **kwargs) + from certbot.log import pre_arg_parse_except_hook + return pre_arg_parse_except_hook(*args, **kwargs) + + @mock.patch('certbot.log.post_arg_parse_except_hook') + def test_it(self, mock_post_arg_parse_except_hook): + # pylint: disable=star-args + memory_handler = mock.MagicMock() + args = ('some', 'args',) + kwargs = {'some': 'kwargs'} + + self._call(memory_handler, *args, **kwargs) + + mock_post_arg_parse_except_hook.assert_called_once_with( + *args, **kwargs) + memory_handler.flush.assert_called_once_with(force=True) + + +class PostArgParseExceptHookTest(unittest.TestCase): + """Tests for certbot.log.post_arg_parse_except_hook.""" + @classmethod + def _call(cls, *args, **kwargs): + from certbot.log import post_arg_parse_except_hook + return post_arg_parse_except_hook(*args, **kwargs) def setUp(self): self.error_msg = 'test error message' From dc63056da7cea24399adf305acec936f4be4e1a7 Mon Sep 17 00:00:00 2001 From: Kevin Le Date: Thu, 25 May 2017 16:27:31 -0700 Subject: [PATCH 2/9] add build of Dockerfile-dev (#4717) --- .travis.yml | 5 +++++ tox.ini | 9 +++++++++ 2 files changed, 14 insertions(+) diff --git a/.travis.yml b/.travis.yml index 22bde836e..d6c8f557e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -74,6 +74,11 @@ matrix: services: docker before_install: addons: + - sudo: required + env: TOXENV=docker_dev + services: docker + before_install: + addons: - python: "2.7" env: TOXENV=apacheconftest sudo: required diff --git a/tox.ini b/tox.ini index 89eef5c76..94d6048c3 100644 --- a/tox.ini +++ b/tox.ini @@ -198,3 +198,12 @@ commands = whitelist_externals = docker passenv = DOCKER_* + +[testenv:docker_dev] +# tests the Dockerfile-dev file to ensure development with it works +# as expected +commands = + docker-compose run --rm --service-ports development bash -c 'tox -e lint' +whitelist_externals = + docker +passenv = DOCKER_* From 4cbdea6ccbebd33d765402a21ede797c685e925d Mon Sep 17 00:00:00 2001 From: Nicholas Tait Date: Thu, 25 May 2017 16:29:19 -0700 Subject: [PATCH 3/9] Improve warning message to user after an operation is canceled (#4723) Fixes #4134 --- certbot/main.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/certbot/main.py b/certbot/main.py index 50dad8d1e..d3f6eaa09 100644 --- a/certbot/main.py +++ b/certbot/main.py @@ -167,8 +167,7 @@ def _handle_identical_cert_request(config, lineage): # TODO: Add notification related to command-line options for # skipping the menu for this case. raise errors.Error( - "User chose to cancel the operation and may " - "reinvoke the client.") + "Operation canceled. You may re-run the client.") elif response[1] == 0: return "reinstall", lineage elif response[1] == 1: From 346659c47fddb96f85182a64c46c4c8e4b73e18e Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Fri, 26 May 2017 10:21:21 -0700 Subject: [PATCH 4/9] Release 0.14.2 (#4742) * Release 0.14.2 (cherry picked from commit d9a2612d21f7cca3d34cf0bda32c2ef87754af13) * Bump version to 0.15.0 --- certbot-auto | 26 +++++++++---------- docs/cli-help.txt | 2 +- letsencrypt-auto | 26 +++++++++---------- letsencrypt-auto-source/certbot-auto.asc | 14 +++++----- letsencrypt-auto-source/letsencrypt-auto.sig | Bin 256 -> 256 bytes 5 files changed, 34 insertions(+), 34 deletions(-) diff --git a/certbot-auto b/certbot-auto index 39edbb3c5..c88c96a5b 100755 --- a/certbot-auto +++ b/certbot-auto @@ -28,7 +28,7 @@ if [ -z "$VENV_PATH" ]; then VENV_PATH="$XDG_DATA_HOME/$VENV_NAME" fi VENV_BIN="$VENV_PATH/bin" -LE_AUTO_VERSION="0.14.1" +LE_AUTO_VERSION="0.14.2" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates @@ -860,18 +860,18 @@ letsencrypt==0.7.0 \ # THE LINES BELOW ARE EDITED BY THE RELEASE SCRIPT; ADD ALL DEPENDENCIES ABOVE. -acme==0.14.1 \ - --hash=sha256:f535d6459dcafa436749a8d2fdfafed21b792efa05b8bd3263fcd739c2e1497c \ - --hash=sha256:0e6d9d1bbb71d80c61c8d10ab9a40bcf38e25f0fa016b9769e96ebf5a79b552b -certbot==0.14.1 \ - --hash=sha256:f950a058d4f657160de4ad163d9f781fe7adeec0c0a44556841adb03ad135d13 \ - --hash=sha256:519b28124869d97116cb1f2f04ccc2937c0b2fd32fce43576eb80c0e4ff1ab65 -certbot-apache==0.14.1 \ - --hash=sha256:1dda9b4dcf66f6dfba37c787d849e69ad25a344572f74a76fc4447bb1a5417b2 \ - --hash=sha256:da84996e345fc5789da3575225536b27fa3b35f89b2db2d8f494a34bced14f9b -certbot-nginx==0.14.1 \ - --hash=sha256:bd3d4a1dcd6fa9e8ead19a9da88693f08b63464c86be2442e42cd60565c3f05f \ - --hash=sha256:f0c19f667072e4cfa6b92abf8312b6bee3ed1d2432676b211593034e7d1abb7e +acme==0.14.2 \ + --hash=sha256:b3068d360beccd3b23a81d7cd2522437d847328811b573a5fe14eb04147667cf \ + --hash=sha256:166b7f4858f5b144b03236b995b787a9da1e410121fb7dcac9c7d3b594bc6fcd +certbot==0.14.2 \ + --hash=sha256:525e15e43c833db9a9934308d69dcdd220fa799488cd84543748671c68aba73d \ + --hash=sha256:5bc8547dcfc0fc587e15253e264f79d8397e48bfbc8697d5aca87eae978769ac +certbot-apache==0.14.2 \ + --hash=sha256:15647d424a5a7e4c44c684324ac07a457a2e0d61fce1acaa421c0b641941a350 \ + --hash=sha256:e5220d3e6ee5114b41b398110dfbd8f13bd1e8c7902758634449e0b4ae515b76 +certbot-nginx==0.14.2 \ + --hash=sha256:231377fbdfb6303adddc73fe3f856b9fb6d0175db825650e39fe3dfd6a58f8ef \ + --hash=sha256:529a18280acf41f5f7e0fe7d82c0a5d4d197d14f82742eaec54bb1d3f69c325a UNLIKELY_EOF # ------------------------------------------------------------------------- diff --git a/docs/cli-help.txt b/docs/cli-help.txt index f1fcba132..1c46ea2c3 100644 --- a/docs/cli-help.txt +++ b/docs/cli-help.txt @@ -89,7 +89,7 @@ optional arguments: case, and to know when to deprecate support for past Python versions and flags. If you wish to hide this information from the Let's Encrypt server, set this to - "". (default: CertbotACMEClient/0.14.1 (certbot; + "". (default: CertbotACMEClient/0.14.2 (certbot; Ubuntu 16.04.2 LTS) Authenticator/XXX Installer/YYY (SUBCOMMAND; flags: FLAGS) Py/2.7.12). The flags encoded in the user agent are: --duplicate, --force- diff --git a/letsencrypt-auto b/letsencrypt-auto index 39edbb3c5..c88c96a5b 100755 --- a/letsencrypt-auto +++ b/letsencrypt-auto @@ -28,7 +28,7 @@ if [ -z "$VENV_PATH" ]; then VENV_PATH="$XDG_DATA_HOME/$VENV_NAME" fi VENV_BIN="$VENV_PATH/bin" -LE_AUTO_VERSION="0.14.1" +LE_AUTO_VERSION="0.14.2" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates @@ -860,18 +860,18 @@ letsencrypt==0.7.0 \ # THE LINES BELOW ARE EDITED BY THE RELEASE SCRIPT; ADD ALL DEPENDENCIES ABOVE. -acme==0.14.1 \ - --hash=sha256:f535d6459dcafa436749a8d2fdfafed21b792efa05b8bd3263fcd739c2e1497c \ - --hash=sha256:0e6d9d1bbb71d80c61c8d10ab9a40bcf38e25f0fa016b9769e96ebf5a79b552b -certbot==0.14.1 \ - --hash=sha256:f950a058d4f657160de4ad163d9f781fe7adeec0c0a44556841adb03ad135d13 \ - --hash=sha256:519b28124869d97116cb1f2f04ccc2937c0b2fd32fce43576eb80c0e4ff1ab65 -certbot-apache==0.14.1 \ - --hash=sha256:1dda9b4dcf66f6dfba37c787d849e69ad25a344572f74a76fc4447bb1a5417b2 \ - --hash=sha256:da84996e345fc5789da3575225536b27fa3b35f89b2db2d8f494a34bced14f9b -certbot-nginx==0.14.1 \ - --hash=sha256:bd3d4a1dcd6fa9e8ead19a9da88693f08b63464c86be2442e42cd60565c3f05f \ - --hash=sha256:f0c19f667072e4cfa6b92abf8312b6bee3ed1d2432676b211593034e7d1abb7e +acme==0.14.2 \ + --hash=sha256:b3068d360beccd3b23a81d7cd2522437d847328811b573a5fe14eb04147667cf \ + --hash=sha256:166b7f4858f5b144b03236b995b787a9da1e410121fb7dcac9c7d3b594bc6fcd +certbot==0.14.2 \ + --hash=sha256:525e15e43c833db9a9934308d69dcdd220fa799488cd84543748671c68aba73d \ + --hash=sha256:5bc8547dcfc0fc587e15253e264f79d8397e48bfbc8697d5aca87eae978769ac +certbot-apache==0.14.2 \ + --hash=sha256:15647d424a5a7e4c44c684324ac07a457a2e0d61fce1acaa421c0b641941a350 \ + --hash=sha256:e5220d3e6ee5114b41b398110dfbd8f13bd1e8c7902758634449e0b4ae515b76 +certbot-nginx==0.14.2 \ + --hash=sha256:231377fbdfb6303adddc73fe3f856b9fb6d0175db825650e39fe3dfd6a58f8ef \ + --hash=sha256:529a18280acf41f5f7e0fe7d82c0a5d4d197d14f82742eaec54bb1d3f69c325a UNLIKELY_EOF # ------------------------------------------------------------------------- diff --git a/letsencrypt-auto-source/certbot-auto.asc b/letsencrypt-auto-source/certbot-auto.asc index cdc9ef58e..2650e5922 100644 --- a/letsencrypt-auto-source/certbot-auto.asc +++ b/letsencrypt-auto-source/certbot-auto.asc @@ -1,11 +1,11 @@ -----BEGIN PGP SIGNATURE----- Version: GnuPG v2 -iQEcBAABCAAGBQJZGzDgAAoJEE0XyZXNl3XyBXYIAIYBMJKzAbLYsHrP/KF3aLLh -S9AWK5IP/tftHWgxS0mQ0JqQvWsRLGoQo7xaeKKIBD8QQsHA9hsdxPwy++rQcaZY -AzvpUBPIfiCDCa1XPiRy7YduAvsAoPB7jncP8rYdoFZL3lcUpbmI/9Sk1nlsm81n -5EcNJ9T8RRAkkH0i6DTLine48DgI7MlLhce/mAr3wDrcKAmENZksZW7vgAlI69ri -cTb+qIlwgFRLAF0Q41klTiFdHi6+vj+mFHHNFyuERpf7VT3ngBZmAmiRybxo/m8g -p9/54LGw3bQ25uAZXKVtIX5CqOoJL1GHe13MEyDOgBSDp+KqNGWJ8PEPA9XGwqw= -=H8UX +iQEcBAABCAAGBQJZJ0tAAAoJEE0XyZXNl3Xyta0H/3+UZ1xeCc7CjZBMEMjb6IPm +h0KhptkLfwRR0/vGhTeIaOi8rzZYPuzZVwRvTuJ30oORI/zP+siGTOVW4Rt/3KI0 +IZidCJkdl3259jtJpSR9dWOXVp8bklZin8k6daQjbizq8Hl6z0aFLbHlqeSAZhUX +ush94CQwB380OUBut+g3CYx4BxD0dgTODPIaVYzeG8lOX5SXAaBbH79BOAtCr9Hy +sRfYjcBo4aL3rPCayPn+ETvQsYYo/Z7zqHjfShiKzZXNtW+RBGXAf8CGoEk7LKM4 +jts6PxOpg2BFpArDKHn6JIWsHOphBAQ/qIIgvD1mKZj6P4hGBJv4+aZ3Q8uhHeg= +=56Pv -----END PGP SIGNATURE----- diff --git a/letsencrypt-auto-source/letsencrypt-auto.sig b/letsencrypt-auto-source/letsencrypt-auto.sig index 9a95dea5712d616c76d5dd2a7432d0770ece9ebc..032f73ca066d20d39221f5e6aa58a2a9af02013c 100644 GIT binary patch literal 256 zcmV+b0ssC3E+vn?mXmvQ;5y9R zngdi#j#+9!W!c9muzY3QGzq(M<pp)vDskpuW|BX~;y4AJ`owMj zp&uQ_Zqo>Zd@?lze5w~4sDB}sl=(yzO9%WbyUB6ZEGjh!Kx*ZA&z`8}@JvHBAj0`~ z;mqv$zd`NgW|%NEI##Y~jd*~VH-;F5ptvUQf1Q-nM|{>(HB>?Nr`h^!P7=8H5B_yM zub@0D>Zqz<*5ZOWuFr9F88LPakTT8F2mo`N4^pfU3T)s)hJY#AT(P%8$aABWuJ|{@ GlUx15cYc@v literal 256 zcmV+b0ssDF#^-kRK?i;CYYk)E?B%4(9BZFNY9;)9>rZSC*MZ);2R0K2OtVVt%UXxg z{f*^*IipLXZ${39P3|d*qJH_aQkL@Al?@2hxEUD|Ax%ocn5?qvtb4+xO2{iR0?nfd zZd-=i>=AnX^nGt#?d1#NFYpOIlfk?p(JuoL`(z zU9jP=Wbkoa1)-+@OW7w$v!*~FBW%s!syPeLcRD;_*sG9?u~k2aPo0VJpo#e-Jn{AS zie(k@SgJ_j(a`#|1bX$2g$54+A;%0tX&&(jbV&P)Q6k%yKL%eTHq9#Why;pY5bgwI GZYFf1+knyl From ed87b86bcaeb9a5934530f8f0ffe153c26a5e887 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Fri, 26 May 2017 10:21:57 -0700 Subject: [PATCH 5/9] Update CHANGELOG.md in response to 0.14.2 release (#4744) --- CHANGELOG.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f66582d9..d52518195 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,21 @@ Certbot adheres to [Semantic Versioning](http://semver.org/). +## 0.14.2 - 2017-05-25 + +### Fixed + +* Certbot 0.14.0 included a bug where Certbot would create a temporary log file +(usually in /tmp) if the program exited during argument parsing. If a user +provided -h/--help/help, --version, or an invalid command line argument, +Certbot would create this temporary log file. This was especially bothersome to +certbot-auto users as certbot-auto runs `certbot --version` internally to see +if the script needs to upgrade causing it to create at least one of these files +on every run. This problem has been resolved. + +More details about this change can be found on our GitHub repo: +https://github.com/certbot/certbot/issues?q=is%3Aissue+milestone%3A0.14.2+is%3Aclosed + ## 0.14.1 - 2017-05-16 ### Fixed From c827c9ec5ffaed884d57999dcdde0839becce0c2 Mon Sep 17 00:00:00 2001 From: Zach Shepherd Date: Fri, 26 May 2017 11:24:38 -0700 Subject: [PATCH 6/9] NS1 DNS Authenticator (#4602) Implement an Authenticator which can fulfill a dns-01 challenge using the NS1 DNS API. Applicable only for domains using NS1 DNS. Testing Done: * `tox -e py27` * `tox -e lint` * Manual testing: * Used `certbot certonly --dns-nsone -d`, specifying a credentials file as a command line argument. Verified that a certificate was successfully obtained without user interaction. * Used `certbot certonly --dns-nsone -d`, without specifying a credentials file as a command line argument. Verified that the user was prompted and that a certificate was successfully obtained. * Used `certbot certonly -d`. Verified that the user was prompted for a credentials file after selecting dnsimple interactively and that a certificate was successfully obtained. * Used `certbot renew --force-renewal`. Verified that certificates were renewed without user interaction. * Negative testing: * Path to non-existent credentials file. * Credentials file with unsafe permissions (644). * Path to credentials file with an invalid token. * Path to credentials file without a token. * Domain name not registered to NS1 account. --- certbot-dns-nsone/LICENSE.txt | 190 ++++++++++++++++++ certbot-dns-nsone/MANIFEST.in | 3 + certbot-dns-nsone/README.rst | 1 + .../certbot_dns_nsone/__init__.py | 1 + .../certbot_dns_nsone/dns_nsone.py | 83 ++++++++ .../certbot_dns_nsone/dns_nsone_test.py | 52 +++++ certbot-dns-nsone/docs/.gitignore | 1 + certbot-dns-nsone/docs/Makefile | 20 ++ certbot-dns-nsone/docs/api.rst | 8 + certbot-dns-nsone/docs/api/dns_nsone.rst | 5 + certbot-dns-nsone/docs/conf.py | 180 +++++++++++++++++ certbot-dns-nsone/docs/index.rst | 28 +++ certbot-dns-nsone/docs/make.bat | 36 ++++ certbot-dns-nsone/setup.cfg | 2 + certbot-dns-nsone/setup.py | 68 +++++++ certbot/cli.py | 2 + certbot/plugins/disco.py | 1 + certbot/plugins/selection.py | 4 +- tools/venv.sh | 1 + tools/venv3.sh | 1 + tox.cover.sh | 4 +- tox.ini | 6 +- 22 files changed, 693 insertions(+), 4 deletions(-) create mode 100644 certbot-dns-nsone/LICENSE.txt create mode 100644 certbot-dns-nsone/MANIFEST.in create mode 100644 certbot-dns-nsone/README.rst create mode 100644 certbot-dns-nsone/certbot_dns_nsone/__init__.py create mode 100644 certbot-dns-nsone/certbot_dns_nsone/dns_nsone.py create mode 100644 certbot-dns-nsone/certbot_dns_nsone/dns_nsone_test.py create mode 100644 certbot-dns-nsone/docs/.gitignore create mode 100644 certbot-dns-nsone/docs/Makefile create mode 100644 certbot-dns-nsone/docs/api.rst create mode 100644 certbot-dns-nsone/docs/api/dns_nsone.rst create mode 100644 certbot-dns-nsone/docs/conf.py create mode 100644 certbot-dns-nsone/docs/index.rst create mode 100644 certbot-dns-nsone/docs/make.bat create mode 100644 certbot-dns-nsone/setup.cfg create mode 100644 certbot-dns-nsone/setup.py diff --git a/certbot-dns-nsone/LICENSE.txt b/certbot-dns-nsone/LICENSE.txt new file mode 100644 index 000000000..981c46c9f --- /dev/null +++ b/certbot-dns-nsone/LICENSE.txt @@ -0,0 +1,190 @@ + Copyright 2015 Electronic Frontier Foundation and others + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/certbot-dns-nsone/MANIFEST.in b/certbot-dns-nsone/MANIFEST.in new file mode 100644 index 000000000..18f018c08 --- /dev/null +++ b/certbot-dns-nsone/MANIFEST.in @@ -0,0 +1,3 @@ +include LICENSE.txt +include README.rst +recursive-include docs * diff --git a/certbot-dns-nsone/README.rst b/certbot-dns-nsone/README.rst new file mode 100644 index 000000000..a1702751d --- /dev/null +++ b/certbot-dns-nsone/README.rst @@ -0,0 +1 @@ +NS1 DNS Authenticator plugin for Certbot diff --git a/certbot-dns-nsone/certbot_dns_nsone/__init__.py b/certbot-dns-nsone/certbot_dns_nsone/__init__.py new file mode 100644 index 000000000..8c061edf7 --- /dev/null +++ b/certbot-dns-nsone/certbot_dns_nsone/__init__.py @@ -0,0 +1 @@ +"""NS1 DNS Authenticator""" diff --git a/certbot-dns-nsone/certbot_dns_nsone/dns_nsone.py b/certbot-dns-nsone/certbot_dns_nsone/dns_nsone.py new file mode 100644 index 000000000..be60ff39d --- /dev/null +++ b/certbot-dns-nsone/certbot_dns_nsone/dns_nsone.py @@ -0,0 +1,83 @@ +"""DNS Authenticator for NS1 DNS.""" +import logging + +import zope.interface +from lexicon.providers import nsone + +from certbot import errors +from certbot import interfaces +from certbot.plugins import dns_common +from certbot.plugins import dns_common_lexicon + +logger = logging.getLogger(__name__) + +ACCOUNT_URL = 'https://my.nsone.net/#/account/settings' + + +@zope.interface.implementer(interfaces.IAuthenticator) +@zope.interface.provider(interfaces.IPluginFactory) +class Authenticator(dns_common.DNSAuthenticator): + """DNS Authenticator for NS1 + + This Authenticator uses the NS1 API to fulfill a dns-01 challenge. + """ + + description = 'Obtain certs using a DNS TXT record (if you are using NS1 for DNS).' + ttl = 60 + + def __init__(self, *args, **kwargs): + super(Authenticator, self).__init__(*args, **kwargs) + self.credentials = None + + @classmethod + def add_parser_arguments(cls, add): # pylint: disable=arguments-differ + super(Authenticator, cls).add_parser_arguments(add, default_propagation_seconds=30) + add('credentials', help='NS1 credentials file.') + + def more_info(self): # pylint: disable=missing-docstring,no-self-use + return 'This plugin configures a DNS TXT record to respond to a dns-01 challenge using ' + \ + 'the NS1 API.' + + def _setup_credentials(self): + self.credentials = self._configure_credentials( + 'credentials', + 'NS1 credentials file', + { + 'api-key': 'API key for NS1 API, obtained from {0}'.format(ACCOUNT_URL) + } + ) + + def _perform(self, domain, validation_name, validation): + self._get_nsone_client().add_txt_record(domain, validation_name, validation) + + def _cleanup(self, domain, validation_name, validation): + self._get_nsone_client().del_txt_record(domain, validation_name, validation) + + def _get_nsone_client(self): + return _NS1LexiconClient(self.credentials.conf('api-key'), self.ttl) + + +class _NS1LexiconClient(dns_common_lexicon.LexiconClient): + """ + Encapsulates all communication with the NS1 via Lexicon. + """ + + def __init__(self, api_key, ttl): + super(_NS1LexiconClient, self).__init__() + + self.provider = nsone.Provider({ + 'auth_token': api_key, + 'ttl': ttl, + }) + + def _handle_http_error(self, e, domain_name): + if domain_name in str(e) and (str(e).startswith('404 Client Error: Not Found for url:') or \ + str(e).startswith("400 Client Error: Bad Request for url:")): + return # Expected errors when zone name guess is wrong + else: + hint = None + if str(e).startswith('401 Client Error: Unauthorized for url:'): + hint = 'Is your API key correct?' + + return errors.PluginError('Error determining zone identifier: {0}.{1}' + .format(e, ' ({0})'.format(hint) if hint else '')) diff --git a/certbot-dns-nsone/certbot_dns_nsone/dns_nsone_test.py b/certbot-dns-nsone/certbot_dns_nsone/dns_nsone_test.py new file mode 100644 index 000000000..56668dd01 --- /dev/null +++ b/certbot-dns-nsone/certbot_dns_nsone/dns_nsone_test.py @@ -0,0 +1,52 @@ +"""Tests for certbot_dns_nsone.dns_nsone.""" + +import os +import unittest + +import mock +from requests.exceptions import HTTPError + +from certbot.plugins import dns_test_common +from certbot.plugins import dns_test_common_lexicon +from certbot.plugins.dns_test_common import DOMAIN +from certbot.tests import util as test_util + +API_KEY = 'foo' + + +class AuthenticatorTest(test_util.TempDirTestCase, + dns_test_common_lexicon.BaseLexiconAuthenticatorTest): + + def setUp(self): + super(AuthenticatorTest, self).setUp() + + from certbot_dns_nsone.dns_nsone import Authenticator + + path = os.path.join(self.tempdir, 'file.ini') + dns_test_common.write({"nsone_api_key": API_KEY}, path) + + self.config = mock.MagicMock(nsone_credentials=path, + nsone_propagation_seconds=0) # don't wait during tests + + self.auth = Authenticator(self.config, "nsone") + + self.mock_client = mock.MagicMock() + # _get_nsone_client | pylint: disable=protected-access + self.auth._get_nsone_client = mock.MagicMock(return_value=self.mock_client) + + +class NS1LexiconClientTest(unittest.TestCase, dns_test_common_lexicon.BaseLexiconClientTest): + DOMAIN_NOT_FOUND = HTTPError('404 Client Error: Not Found for url: {0}.'.format(DOMAIN)) + LOGIN_ERROR = HTTPError('401 Client Error: Unauthorized for url: {0}.'.format(DOMAIN)) + + def setUp(self): + from certbot_dns_nsone.dns_nsone import _NS1LexiconClient + + self.client = _NS1LexiconClient(API_KEY, 0) + + self.provider_mock = mock.MagicMock() + self.client.provider = self.provider_mock + + +if __name__ == "__main__": + unittest.main() # pragma: no cover diff --git a/certbot-dns-nsone/docs/.gitignore b/certbot-dns-nsone/docs/.gitignore new file mode 100644 index 000000000..ba65b13af --- /dev/null +++ b/certbot-dns-nsone/docs/.gitignore @@ -0,0 +1 @@ +/_build/ diff --git a/certbot-dns-nsone/docs/Makefile b/certbot-dns-nsone/docs/Makefile new file mode 100644 index 000000000..81a75ed04 --- /dev/null +++ b/certbot-dns-nsone/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +SPHINXPROJ = certbot-dns-nsone +SOURCEDIR = . +BUILDDIR = _build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) \ No newline at end of file diff --git a/certbot-dns-nsone/docs/api.rst b/certbot-dns-nsone/docs/api.rst new file mode 100644 index 000000000..8668ec5d8 --- /dev/null +++ b/certbot-dns-nsone/docs/api.rst @@ -0,0 +1,8 @@ +================= +API Documentation +================= + +.. toctree:: + :glob: + + api/** diff --git a/certbot-dns-nsone/docs/api/dns_nsone.rst b/certbot-dns-nsone/docs/api/dns_nsone.rst new file mode 100644 index 000000000..788ce732a --- /dev/null +++ b/certbot-dns-nsone/docs/api/dns_nsone.rst @@ -0,0 +1,5 @@ +:mod:`certbot_dns_nsone.dns_nsone` +---------------------------------- + +.. automodule:: certbot_dns_nsone.dns_nsone + :members: diff --git a/certbot-dns-nsone/docs/conf.py b/certbot-dns-nsone/docs/conf.py new file mode 100644 index 000000000..cffe2a25c --- /dev/null +++ b/certbot-dns-nsone/docs/conf.py @@ -0,0 +1,180 @@ +# -*- coding: utf-8 -*- +# +# certbot-dns-nsone documentation build configuration file, created by +# sphinx-quickstart on Wed May 10 18:30:40 2017. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +import os +# import sys +# sys.path.insert(0, os.path.abspath('.')) + + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +# +needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = ['sphinx.ext.autodoc', + 'sphinx.ext.intersphinx', + 'sphinx.ext.todo', + 'sphinx.ext.coverage', + 'sphinx.ext.viewcode'] + +autodoc_member_order = 'bysource' +autodoc_default_flags = ['show-inheritance', 'private-members'] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# +# source_suffix = ['.rst', '.md'] +source_suffix = '.rst' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'certbot-dns-nsone' +copyright = u'2017, Certbot Project' +author = u'Certbot Project' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = u'0' +# The full version, including alpha/beta/rc tags. +release = u'0' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = 'en' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This patterns also effect to html_static_path and html_extra_path +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] + +default_role = 'py:obj' + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = True + + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# + +# http://docs.readthedocs.org/en/latest/theme.html#how-do-i-use-this-locally-and-on-read-the-docs +# on_rtd is whether we are on readthedocs.org +on_rtd = os.environ.get('READTHEDOCS', None) == 'True' +if not on_rtd: # only import and set the theme if we're building docs locally + import sphinx_rtd_theme + html_theme = 'sphinx_rtd_theme' + html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] +# otherwise, readthedocs.org uses their theme by default, so no need to specify it + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +# +# html_theme_options = {} + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + + +# -- Options for HTMLHelp output ------------------------------------------ + +# Output file base name for HTML help builder. +htmlhelp_basename = 'certbot-dns-nsonedoc' + + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + # + # 'papersize': 'letterpaper', + + # The font size ('10pt', '11pt' or '12pt'). + # + # 'pointsize': '10pt', + + # Additional stuff for the LaTeX preamble. + # + # 'preamble': '', + + # Latex figure (float) alignment + # + # 'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (master_doc, 'certbot-dns-nsone.tex', u'certbot-dns-nsone Documentation', + u'Certbot Project', 'manual'), +] + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + (master_doc, 'certbot-dns-nsone', u'certbot-dns-nsone Documentation', + [author], 1) +] + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + (master_doc, 'certbot-dns-nsone', u'certbot-dns-nsone Documentation', + author, 'certbot-dns-nsone', 'One line description of project.', + 'Miscellaneous'), +] + + + + +# Example configuration for intersphinx: refer to the Python standard library. +intersphinx_mapping = { + 'python': ('https://docs.python.org/', None), + 'acme': ('https://acme-python.readthedocs.org/en/latest/', None), + 'certbot': ('https://certbot.eff.org/docs/', None), +} diff --git a/certbot-dns-nsone/docs/index.rst b/certbot-dns-nsone/docs/index.rst new file mode 100644 index 000000000..6abba81ec --- /dev/null +++ b/certbot-dns-nsone/docs/index.rst @@ -0,0 +1,28 @@ +.. certbot-dns-nsone documentation master file, created by + sphinx-quickstart on Wed May 10 18:30:40 2017. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to certbot-dns-nsone's documentation! +============================================= + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + +.. toctree:: + :maxdepth: 1 + + api + +.. automodule:: certbot_dns_nsone + :members: + + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/certbot-dns-nsone/docs/make.bat b/certbot-dns-nsone/docs/make.bat new file mode 100644 index 000000000..0d19eff5d --- /dev/null +++ b/certbot-dns-nsone/docs/make.bat @@ -0,0 +1,36 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=. +set BUILDDIR=_build +set SPHINXPROJ=certbot-dns-nsone + +if "%1" == "" goto help + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% + +:end +popd diff --git a/certbot-dns-nsone/setup.cfg b/certbot-dns-nsone/setup.cfg new file mode 100644 index 000000000..2a9acf13d --- /dev/null +++ b/certbot-dns-nsone/setup.cfg @@ -0,0 +1,2 @@ +[bdist_wheel] +universal = 1 diff --git a/certbot-dns-nsone/setup.py b/certbot-dns-nsone/setup.py new file mode 100644 index 000000000..658961bd8 --- /dev/null +++ b/certbot-dns-nsone/setup.py @@ -0,0 +1,68 @@ +import sys + +from setuptools import setup +from setuptools import find_packages + + +version = '0.15.0.dev0' + +# Please update tox.ini when modifying dependency version requirements +install_requires = [ + 'acme=={0}'.format(version), + 'certbot=={0}'.format(version), + 'dns-lexicon', + 'mock', + # For pkg_resources. >=1.0 so pip resolves it to a version cryptography + # will tolerate; see #2599: + 'setuptools>=1.0', + 'zope.interface', +] + +docs_extras = [ + 'Sphinx>=1.0', # autodoc_member_order = 'bysource', autodoc_default_flags + 'sphinx_rtd_theme', +] + +setup( + name='certbot-dns-nsone', + version=version, + description="NS1 DNS Authenticator plugin for Certbot", + url='https://github.com/certbot/certbot', + author="Certbot Project", + author_email='client-dev@letsencrypt.org', + license='Apache License 2.0', + classifiers=[ + 'Development Status :: 3 - Alpha', + 'Environment :: Plugins', + 'Intended Audience :: System Administrators', + 'License :: OSI Approved :: Apache Software License', + 'Operating System :: POSIX :: Linux', + 'Programming Language :: Python', + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + 'Topic :: Internet :: WWW/HTTP', + 'Topic :: Security', + 'Topic :: System :: Installation/Setup', + 'Topic :: System :: Networking', + 'Topic :: System :: Systems Administration', + 'Topic :: Utilities', + ], + + packages=find_packages(), + include_package_data=True, + install_requires=install_requires, + extras_require={ + 'docs': docs_extras, + }, + entry_points={ + 'certbot.plugins': [ + 'dns-nsone = certbot_dns_nsone.dns_nsone:Authenticator', + ], + }, + test_suite='certbot_dns_nsone', +) diff --git a/certbot/cli.py b/certbot/cli.py index ac3193773..3e84553d3 100644 --- a/certbot/cli.py +++ b/certbot/cli.py @@ -1233,6 +1233,8 @@ def _plugins_parsing(helpful, plugins): help='Obtain certs using a DNS TXT record (if you are using DNSimple for DNS).') helpful.add(["plugins", "certonly"], "--dns-google", action="store_true", help='Obtain certs using a DNS TXT record (if you are using Google Cloud DNS).') + helpful.add(["plugins", "certonly"], "--dns-nsone", action="store_true", + help='Obtain certs using a DNS TXT record (if you are using NS1 for DNS).') # things should not be reorder past/pre this comment: # plugins_group should be displayed in --help before plugin diff --git a/certbot/plugins/disco.py b/certbot/plugins/disco.py index 5347ab050..af577564c 100644 --- a/certbot/plugins/disco.py +++ b/certbot/plugins/disco.py @@ -33,6 +33,7 @@ class PluginEntryPoint(object): "certbot-dns-digitalocean", "certbot-dns-dnsimple", "certbot-dns-google", + "certbot-dns-nsone", "certbot-nginx", ] """Distributions for which prefix will be omitted.""" diff --git a/certbot/plugins/selection.py b/certbot/plugins/selection.py index 89fa0ab7b..eb41d5b93 100644 --- a/certbot/plugins/selection.py +++ b/certbot/plugins/selection.py @@ -134,7 +134,7 @@ def choose_plugin(prepared, question): return None noninstaller_plugins = ["webroot", "manual", "standalone", "dns-cloudflare", "dns-cloudxns", - "dns-digitalocean", "dns-dnsimple", "dns-google"] + "dns-digitalocean", "dns-dnsimple", "dns-google", "dns-nsone"] def record_chosen_plugins(config, plugins, auth, inst): "Update the config entries to reflect the plugins we actually selected." @@ -248,6 +248,8 @@ def cli_plugin_requests(config): req_auth = set_configurator(req_auth, "dns-dnsimple") if config.dns_google: req_auth = set_configurator(req_auth, "dns-google") + if config.dns_nsone: + req_auth = set_configurator(req_auth, "dns-nsone") logger.debug("Requested authenticator %s and installer %s", req_auth, req_inst) return req_auth, req_inst diff --git a/tools/venv.sh b/tools/venv.sh index 2a59737a7..a8332cc6e 100755 --- a/tools/venv.sh +++ b/tools/venv.sh @@ -19,6 +19,7 @@ fi -e certbot-dns-digitalocean \ -e certbot-dns-dnsimple \ -e certbot-dns-google \ + -e certbot-dns-nsone \ -e certbot-nginx \ -e letshelp-certbot \ -e certbot-compatibility-test diff --git a/tools/venv3.sh b/tools/venv3.sh index a0c98126e..4d5a07f21 100755 --- a/tools/venv3.sh +++ b/tools/venv3.sh @@ -18,6 +18,7 @@ fi -e certbot-dns-digitalocean \ -e certbot-dns-dnsimple \ -e certbot-dns-google \ + -e certbot-dns-nsone \ -e certbot-nginx \ -e letshelp-certbot \ -e certbot-compatibility-test diff --git a/tox.cover.sh b/tox.cover.sh index f7064f918..51425faf2 100755 --- a/tox.cover.sh +++ b/tox.cover.sh @@ -9,7 +9,7 @@ # -e makes sure we fail fast and don't submit coveralls submit if [ "xxx$1" = "xxx" ]; then - pkgs="certbot acme certbot_apache certbot_dns_cloudflare certbot_dns_cloudxns certbot_dns_digitalocean certbot_dns_dnsimple certbot_dns_google certbot_nginx letshelp_certbot" + pkgs="certbot acme certbot_apache certbot_dns_cloudflare certbot_dns_cloudxns certbot_dns_digitalocean certbot_dns_dnsimple certbot_dns_google certbot_dns_nsone certbot_nginx letshelp_certbot" else pkgs="$@" fi @@ -31,6 +31,8 @@ cover () { min=98 elif [ "$1" = "certbot_dns_google" ]; then min=99 + elif [ "$1" = "certbot_dns_nsone" ]; then + min=99 elif [ "$1" = "certbot_nginx" ]; then min=97 elif [ "$1" = "letshelp_certbot" ]; then diff --git a/tox.ini b/tox.ini index 94d6048c3..75b6ce2f2 100644 --- a/tox.ini +++ b/tox.ini @@ -45,8 +45,10 @@ lexicon_dns_plugin_commands = nosetests -v certbot_dns_cloudxns --processes=-1 pip install -e certbot-dns-dnsimple nosetests -v certbot_dns_dnsimple --processes=-1 -lexicon_dns_plugin_install_args = -e certbot-dns-cloudxns -e certbot-dns-dnsimple -lexicon_dns_plugin_paths = certbot-dns-cloudxns/certbot_dns_cloudxns certbot-dns-dnsimple/certbot_dns_dnsimple + pip install -e certbot-dns-nsone + nosetests -v certbot_dns_nsone --processes=-1 +lexicon_dns_plugin_install_args = -e certbot-dns-cloudxns -e certbot-dns-dnsimple -e certbot-dns-nsone +lexicon_dns_plugin_paths = certbot-dns-cloudxns/certbot_dns_cloudxns certbot-dns-dnsimple/certbot_dns_dnsimple certbot-dns-nsone/certbot_dns_nsone compatibility_install_args = -e certbot-compatibility-test compatibility_paths = certbot-compatibility-test/certbot_compatibility_test From 4146685104416cfa9181d049627c7d816c88eb51 Mon Sep 17 00:00:00 2001 From: Zach Shepherd Date: Fri, 26 May 2017 11:28:55 -0700 Subject: [PATCH 7/9] route53: tweak source organization to match other packages (#4729) This change re-organizes some ancillary files to more closely match repository conventions. --- certbot-route53/README.md | 2 +- certbot-route53/{ => examples}/sample-aws-policy.json | 0 certbot-route53/{ => tools}/tester.pkoch-macos_sierra.sh | 0 3 files changed, 1 insertion(+), 1 deletion(-) rename certbot-route53/{ => examples}/sample-aws-policy.json (100%) rename certbot-route53/{ => tools}/tester.pkoch-macos_sierra.sh (100%) diff --git a/certbot-route53/README.md b/certbot-route53/README.md index cec9c295c..582a0fb35 100644 --- a/certbot-route53/README.md +++ b/certbot-route53/README.md @@ -24,7 +24,7 @@ for example). Make sure you have access to AWS's Route53 service, either through IAM roles or via `.aws/credentials`. Check out -[sample-aws-policy.json](sample-aws-policy.json) for the necessary permissions. +[sample-aws-policy.json](examples/sample-aws-policy.json) for the necessary permissions. To generate a certificate: ``` diff --git a/certbot-route53/sample-aws-policy.json b/certbot-route53/examples/sample-aws-policy.json similarity index 100% rename from certbot-route53/sample-aws-policy.json rename to certbot-route53/examples/sample-aws-policy.json diff --git a/certbot-route53/tester.pkoch-macos_sierra.sh b/certbot-route53/tools/tester.pkoch-macos_sierra.sh similarity index 100% rename from certbot-route53/tester.pkoch-macos_sierra.sh rename to certbot-route53/tools/tester.pkoch-macos_sierra.sh From 4a0c33648441ee07d02506ce95ec84e5fb87e9cc Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Fri, 26 May 2017 14:41:59 -0700 Subject: [PATCH 8/9] modification-check.sh now fails if a command fails (#4746) --- tests/modification-check.sh | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/modification-check.sh b/tests/modification-check.sh index 6f412ba47..0145b0228 100755 --- a/tests/modification-check.sh +++ b/tests/modification-check.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/bin/bash -e temp_dir=`mktemp -d` trap "rm -rf $temp_dir" EXIT @@ -43,9 +43,7 @@ cp ${temp_dir}/original-lea letsencrypt-auto-source/letsencrypt-auto cd $temp_dir -cmp -s original-lea build-lea - -if [ $? != 0 ]; then +if ! cmp -s original-lea build-lea; then echo "letsencrypt-auto-source/letsencrypt-auto doesn't match output of \ build.py." FLAG=true From 6048bfa87b5d849678cc6f87c344d0a0efc484a6 Mon Sep 17 00:00:00 2001 From: Zach Shepherd Date: Fri, 26 May 2017 14:44:05 -0700 Subject: [PATCH 9/9] route53: update setup.py to follow repo conventions (#4731) This change updates the setup script for the route53 plugin to more closely match conventions from other packages in the repository. Notable changes: * The version number is bumped to match the rest of Certbot. * The package now requires a matching version of ACME and core Certbot. * Contact information is updated. * Additional versions of Python are listed. --- certbot-route53/setup.py | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/certbot-route53/setup.py b/certbot-route53/setup.py index 49b1ea467..40a104a40 100644 --- a/certbot-route53/setup.py +++ b/certbot-route53/setup.py @@ -3,23 +3,27 @@ import sys from distutils.core import setup from setuptools import find_packages -version = '0.1.5' +version = '0.15.0.dev0' install_requires = [ - 'acme>=0.9.0', - 'certbot>=0.9.0', - 'zope.interface', + 'acme=={0}'.format(version), + 'certbot=={0}'.format(version), 'boto3', + 'mock', + # For pkg_resources. >=1.0 so pip resolves it to a version cryptography + # will tolerate; see #2599: + 'setuptools>=1.0', + 'zope.interface', ] setup( name='certbot-route53', version=version, - description="Route53 plugin for certbot", - url='https://github.com/lifeonmarspt/certbot-route53', - author="Hugo Peixoto", - author_email='hugo@lifeonmars.pt', - license='Apache2.0', + description="Route53 DNS Authenticator plugin for Certbot", + url='https://github.com/certbot/certbot', + author="Certbot Project", + author_email='client-dev@letsencrypt.org', + license='Apache License 2.0', classifiers=[ 'Development Status :: 3 - Alpha', 'Environment :: Plugins', @@ -28,7 +32,13 @@ setup( 'Operating System :: POSIX :: Linux', 'Programming Language :: Python', 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', 'Topic :: Internet :: WWW/HTTP', 'Topic :: Security', 'Topic :: System :: Installation/Setup', @@ -37,6 +47,7 @@ setup( 'Topic :: Utilities', ], packages=find_packages(), + include_package_data=True, install_requires=install_requires, keywords=['certbot', 'route53', 'aws'], entry_points={ @@ -44,4 +55,5 @@ setup( 'auth = certbot_route53.authenticator:Authenticator' ], }, + test_suite='certbot_route53', )