Merge ~newell-jensen/maas:lp1786193 into maas:master

Proposed by Newell Jensen
Status: Merged
Approved by: Newell Jensen
Approved revision: 8e243594f56dc384071268d0a89f17d18f5e20f4
Merge reported by: MAAS Lander
Merged at revision: not available
Proposed branch: ~newell-jensen/maas:lp1786193
Merge into: maas:master
Diff against target: 226 lines (+114/-4)
6 files modified
src/maasserver/api/ssh_keys.py (+21/-2)
src/maasserver/api/tests/test_api.py (+36/-0)
src/maasserver/api/tests/test_users.py (+30/-0)
src/maasserver/api/users.py (+7/-1)
src/maasserver/forms/__init__.py (+5/-1)
src/maasserver/forms/tests/test_sshkey.py (+15/-0)
Reviewer Review Type Date Requested Status
Lee Trager (community) Approve
MAAS Lander Approve
Review via email: mp+356892@code.launchpad.net

Commit message

LP: #1786193 -- Add ability to create ssh key during admin creation in the API. Add ability to specify user during ssh key creation in the API.

To post a comment you must log in.
Revision history for this message
MAAS Lander (maas-lander) wrote :

UNIT TESTS
-b lp1786193 lp:~newell-jensen/maas/+git/maas into -b master lp:~maas-committers/maas

STATUS: SUCCESS
COMMIT: 34b99fe81a153dce212c7840c15466a8f5301c13

review: Approve
Revision history for this message
Lee Trager (ltrager) wrote :

Allowing administrators to specify a users SSH keys at account creation makes sense. However once the account is created what keys are in use should be in control of that user. Allowing any user to create keys for another user creates a security risk. With this patch I could upload my SSH key to your account which will give me access to any machine you deploy or run ephemerally.

review: Needs Fixing
Revision history for this message
Andres Rodriguez (andreserl) wrote :

*only* administrators should be allowed to add keys for a given user.

~newell-jensen/maas:lp1786193 updated
0260d0c... by Newell Jensen

Review fixes.

Revision history for this message
Lee Trager (ltrager) wrote :

I don't think we want to allow administrators to add keys to another account once its created.

In a datacenter environment you'll often have 3 different teams accessing hardware. IT administrators who will handle administrating MAAS and have root access to the boxes running MAAS, data techs which handle setting up new machines and fixing existing hardware, and dev groups which actually deploy OS's to run applications which have customer data.

When a dev group deploys a machine it shouldn't be possible for anyone but that dev group to access that machine as it has customer data on it. The way MAAS works a data tech will need an administrative account to add machines and fix existing ones. A data tech should not be able to access any deployed machine but because they are an administrator they will be able to access any machine in the data center.

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

UNIT TESTS
-b lp1786193 lp:~newell-jensen/maas/+git/maas into -b master lp:~maas-committers/maas

STATUS: SUCCESS
COMMIT: 0260d0c4981ec35f6a932a1db2a26c5a62386a56

review: Approve
~newell-jensen/maas:lp1786193 updated
0bcf417... by Newell Jensen

Raise error if non admin tries supplies a username when creating an SSH key.

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

UNIT TESTS
-b lp1786193 lp:~newell-jensen/maas/+git/maas into -b master lp:~maas-committers/maas

STATUS: SUCCESS
COMMIT: 0bcf41785d8db79b17af419b02eccc83c715a0b2

review: Approve
Revision history for this message
Lee Trager (ltrager) wrote :

This looks much better but I think the audit log needs to be improved. Right now if I create a key for you the audit log will only show I created an SSH key. It should show I created an SSH key for you.

~newell-jensen/maas:lp1786193 updated
8e24359... by Newell Jensen

Update audit log description for SSH key creation when the requesting user is different than who the SSH key is created for.

Revision history for this message
Lee Trager (ltrager) wrote :

