Merge lp:~oddbloke/charm-helpers/unison_patches into lp:charm-helpers

Proposed by Dan Watkins
Status: Merged
Merged at revision: 411
Proposed branch: lp:~oddbloke/charm-helpers/unison_patches
Merge into: lp:charm-helpers
Diff against target: 161 lines (+81/-9)
2 files modified
charmhelpers/contrib/unison/__init__.py (+23/-8)
tests/contrib/unison/test_unison.py (+58/-1)
To merge this branch: bzr merge lp:~oddbloke/charm-helpers/unison_patches
Reviewer Review Type Date Requested Status
Stuart Bishop (community) Approve
Review via email: mp+265633@code.launchpad.net

Description of the change

This is an update of https://code.launchpad.net/~rcj/charm-helpers/unison_patches/+merge/236028 with the changes asked for there.

To post a comment you must log in.
411. By Robert C Jennings

unison: Add handling for peer departed hook

This adds handing for the <peer>-relation-departed hook in
ssh_authorized_peers() and updates the documentation.

Adding ssh_authorized_peers(...) calls to the peer departed
relationship hook will remove the departing unit from the
authorized keys and list of peers.

412. By Robert C Jennings

unison: Add all known keys for each host

Adding only the rsa key is problematic. The remote host could be
using ecdsa and the first attmempt to connect will add that ecdsa key
and complain that the rsa key conflicts. Taking all key types adds
flexibility and future-proofing.

Without this, connecting to a host that presents an ecdsa host key will
warn that the rsa key conflicts and will require interactive input to
accept the ecdsa key; this breaks the charm.

413. By Robert C Jennings

charmhelpers.contrib.unison: Add support for ecdsa keys

Add support for choosing an ssh key type of ecdsa in addition
to the default rsa type already provided.

Revision history for this message
Stuart Bishop (stub) wrote :

