Merge ~bjornt/maas:create-certificate-websocket into maas:master

Proposed by Björn Tillenius
Status: Merged
Approved by: Björn Tillenius
Approved revision: 329bb4d5a12510a4ed88fab1a332e65d5652f84d
Merge reported by: MAAS Lander
Merged at revision: not available
Proposed branch: ~bjornt/maas:create-certificate-websocket
Merge into: maas:master
Diff against target: 160 lines (+81/-4)
5 files modified
src/maasserver/forms/pods.py (+2/-4)
src/maasserver/utils/certificates.py (+15/-0)
src/maasserver/utils/tests/test_certificates.py (+14/-0)
src/maasserver/websockets/handlers/general.py (+22/-0)
src/maasserver/websockets/handlers/tests/test_general.py (+28/-0)
Reviewer Review Type Date Requested Status
MAAS Lander Approve
Alberto Donato (community) Approve
Review via email: mp+407821@code.launchpad.net

Commit message

Add a websocket function for generating a client certificate.

This can be used to generate a certificate to pass along when creating a VM
host.

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

UNIT TESTS
-b create-certificate-websocket lp:~bjornt/maas/+git/maas into -b master lp:~maas-committers/maas

STATUS: SUCCESS
COMMIT: a08a820ab61924975f34611a7eb8284e2c1fe969

review: Approve
Revision history for this message
Alberto Donato (ack) wrote :

+1

one minor question inline

review: Approve
Revision history for this message
Björn Tillenius (bjornt) :
Revision history for this message
MAAS Lander (maas-lander) wrote :

LANDING
-b create-certificate-websocket lp:~bjornt/maas/+git/maas into -b master lp:~maas-committers/maas

STATUS: FAILED BUILD
LOG: http://maas-ci.internal:8080/job/maas/job/branch-tester/10817/consoleText

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

UNIT TESTS
-b create-certificate-websocket lp:~bjornt/maas/+git/maas into -b master lp:~maas-committers/maas

STATUS: SUCCESS
COMMIT: 329bb4d5a12510a4ed88fab1a332e65d5652f84d

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

LANDING
-b create-certificate-websocket lp:~bjornt/maas/+git/maas into -b master lp:~maas-committers/maas