LGTM!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/src/maasserver/api/ssh_keys.py b/src/maasserver/api/ssh_keys.py
2index a911407..b26e157 100644
3--- a/src/maasserver/api/ssh_keys.py
4+++ b/src/maasserver/api/ssh_keys.py
5@@ -11,6 +11,7 @@ __all__ = [
6 import http.client
7
8 from django.conf import settings
9+from django.contrib.auth.models import User
10 from django.http import (
11 HttpResponse,
12 HttpResponseForbidden,
13@@ -20,6 +21,7 @@ from maasserver.api.support import (
14 operation,
15 OperationsHandler,
16 )
17+from maasserver.api.utils import get_optional_param
18 from maasserver.audit import create_audit_event
19 from maasserver.enum import (
20 ENDPOINT,
21@@ -35,6 +37,7 @@ from maasserver.models import (
22 SSHKey,
23 )
24 from maasserver.utils.keys import ImportSSHKeysError
25+from maasserver.utils.orm import get_one
26 from piston3.emitters import JSONEmitter
27 from piston3.handler import typemapper
28 from piston3.utils import rc
29@@ -56,12 +59,28 @@ class SSHKeysHandler(OperationsHandler):
30 return SSHKey.objects.filter(user=request.user)
31
32 def create(self, request):
33- """Add a new SSH key to the requesting user's account.
34+ """Add a new SSH key to the requesting or supplied user's account.
35
36 The request payload should contain the public SSH key data in form
37 data whose name is "key".
38 """
39- form = SSHKeyForm(user=request.user, data=request.data)
40+ user = request.user
41+ username = get_optional_param(request.POST, 'user')
42+ if username is not None and request.user.is_superuser:
43+ supplied_user = get_one(User.objects.filter(username=username))
44+ if supplied_user is not None:
45+ user = supplied_user
46+ else:
47+ # Raise an error so that the user can know that their
48+ # attempt at specifying a user did not work.
49+ raise MAASAPIValidationError(
50+ "Supplied username does not match any current users.")
51+ elif username is not None and not request.user.is_superuser:
52+ raise MAASAPIValidationError(
53+ "Only administrators can specify a user"
54+ " when creating an SSH key.")
55+
56+ form = SSHKeyForm(user=user, data=request.data)
57 if form.is_valid():
58 sshkey = form.save(ENDPOINT.API, request)
59 emitter = JSONEmitter(
60diff --git a/src/maasserver/api/tests/test_api.py b/src/maasserver/api/tests/test_api.py
61index ed0f926..b36cfc9 100644
62--- a/src/maasserver/api/tests/test_api.py
63+++ b/src/maasserver/api/tests/test_api.py
64@@ -529,6 +529,42 @@ class TestSSHKeyHandlers(APITestCase.ForUser):
65 added_key = get_one(SSHKey.objects.filter(user=self.user))
66 self.assertEqual(key_string, added_key.key)
67
68+ def test_adding_works_with_user(self):
69+ self.become_admin()
70+ username = factory.make_name('user')
71+ user = factory.make_User(username=username)
72+ key_string = get_data('data/test_rsa0.pub')
73+ response = self.client.post(
74+ reverse('sshkeys_handler'), data=dict(
75+ key=key_string, user=username))
76+ self.assertEqual(http.client.CREATED, response.status_code)
77+ parsed_response = json_load_bytes(response.content)
78+ self.assertEqual(key_string, parsed_response["key"])
79+ added_key = get_one(SSHKey.objects.filter(user=user))
80+ self.assertEqual(key_string, added_key.key)
81+
82+ def test_adding_does_not_work_with_unknown_user(self):
83+ self.become_admin()
84+ username = factory.make_name('user')
85+ key_string = get_data('data/test_rsa0.pub')
86+ response = self.client.post(
87+ reverse('sshkeys_handler'), data=dict(
88+ key=key_string, user=username))
89+ self.assertEqual(
90+ http.client.BAD_REQUEST, response.status_code, response)
91+ self.assertIn(b"Supplied username", response.content)
92+
93+ def test_adding_does_not_work_with_user_for_non_admin(self):
94+ username = factory.make_name('user')
95+ factory.make_User(username=username)
96+ key_string = get_data('data/test_rsa0.pub')
97+ response = self.client.post(
98+ reverse('sshkeys_handler'), data=dict(
99+ key=key_string, user=username))
100+ self.assertEqual(
101+ http.client.BAD_REQUEST, response.status_code, response)
102+ self.assertIn(b"Only administrators", response.content)
103+
104 def test_adding_catches_key_validation_errors(self):
105 key_string = factory.make_string()
106 response = self.client.post(
107diff --git a/src/maasserver/api/tests/test_users.py b/src/maasserver/api/tests/test_users.py
108index 068ddd1..373a68b 100644
109--- a/src/maasserver/api/tests/test_users.py
110+++ b/src/maasserver/api/tests/test_users.py
111@@ -23,9 +23,11 @@ from maasserver.models import (
112 StaticIPAddress,
113 )
114 from maasserver.models.event import Event
115+from maasserver.testing import get_data
116 from maasserver.testing.api import APITestCase
117 from maasserver.testing.factory import factory
118 from maasserver.utils.django_urls import reverse
119+from maasserver.utils.orm import get_one
120 from provisioningserver.events import AUDIT
121 from testtools.matchers import (
122 ContainsAll,
123@@ -119,6 +121,34 @@ class TestUsers(APITestCase.ForUser):
124 (email, True),
125 (created_user.email, created_user.is_superuser))
126
127+ def test_POST_creates_admin_with_ssh_key(self):
128+ self.become_admin()
129+ username = factory.make_name('user')
130+ email = factory.make_email_address()
131+ password = factory.make_string()
132+ key_string = get_data('data/test_rsa0.pub')
133+ response = self.client.post(
134+ reverse('users_handler'),
135+ {
136+ 'username': username,
137+ 'email': email,
138+ 'password': password,
139+ 'is_superuser': '1',
140+ 'key': key_string,
141+ })
142+ self.assertEqual(
143+ http.client.OK, response.status_code, response.content)
144+
145+ self.assertEqual(
146+ username, json.loads(
147+ response.content.decode(settings.DEFAULT_CHARSET))['username'])
148+ created_user = User.objects.get(username=username)
149+ self.assertEqual(
150+ (email, True),
151+ (created_user.email, created_user.is_superuser))
152+ added_key = get_one(SSHKey.objects.filter(user=created_user))
153+ self.assertEqual(key_string, added_key.key)
154+
155 def test_POST_requires_admin(self):
156 response = self.client.post(
157 reverse('users_handler'),
158diff --git a/src/maasserver/api/users.py b/src/maasserver/api/users.py
159index e4a4a8d..10dfdf0 100644
160--- a/src/maasserver/api/users.py
161+++ b/src/maasserver/api/users.py
162@@ -11,6 +11,7 @@ __all__ = [
163
164 from django.core.exceptions import ValidationError
165 from django.shortcuts import get_object_or_404
166+from maasserver.api.ssh_keys import SSHKeysHandler
167 from maasserver.api.support import (
168 admin_method,
169 operation,
170@@ -90,8 +91,13 @@ class UsersHandler(OperationsHandler):
171 description=("Created %s '%s'." % (
172 'admin' if is_superuser else 'user', username)))
173 if is_superuser:
174- return User.objects.create_superuser(
175+ user = User.objects.create_superuser(
176 username=username, password=password, email=email)
177+ if request.data.get('key') is not None:
178+ request.user = user
179+ sshkeys_handler = SSHKeysHandler()
180+ sshkeys_handler.create(request)
181+ return user
182 else:
183 return User.objects.create_user(
184 username=username, password=password, email=email)
185diff --git a/src/maasserver/forms/__init__.py b/src/maasserver/forms/__init__.py
186index fd66dfd..414e949 100644
187--- a/src/maasserver/forms/__init__.py
188+++ b/src/maasserver/forms/__init__.py
189@@ -1125,9 +1125,13 @@ class SSHKeyForm(MAASModelForm):
190
191 def save(self, endpoint, request):
192 sshkey = super(SSHKeyForm, self).save()
193+ if self.instance.user is request.user:
194+ description = "Created SSH key."
195+ else:
196+ description = "Created SSH key for %s." % self.instance.user
197 create_audit_event(
198 EVENT_TYPES.AUTHORISATION, endpoint, request,
199- None, description="Created SSH key.")
200+ None, description=description)
201 return sshkey
202
203
204diff --git a/src/maasserver/forms/tests/test_sshkey.py b/src/maasserver/forms/tests/test_sshkey.py
205index 7f6e950..9d946e5 100644
206--- a/src/maasserver/forms/tests/test_sshkey.py
207+++ b/src/maasserver/forms/tests/test_sshkey.py
208@@ -30,3 +30,18 @@ class TestSSHKeyForm(MAASServerTestCase):
209 event = Event.objects.get(type__level=AUDIT)
210 self.assertIsNotNone(event)
211 self.assertEqual(event.description, "Created SSH key.")
212+
213+ def test_creates_audit_event_for_specified_user_on_save(self):
214+ specified_user = factory.make_User()
215+ request_user = factory.make_User()
216+ key_string = get_data('data/test_rsa0.pub')
217+ form = SSHKeyForm(
218+ user=specified_user,
219+ data={'key': key_string})
220+ request = HttpRequest()
221+ request.user = request_user
222+ form.save(factory.pick_choice(ENDPOINT_CHOICES), request)
223+ event = Event.objects.get(type__level=AUDIT)
224+ self.assertIsNotNone(event)
225+ self.assertEqual(
226+ event.description, "Created SSH key for %s." % specified_user)

Subscribers

People subscribed via source and target branches