All looks good and seems correct. Tests pass, except for an unrelated failure on trunk (grrr). I can't suggest any improvements or changes.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'charmhelpers/contrib/unison/__init__.py'
2--- charmhelpers/contrib/unison/__init__.py 2015-04-03 15:23:46 +0000
3+++ charmhelpers/contrib/unison/__init__.py 2015-07-23 10:49:49 +0000
4@@ -16,7 +16,7 @@
5
6 # Easy file synchronization among peer units using ssh + unison.
7 #
8-# From *both* peer relation -joined and -changed, add a call to
9+# For the -joined, -changed, and -departed peer relations, add a call to
10 # ssh_authorized_peers() describing the peer relation and the desired
11 # user + group. After all peer relations have settled, all hosts should
12 # be able to connect to on another via key auth'd ssh as the specified user.
13@@ -30,14 +30,21 @@
14 # ...
15 # ssh_authorized_peers(peer_interface='cluster',
16 # user='juju_ssh', group='juju_ssh',
17-# ensure_user=True)
18+# ensure_local_user=True)
19 # ...
20 #
21 # cluster-relation-changed:
22 # ...
23 # ssh_authorized_peers(peer_interface='cluster',
24 # user='juju_ssh', group='juju_ssh',
25-# ensure_user=True)
26+# ensure_local_user=True)
27+# ...
28+#
29+# cluster-relation-departed:
30+# ...
31+# ssh_authorized_peers(peer_interface='cluster',
32+# user='juju_ssh', group='juju_ssh',
33+# ensure_local_user=True)
34 # ...
35 #
36 # Hooks are now free to sync files as easily as:
37@@ -92,11 +99,18 @@
38 raise Exception
39
40
41-def create_private_key(user, priv_key_path):
42+def create_private_key(user, priv_key_path, key_type='rsa'):
43+ types_bits = {
44+ 'rsa': '2048',
45+ 'ecdsa': '521',
46+ }
47+ if key_type not in types_bits:
48+ log('Unknown ssh key type {}, using rsa'.format(key_type), ERROR)
49+ key_type = 'rsa'
50 if not os.path.isfile(priv_key_path):
51 log('Generating new SSH key for user %s.' % user)
52- cmd = ['ssh-keygen', '-q', '-N', '', '-t', 'rsa', '-b', '2048',
53- '-f', priv_key_path]
54+ cmd = ['ssh-keygen', '-q', '-N', '', '-t', key_type,
55+ '-b', types_bits[key_type], '-f', priv_key_path]
56 check_call(cmd)
57 else:
58 log('SSH key already exists at %s.' % priv_key_path)
59@@ -152,7 +166,7 @@
60 known_hosts = os.path.join(ssh_dir, 'known_hosts')
61 khosts = []
62 for host in hosts:
63- cmd = ['ssh-keyscan', '-H', '-t', 'rsa', host]
64+ cmd = ['ssh-keyscan', host]
65 remote_key = check_output(cmd, universal_newlines=True).strip()
66 khosts.append(remote_key)
67 log('Syncing known_hosts @ %s.' % known_hosts)
68@@ -179,7 +193,8 @@
69 hook = hook_name()
70 if hook == '%s-relation-joined' % peer_interface:
71 relation_set(ssh_pub_key=pub_key)
72- elif hook == '%s-relation-changed' % peer_interface:
73+ elif hook == '%s-relation-changed' % peer_interface or \
74+ hook == '%s-relation-departed' % peer_interface:
75 hosts = []
76 keys = []
77
78
79=== modified file 'tests/contrib/unison/test_unison.py'
80--- tests/contrib/unison/test_unison.py 2015-04-03 15:23:46 +0000
81+++ tests/contrib/unison/test_unison.py 2015-07-23 10:49:49 +0000
82@@ -74,7 +74,7 @@
83 self.assertIn(call(_call), self.check_call.call_args_list)
84
85 @patch('os.path.isfile')
86- def test_create_private_key(self, isfile):
87+ def test_create_private_key_rsa(self, isfile):
88 create_cmd = [
89 'ssh-keygen', '-q', '-N', '', '-t', 'rsa', '-b', '2048',
90 '-f', '/home/foo/.ssh/id_rsa']
91@@ -100,6 +100,36 @@
92 _ensure_perms()
93
94 @patch('os.path.isfile')
95+ def test_create_private_key_ecdsa(self, isfile):
96+ create_cmd = [
97+ 'ssh-keygen', '-q', '-N', '', '-t', 'ecdsa', '-b', '521',
98+ '-f', '/home/foo/.ssh/id_ecdsa']
99+
100+ def _ensure_perms():
101+ cmds = [
102+ ['chown', 'foo', '/home/foo/.ssh/id_ecdsa'],
103+ ['chmod', '0600', '/home/foo/.ssh/id_ecdsa'],
104+ ]
105+ self._ensure_calls_in(cmds)
106+
107+ isfile.return_value = False
108+ unison.create_private_key(
109+ user='foo',
110+ priv_key_path='/home/foo/.ssh/id_ecdsa',
111+ key_type='ecdsa')
112+ self.assertIn(call(create_cmd), self.check_call.call_args_list)
113+ _ensure_perms()
114+ self.check_call.call_args_list = []
115+
116+ isfile.return_value = True
117+ unison.create_private_key(
118+ user='foo',
119+ priv_key_path='/home/foo/.ssh/id_ecdsa',
120+ key_type='ecdsa')
121+ self.assertNotIn(call(create_cmd), self.check_call.call_args_list)
122+ _ensure_perms()
123+
124+ @patch('os.path.isfile')
125 def test_create_public_key(self, isfile):
126 create_cmd = ['ssh-keygen', '-y', '-f', '/home/foo/.ssh/id_rsa']
127 isfile.return_value = True
128@@ -273,6 +303,33 @@
129 write_hosts.assert_called_with('foo', ['host1', 'host2'])
130 self.relation_set.assert_called_with(ssh_authorized_hosts='host1:host2')
131
132+ @patch.object(unison, 'write_known_hosts')
133+ @patch.object(unison, 'write_authorized_keys')
134+ @patch.object(unison, 'get_keypair')
135+ @patch.object(unison, 'ensure_user')
136+ def test_ssh_auth_peer_departed(self, ensure_user, get_keypair,
137+ write_keys, write_hosts):
138+ get_keypair.return_value = ('privkey', 'pubkey')
139+
140+ self.hook_name.return_value = 'cluster-relation-departed'
141+
142+ self.relation_get.side_effect = [
143+ 'key1',
144+ 'host1',
145+ 'key2',
146+ 'host2',
147+ '', ''
148+ ]
149+ unison.ssh_authorized_peers(peer_interface='cluster',
150+ user='foo', group='foo',
151+ ensure_local_user=True)
152+
153+ ensure_user.assert_called_with('foo', 'foo')
154+ get_keypair.assert_called_with('foo')
155+ write_keys.assert_called_with('foo', ['key1', 'key2'])
156+ write_hosts.assert_called_with('foo', ['host1', 'host2'])
157+ self.relation_set.assert_called_with(ssh_authorized_hosts='host1:host2')
158+
159 def test_collect_authed_hosts(self):
160 # only one of the hosts in fake environment has auth'd
161 # the local peer

Subscribers

People subscribed via source and target branches