launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #23900
[Merge] lp:~cjwatson/launchpad/generate-key-pair-script into lp:launchpad
Colin Watson has proposed merging lp:~cjwatson/launchpad/generate-key-pair-script into lp:launchpad.
Commit message:
Add a script to generate a NaCl key pair.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/generate-key-pair-script/+merge/371732
The instructions for generating config.snappy.store_secrets_private_key and config.snappy.store_secrets_public_key were getting a bit long, so I decided to put some of them in this script.
I used a plain entry point rather than the LaunchpadScript infrastructure, since this is very simple and doesn't need all of that machinery.
--
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~cjwatson/launchpad/generate-key-pair-script into lp:launchpad.
=== added directory 'lib/lp/services/crypto/scripts'
=== added file 'lib/lp/services/crypto/scripts/__init__.py'
=== added file 'lib/lp/services/crypto/scripts/generatekeypair.py'
--- lib/lp/services/crypto/scripts/generatekeypair.py 1970-01-01 00:00:00 +0000
+++ lib/lp/services/crypto/scripts/generatekeypair.py 2019-08-23 10:13:21 +0000
@@ -0,0 +1,34 @@
+# Copyright 2019 Canonical Ltd. This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""Generate a NaCl key pair.
+
+The resulting private and public keys are base64-encoded and can be stored
+in Launchpad configuration files. The private key should only be stored in
+secret overlays on systems that need it.
+"""
+
+from __future__ import absolute_import, print_function, unicode_literals
+
+__metaclass__ = type
+__all__ = ['main']
+
+import argparse
+import base64
+
+from nacl.public import PrivateKey
+
+
+def encode_key(key):
+ return base64.b64encode(key.encode()).decode('ASCII')
+
+
+def main():
+ parser = argparse.ArgumentParser(
+ description=__doc__,
+ formatter_class=argparse.RawDescriptionHelpFormatter)
+ parser.parse_args()
+
+ key = PrivateKey.generate()
+ print('Private: ' + encode_key(key))
+ print('Public: ' + encode_key(key.public_key))
=== added directory 'lib/lp/services/crypto/scripts/tests'
=== added file 'lib/lp/services/crypto/scripts/tests/__init__.py'
=== added file 'lib/lp/services/crypto/scripts/tests/test_generatekeypair.py'
--- lib/lp/services/crypto/scripts/tests/test_generatekeypair.py 1970-01-01 00:00:00 +0000
+++ lib/lp/services/crypto/scripts/tests/test_generatekeypair.py 2019-08-23 10:13:21 +0000
@@ -0,0 +1,68 @@
+# Copyright 2019 Canonical Ltd. This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""Test the script to generate a NaCl key pair."""
+
+from __future__ import absolute_import, print_function, unicode_literals
+
+__metaclass__ = type
+
+import base64
+
+from fixtures import MockPatch
+from nacl.public import (
+ PrivateKey,
+ PublicKey,
+ )
+from testtools.content import text_content
+from testtools.matchers import (
+ MatchesListwise,
+ StartsWith,
+ )
+
+from lp.services.crypto.scripts.generatekeypair import main as gkp_main
+from lp.services.utils import CapturedOutput
+from lp.testing import TestCase
+
+
+def decode_key(factory, data):
+ return factory(base64.b64decode(data.encode('ASCII')))
+
+
+class TestGenerateKeyPair(TestCase):
+
+ def runScript(self, args, expect_exit=False):
+ try:
+ with MockPatch('sys.argv', ['version-info'] + args):
+ with CapturedOutput() as captured:
+ gkp_main()
+ except SystemExit:
+ exited = True
+ else:
+ exited = False
+ stdout = captured.stdout.getvalue()
+ stderr = captured.stderr.getvalue()
+ self.addDetail('stdout', text_content(stdout))
+ self.addDetail('stderr', text_content(stderr))
+ if expect_exit:
+ if not exited:
+ raise AssertionError('Script unexpectedly exited successfully')
+ else:
+ if exited:
+ raise AssertionError(
+ 'Script unexpectedly exited unsuccessfully')
+ self.assertEqual('', stderr)
+ return stdout
+
+ def test_bad_arguments(self):
+ self.runScript(['--nonsense'], expect_exit=True)
+
+ def test_generates_key_pair(self):
+ lines = self.runScript([]).splitlines()
+ self.assertThat(lines, MatchesListwise([
+ StartsWith('Private: '),
+ StartsWith('Public: '),
+ ]))
+ private_key = decode_key(PrivateKey, lines[0][len('Private: '):])
+ public_key = decode_key(PublicKey, lines[1][len('Public: '):])
+ self.assertEqual(public_key, private_key.public_key)
=== modified file 'setup.py'
--- setup.py 2019-08-22 14:23:29 +0000
+++ setup.py 2019-08-23 10:13:21 +0000
@@ -297,6 +297,8 @@
'build-twisted-plugin-cache = '
'lp.services.twistedsupport.plugincache:main',
'combine-css = lp.scripts.utilities.js.combinecss:main',
+ 'generate-key-pair = '
+ 'lp.services.crypto.scripts.generatekeypair:main',
'harness = lp.scripts.harness:python',
'iharness = lp.scripts.harness:ipython',
'ipy = IPython.frontend.terminal.ipapp:launch_new_instance',
Follow ups