Merge ~ltrager/maas:enlistment_sshkeys into maas:master

Proposed by Lee Trager
Status: Rejected
Rejected by: Blake Rouse
Proposed branch: ~ltrager/maas:enlistment_sshkeys
Merge into: maas:master
Diff against target: 198 lines (+89/-22)
6 files modified
src/maasserver/models/sshkey.py (+6/-1)
src/maasserver/models/tests/test_sshkey.py (+15/-1)
src/maasserver/testing/factory.py (+25/-0)
src/metadataserver/api.py (+38/-15)
src/metadataserver/tests/test_api.py (+4/-4)
src/metadataserver/user_data/templates/enlistment.template (+1/-1)
Reviewer Review Type Date Requested Status
MAAS Lander Approve
Andres Rodriguez (community) Needs Information
Review via email: mp+353042@code.launchpad.net

Commit message

Provide all administrator SSH keys to the enlistment environment.

When a new node enlists it does not have an owner. Previously that meant no
SSH keys were sent. If enlistment failed it was impossible to debug as there
was no way to access the system. Now the SSH keys for all administrators are
sent so any administrator can debug the problem.

To post a comment you must log in.
Revision history for this message
Andres Rodriguez (andreserl) wrote :

I’m not quite sure about this. We should talk about this. If there are many admins the keys of all of them would be here and this may change once we do rbac.

review: Needs Information
Revision history for this message
MAAS Lander (maas-lander) wrote :

UNIT TESTS
-b enlistment_sshkeys lp:~ltrager/maas/+git/maas into -b master lp:~maas-committers/maas

STATUS: SUCCESS
COMMIT: 80a8ed48ac86c5c689eb91cae3633c40185e2a92

review: Approve
~ltrager/maas:enlistment_sshkeys updated
c0d4fe7... by Lee Trager

Merge branch 'master' into enlistment_sshkeys

Revision history for this message
MAAS Lander (maas-lander) wrote :

UNIT TESTS
-b enlistment_sshkeys lp:~ltrager/maas/+git/maas into -b master lp:~maas-committers/maas

STATUS: SUCCESS
COMMIT: c0d4fe72da62b751a8dfc18a6ceacf42d594a46d

review: Approve
Revision history for this message
Blake Rouse (blake-rouse) wrote :

Rejecting due to inactivity.

Unmerged commits

c0d4fe7... by Lee Trager

Merge branch 'master' into enlistment_sshkeys

80a8ed4... by Lee Trager

Provide all administrator SSH keys to the enlistment environment.

