Merge lp:~cjwatson/launchpad/generate-key-pair-script into lp:launchpad

Proposed by Colin Watson
Status: Merged
Merged at revision: 19039
Proposed branch: lp:~cjwatson/launchpad/generate-key-pair-script
Merge into: lp:launchpad
Diff against target: 128 lines (+104/-0)
3 files modified
lib/lp/services/crypto/scripts/generatekeypair.py (+34/-0)
lib/lp/services/crypto/scripts/tests/test_generatekeypair.py (+68/-0)
setup.py (+2/-0)
To merge this branch: bzr merge lp:~cjwatson/launchpad/generate-key-pair-script
Reviewer Review Type Date Requested Status
William Grant code Approve
Review via email: mp+371732@code.launchpad.net

Commit message

Add a script to generate a NaCl key pair.

Description of the change

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.

To post a comment you must log in.
Revision history for this message
William Grant (wgrant) :
review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added directory 'lib/lp/services/crypto/scripts'
2=== added file 'lib/lp/services/crypto/scripts/__init__.py'
3=== added file 'lib/lp/services/crypto/scripts/generatekeypair.py'
4--- lib/lp/services/crypto/scripts/generatekeypair.py 1970-01-01 00:00:00 +0000
5+++ lib/lp/services/crypto/scripts/generatekeypair.py 2019-08-23 10:13:21 +0000
6@@ -0,0 +1,34 @@
7+# Copyright 2019 Canonical Ltd. This software is licensed under the
8+# GNU Affero General Public License version 3 (see the file LICENSE).
9+
10+"""Generate a NaCl key pair.
11+
12+The resulting private and public keys are base64-encoded and can be stored
13+in Launchpad configuration files. The private key should only be stored in
14+secret overlays on systems that need it.
15+"""
16+
17+from __future__ import absolute_import, print_function, unicode_literals
18+
19+__metaclass__ = type
20+__all__ = ['main']
21+
22+import argparse
23+import base64
24+
25+from nacl.public import PrivateKey
26+
27+
28+def encode_key(key):
29+ return base64.b64encode(key.encode()).decode('ASCII')
30+
31+
32+def main():
33+ parser = argparse.ArgumentParser(
34+ description=__doc__,
35+ formatter_class=argparse.RawDescriptionHelpFormatter)
36+ parser.parse_args()
37+
38+ key = PrivateKey.generate()
39+ print('Private: ' + encode_key(key))
40+ print('Public: ' + encode_key(key.public_key))
41
42=== added directory 'lib/lp/services/crypto/scripts/tests'
43=== added file 'lib/lp/services/crypto/scripts/tests/__init__.py'
44=== added file 'lib/lp/services/crypto/scripts/tests/test_generatekeypair.py'
45--- lib/lp/services/crypto/scripts/tests/test_generatekeypair.py 1970-01-01 00:00:00 +0000
46+++ lib/lp/services/crypto/scripts/tests/test_generatekeypair.py 2019-08-23 10:13:21 +0000
47@@ -0,0 +1,68 @@
48+# Copyright 2019 Canonical Ltd. This software is licensed under the
49+# GNU Affero General Public License version 3 (see the file LICENSE).
50+
51+"""Test the script to generate a NaCl key pair."""
52+
53+from __future__ import absolute_import, print_function, unicode_literals
54+
55+__metaclass__ = type
56+
57+import base64
58+
59+from fixtures import MockPatch
60+from nacl.public import (
61+ PrivateKey,
62+ PublicKey,
63+ )
64+from testtools.content import text_content
65+from testtools.matchers import (
66+ MatchesListwise,
67+ StartsWith,
68+ )
69+
70+from lp.services.crypto.scripts.generatekeypair import main as gkp_main
71+from lp.services.utils import CapturedOutput
72+from lp.testing import TestCase
73+
74+
75+def decode_key(factory, data):
76+ return factory(base64.b64decode(data.encode('ASCII')))
77+
78+
79+class TestGenerateKeyPair(TestCase):
80+
81+ def runScript(self, args, expect_exit=False):
82+ try:
83+ with MockPatch('sys.argv', ['version-info'] + args):
84+ with CapturedOutput() as captured:
85+ gkp_main()
86+ except SystemExit:
87+ exited = True
88+ else:
89+ exited = False
90+ stdout = captured.stdout.getvalue()
91+ stderr = captured.stderr.getvalue()
92+ self.addDetail('stdout', text_content(stdout))
93+ self.addDetail('stderr', text_content(stderr))
94+ if expect_exit:
95+ if not exited:
96+ raise AssertionError('Script unexpectedly exited successfully')
97+ else:
98+ if exited:
99+ raise AssertionError(
100+ 'Script unexpectedly exited unsuccessfully')
101+ self.assertEqual('', stderr)
102+ return stdout
103+
104+ def test_bad_arguments(self):
105+ self.runScript(['--nonsense'], expect_exit=True)
106+
107+ def test_generates_key_pair(self):
108+ lines = self.runScript([]).splitlines()
109+ self.assertThat(lines, MatchesListwise([
110+ StartsWith('Private: '),
111+ StartsWith('Public: '),
112+ ]))
113+ private_key = decode_key(PrivateKey, lines[0][len('Private: '):])
114+ public_key = decode_key(PublicKey, lines[1][len('Public: '):])
115+ self.assertEqual(public_key, private_key.public_key)
116
117=== modified file 'setup.py'
118--- setup.py 2019-08-22 14:23:29 +0000
119+++ setup.py 2019-08-23 10:13:21 +0000
120@@ -297,6 +297,8 @@
121 'build-twisted-plugin-cache = '
122 'lp.services.twistedsupport.plugincache:main',
123 'combine-css = lp.scripts.utilities.js.combinecss:main',
124+ 'generate-key-pair = '
125+ 'lp.services.crypto.scripts.generatekeypair:main',
126 'harness = lp.scripts.harness:python',
127 'iharness = lp.scripts.harness:ipython',
128 'ipy = IPython.frontend.terminal.ipapp:launch_new_instance',