STATUS: FAILED BUILD
LOG: http://maas-ci.internal:8080/job/maas/job/branch-tester/10821/consoleText

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/src/maasserver/forms/pods.py b/src/maasserver/forms/pods.py
2index d361d01..9a9eee0 100644
3--- a/src/maasserver/forms/pods.py
4+++ b/src/maasserver/forms/pods.py
5@@ -44,7 +44,6 @@ from maasserver.forms import MAASModelForm
6 from maasserver.models import (
7 BMC,
8 BMCRoutableRackControllerRelationship,
9- Config,
10 Domain,
11 Event,
12 Interface,
13@@ -66,6 +65,7 @@ from maasserver.node_constraint_filter_forms import (
14 )
15 from maasserver.rpc import getClientFromIdentifiers
16 from maasserver.utils import absolute_reverse
17+from maasserver.utils.certificates import get_maas_client_cn
18 from maasserver.utils.dns import validate_hostname
19 from maasserver.utils.forms import set_form_error
20 from maasserver.utils.orm import post_commit_do, transactional
21@@ -295,10 +295,8 @@ class PodForm(MAASModelForm):
22 and not cleaned_data.get("key")
23 )
24 if should_generate_cert:
25- maas_name = Config.objects.get_config("maas_name")
26 pod_name = cleaned_data.get("name")
27- cn = f"{pod_name}@{maas_name}" if pod_name else maas_name
28- cert = generate_certificate(cn)
29+ cert = generate_certificate(get_maas_client_cn(pod_name))
30 cleaned_data["certificate"] = cert.certificate_pem()
31 cleaned_data["key"] = cert.private_key_pem()
32
33diff --git a/src/maasserver/utils/certificates.py b/src/maasserver/utils/certificates.py
34new file mode 100644
35index 0000000..ad8d225
36--- /dev/null
37+++ b/src/maasserver/utils/certificates.py
38@@ -0,0 +1,15 @@
39+from maasserver.models import Config
40+
41+
42+def get_maas_client_cn(object_name):
43+ """Get a CN suitable for a client certificate.
44+
45+ If the certificate is for a model object, like a Pod, the name of
46+ the object should be passed in, and the CN will look like
47+ '$maas_name@object_name'
48+
49+ If the client certificate isn't tied to a specific object, None can
50+ be passed in, which will result in the CN beeing the MAAS name.
51+ """
52+ maas_name = Config.objects.get_config("maas_name")
53+ return f"{object_name}@{maas_name}" if object_name else maas_name
54diff --git a/src/maasserver/utils/tests/test_certificates.py b/src/maasserver/utils/tests/test_certificates.py
55new file mode 100644
56index 0000000..f9e69a2
57--- /dev/null
58+++ b/src/maasserver/utils/tests/test_certificates.py
59@@ -0,0 +1,14 @@
60+from maasserver.models import Config
61+from maasserver.testing.testcase import MAASServerTestCase
62+from maasserver.utils.certificates import get_maas_client_cn
63+
64+
65+class TestGetMAASClientCN(MAASServerTestCase):
66+ def test_with_object(self):
67+ Config.objects.set_config("maas_name", "my-maas")
68+ object_name = "my-object"
69+ self.assertEqual("my-object@my-maas", get_maas_client_cn(object_name))
70+
71+ def test_no_object(self):
72+ Config.objects.set_config("maas_name", "my-maas")
73+ self.assertEqual("my-maas", get_maas_client_cn(None))
74diff --git a/src/maasserver/websockets/handlers/general.py b/src/maasserver/websockets/handlers/general.py
75index cdbc104..c9d06ef 100644
76--- a/src/maasserver/websockets/handlers/general.py
77+++ b/src/maasserver/websockets/handlers/general.py
78@@ -25,6 +25,7 @@ from maasserver.models.node import Node
79 from maasserver.models.packagerepository import PackageRepository
80 from maasserver.node_action import ACTIONS_DICT
81 from maasserver.permissions import NodePermission
82+from maasserver.utils.certificates import get_maas_client_cn
83 from maasserver.utils.osystems import (
84 list_all_usable_hwe_kernels,
85 list_all_usable_osystems,
86@@ -35,6 +36,7 @@ from maasserver.utils.osystems import (
87 )
88 from maasserver.websockets.base import Handler
89 from provisioningserver.boot import BootMethodRegistry
90+from provisioningserver.certificates import generate_certificate
91
92
93 class GeneralHandler(Handler):
94@@ -47,6 +49,7 @@ class GeneralHandler(Handler):
95 "components_to_disable",
96 "default_min_hwe_kernel",
97 "device_actions",
98+ "generate_client_certificate",
99 "hwe_kernels",
100 "known_architectures",
101 "known_boot_architectures",
102@@ -236,3 +239,22 @@ class GeneralHandler(Handler):
103 for _, boot_method in BootMethodRegistry
104 if boot_method.arch_octet or boot_method.user_class
105 ]
106+
107+ def generate_client_certificate(self, params):
108+ """Generate a client X509 client certificate.
109+
110+ This can be used for something like a LXD VM host. The
111+ certificate is not stored in the DB, so that caller is
112+ responsible for keeping track of it and pass it around.
113+
114+ If object_name is passed, the certificate's CN will be
115+ $objectname@$maas_name. If not, the CN will be $maas_name.
116+ """
117+ cert = generate_certificate(
118+ get_maas_client_cn(params.get("object_name"))
119+ )
120+ return {
121+ "CN": cert.cn(),
122+ "certificate": cert.certificate_pem(),
123+ "private_key": cert.private_key_pem(),
124+ }
125diff --git a/src/maasserver/websockets/handlers/tests/test_general.py b/src/maasserver/websockets/handlers/tests/test_general.py
126index 2edad9b..4789e5e 100644
127--- a/src/maasserver/websockets/handlers/tests/test_general.py
128+++ b/src/maasserver/websockets/handlers/tests/test_general.py
129@@ -365,3 +365,31 @@ class TestGeneralHandler(MAASServerTestCase):
130 ],
131 handler.known_boot_architectures({}),
132 )
133+
134+ def test_generate_certificate_no_name(self):
135+ Config.objects.set_config("maas_name", "mymaas")
136+ handler = GeneralHandler(factory.make_User(), {}, None)
137+ result = handler.generate_client_certificate({})
138+ self.assertEqual(result["CN"], "mymaas")
139+ self.assertTrue(
140+ result["certificate"].startswith("-----BEGIN CERTIFICATE-----"),
141+ result["certificate"],
142+ )
143+ self.assertTrue(
144+ result["private_key"].startswith("-----BEGIN PRIVATE KEY-----"),
145+ result["private_key"],
146+ )
147+
148+ def test_generate_certificate_with_name(self):
149+ Config.objects.set_config("maas_name", "mymaas")
150+ handler = GeneralHandler(factory.make_User(), {}, None)
151+ result = handler.generate_client_certificate({"object_name": "mypod"})
152+ self.assertEqual(result["CN"], "mypod@mymaas")
153+ self.assertTrue(
154+ result["certificate"].startswith("-----BEGIN CERTIFICATE-----"),
155+ result["certificate"],
156+ )
157+ self.assertTrue(
158+ result["private_key"].startswith("-----BEGIN PRIVATE KEY-----"),
159+ result["private_key"],
160+ )

Subscribers

People subscribed via source and target branches