When a new node enlists it does not have an owner. Previously that meant no
SSH keys were sent. If enlistment failed it was impossible to debug as there
was no way to access the system. Now the SSH keys for all administrators are
sent so any administrator can debug the problem.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/src/maasserver/models/sshkey.py b/src/maasserver/models/sshkey.py
2index f0dfdf3..3933e21 100644
3--- a/src/maasserver/models/sshkey.py
4+++ b/src/maasserver/models/sshkey.py
5@@ -1,4 +1,4 @@
6-# Copyright 2012-2016 Canonical Ltd. This software is licensed under the
7+# Copyright 2012-2018 Canonical Ltd. This software is licensed under the
8 # GNU Affero General Public License version 3 (see the file LICENSE).
9
10 """:class:`SSHKey` and friends."""
11@@ -32,6 +32,11 @@ class SSHKeyManager(Manager):
12 """Return the text of the ssh keys associated with a user."""
13 return SSHKey.objects.filter(user=user).values_list('key', flat=True)
14
15+ def get_keys_for_all_admins(self):
16+ """Return all keys from all admins."""
17+ return SSHKey.objects.filter(user__is_superuser=True).values_list(
18+ 'key', flat=True)
19+
20
21 def validate_ssh_public_key(value):
22 """Validate that the given value contains a valid SSH public key."""
23diff --git a/src/maasserver/models/tests/test_sshkey.py b/src/maasserver/models/tests/test_sshkey.py
24index 03bde0e..1a6d4a6 100644
25--- a/src/maasserver/models/tests/test_sshkey.py
26+++ b/src/maasserver/models/tests/test_sshkey.py
27@@ -1,10 +1,11 @@
28-# Copyright 2012-2016 Canonical Ltd. This software is licensed under the
29+# Copyright 2012-2018 Canonical Ltd. This software is licensed under the
30 # GNU Affero General Public License version 3 (see the file LICENSE).
31
32 """Tests for the SSHKey model."""
33
34 __all__ = []
35
36+from itertools import chain
37 import random
38
39 from django.core.exceptions import ValidationError
40@@ -335,3 +336,16 @@ class SSHKeyManagerTest(MAASServerTestCase):
41 factory.make_user_with_keys(n_keys=2)
42 keys = SSHKey.objects.get_keys_for_user(user1)
43 self.assertItemsEqual([key.key for key in created_keys], keys)
44+
45+ def test_get_keys_for_all_admins_no_keys(self):
46+ for _ in range(3):
47+ factory.make_admin()
48+ keys = SSHKey.objects.get_keys_for_all_admins()
49+ self.assertItemsEqual([], keys)
50+
51+ def test_get_keys_for_all_admins_with_keys(self):
52+ admin1, key1 = factory.make_admin_with_keys(n_keys=3)
53+ admin2, key2 = factory.make_admin_with_keys(n_keys=4)
54+ user, key3 = factory.make_user_with_keys()
55+ keys = SSHKey.objects.get_keys_for_all_admins()
56+ self.assertItemsEqual([key.key for key in chain(key1, key2)], keys)
57diff --git a/src/maasserver/testing/factory.py b/src/maasserver/testing/factory.py
58index c79ef07..b5ac5c0 100644
59--- a/src/maasserver/testing/factory.py
60+++ b/src/maasserver/testing/factory.py
61@@ -1547,6 +1547,31 @@ class Factory(maastesting.factory.Factory):
62 user.userprofile.save()
63 return user
64
65+ def make_admin_with_keys(
66+ self, n_keys=2, user=None, keysource=None, **kwargs):
67+ """Create an admin with n `SSHKey`. If user is not None, use this user
68+ instead of creating one. If keysource is not None, use this keysource
69+ instaed of creating one.
70+
71+ Additional keyword arguments are passed to `make_user()`.
72+ """
73+ if n_keys > MAX_PUBLIC_KEYS:
74+ raise RuntimeError(
75+ "Cannot create more than %d public keys. If you need more: "
76+ "add more keys in src/maasserver/tests/data/."
77+ % MAX_PUBLIC_KEYS)
78+ if user is None:
79+ user = self.make_admin(**kwargs)
80+ if keysource is None:
81+ keysource = self.make_KeySource()
82+ keys = []
83+ for i in range(n_keys):
84+ key_string = get_data('data/test_rsa%d.pub' % i)
85+ key = SSHKey(user=user, key=key_string, keysource=keysource)
86+ key.save()
87+ keys.append(key)
88+ return user, keys
89+
90 def make_FileStorage(self, filename=None, content=None, owner=None):
91 fake_file = self.make_file_upload(filename, content)
92 return FileStorage.objects.save_file(fake_file.name, fake_file, owner)
93diff --git a/src/metadataserver/api.py b/src/metadataserver/api.py
94index f096347..602c4c1 100644
95--- a/src/metadataserver/api.py
96+++ b/src/metadataserver/api.py
97@@ -1102,28 +1102,51 @@ class EnlistMetaDataHandler(OperationsHandler):
98
99 create = update = delete = None
100
101- data = {
102- 'instance-id': 'i-maas-enlistment',
103- 'local-hostname': "maas-enlisting-node",
104- 'public-keys': "",
105- }
106-
107 def read(self, request, version, item=None):
108 check_version(version)
109
110+ producers = {
111+ 'instance-id': self.instance_id,
112+ 'local-hostname': self.local_hostname,
113+ 'public-keys': self.public_keys,
114+ }
115+
116 # Requesting the list of attributes, not any particular attribute.
117 if item is None or len(item) == 0:
118- keys = sorted(self.data.keys())
119- # There's nothing in public-keys, so we don't advertise it.
120- # But cloud-init does ask for it and it's not worth logging
121- # a traceback for.
122- keys.remove('public-keys')
123- return make_list_response(keys)
124-
125- if item not in self.data:
126+ subfields = list(producers.keys())
127+ # Add public-keys to the list of attributes, if the
128+ # an admin has registered SSH keys.
129+ if not SSHKey.objects.get_keys_for_all_admins().exists():
130+ subfields.remove('public-keys')
131+ return make_list_response(sorted(subfields))
132+
133+ if item not in producers:
134 raise MAASAPINotFound("Unknown metadata attribute: %s" % item)
135
136- return make_text_response(self.data[item])
137+ return producers[item]()
138+
139+ def instance_id(self):
140+ """Return a generic instance id.
141+
142+ A permanent instance id will be generated when the node object is
143+ created.
144+ """
145+ return make_text_response('i-maas-enlistment')
146+
147+ def local_hostname(self):
148+ """Return a generic hostname.
149+
150+ A unique hostname will be generated when the node object is created.
151+ """
152+ return make_text_response('maas-enlisting-node')
153+
154+ def public_keys(self):
155+ """Return all SSH keys from all admins.
156+
157+ As the enlistment environment does not have an owner return all SSH
158+ keys from all admins to allow debugging.
159+ """
160+ return make_list_response(SSHKey.objects.get_keys_for_all_admins())
161
162
163 class EnlistUserDataHandler(OperationsHandler):
164diff --git a/src/metadataserver/tests/test_api.py b/src/metadataserver/tests/test_api.py
165index 6f5c67a..68a7339 100644
166--- a/src/metadataserver/tests/test_api.py
167+++ b/src/metadataserver/tests/test_api.py
168@@ -2842,14 +2842,14 @@ class TestEnlistViews(MAASServerTestCase):
169 # just insist content is non-empty. It doesn't matter what it is.
170 self.assertTrue(response.content)
171
172- def test_public_keys_returns_empty(self):
173- # An enlisting node has no SSH keys, but it does request them.
174- # If the node insists, we give it the empty list.
175+ def test_public_keys_returns_admin_keys(self):
176+ _, keys = factory.make_admin_with_keys(n_keys=1)
177+ factory.make_user_with_keys()
178 md_url = reverse(
179 'enlist-metadata-meta-data', args=['latest', 'public-keys'])
180 response = self.client.get(md_url)
181 self.assertEqual(
182- (http.client.OK, ""),
183+ (http.client.OK, ''.join([key.key for key in keys])),
184 (response.status_code,
185 response.content.decode(settings.DEFAULT_CHARSET)))
186
187diff --git a/src/metadataserver/user_data/templates/enlistment.template b/src/metadataserver/user_data/templates/enlistment.template
188index f389108..18f5333 100644
189--- a/src/metadataserver/user_data/templates/enlistment.template
190+++ b/src/metadataserver/user_data/templates/enlistment.template
191@@ -53,7 +53,7 @@ main() {
192 echo $output | jq .
193 if [ $ret -ne 0 ]; then
194 echo "Enlistment on {{server_url}} failed!"
195- sleep 10
196+ sleep 120
197 exit 1
198 fi
199

Subscribers

People subscribed via source and target branches