Merge ~ltrager/maas:improve_ipmi_cipher_detection_2.9 into maas:2.9

Proposed by Lee Trager
Status: Merged
Approved by: Lee Trager
Approved revision: a4e907bd0bbb192fc4150aa7f450ef585c015157
Merge reported by: MAAS Lander
Merged at revision: not available
Proposed branch: ~ltrager/maas:improve_ipmi_cipher_detection_2.9
Merge into: maas:2.9
Diff against target: 744 lines (+219/-410)
2 files modified
src/metadataserver/builtin_scripts/commissioning_scripts/bmc_config.py (+87/-180)
src/metadataserver/builtin_scripts/commissioning_scripts/tests/test_bmc_config.py (+132/-230)
Reviewer Review Type Date Requested Status
Lee Trager (community) Approve
MAAS Lander Pending
Review via email: mp+400364@code.launchpad.net

Commit message

Improve IPMI cipher detection.

Previously IPMI cipher detection tried both bmc-config and ipmitool. This
code would sometimes fail to detect cipher 17. There isn't an advantage to
using both methods as ipmitool will detect 17 and can be used to configure
all ciphers. The ipmitool detection and configuration code has been
refactored and simplified.

Backport of 3961116

To post a comment you must log in.
Revision history for this message
Lee Trager (ltrager) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/src/metadataserver/builtin_scripts/commissioning_scripts/bmc_config.py b/src/metadataserver/builtin_scripts/commissioning_scripts/bmc_config.py
2index 69cc4c6..ae082c7 100755
3--- a/src/metadataserver/builtin_scripts/commissioning_scripts/bmc_config.py
4+++ b/src/metadataserver/builtin_scripts/commissioning_scripts/bmc_config.py
5@@ -266,8 +266,6 @@ class IPMI(BMCConfig):
6 )
7 except (CalledProcessError, TimeoutExpired):
8 pass
9- else:
10- return i
11 return -1, ""
12
13 def detected(self):
14@@ -496,166 +494,62 @@ class IPMI(BMCConfig):
15 )
16 self._bmc_set_keys("Lan_Channel_Auth", ["SOL_Payload_Access"], "Yes")
17
18- def _get_bmc_config_cipher_suite_ids(self):
19- """Return the supported IPMI cipher suite ids from bmc-config."""
20- cipher_suite_ids = {}
21- max_cipher_suite_id = 0
22- regex = re.compile(
23- r"^Maximum_Privilege_Cipher_Suite_Id_(?P<cipher_suite_id>1?\d)$"
24- )
25- for key, value in self._bmc_config.get(
26- "Rmcpplus_Conf_Privilege", {}
27- ).items():
28- m = regex.search(key)
29- if m:
30- cipher_suite_id = m.group("cipher_suite_id")
31- cipher_suite_ids[cipher_suite_id] = value
32- max_cipher_suite_id = max(
33- max_cipher_suite_id, int(cipher_suite_id)
34- )
35- return max_cipher_suite_id, cipher_suite_ids
36-
37- def _config_bmc_config_cipher_suite_ids(self, cipher_suite_ids):
38- # First find the most secure cipher suite id MAAS will use to
39- # communicate to the BMC with.
40- # 3 - HMAC-SHA1::HMAC-SHA1-96::AES-CBC-128
41- # 8 - HMAC-MD5::HMAC-MD5-128::AES-CBC-128
42- # 12 - HMAC-MD5::MD5-128::AES-CBC-128
43- # 17 - HMAC-SHA256::HMAC_SHA256_128::AES-CBC-128
44- # This is not in order as MAAS prefers to use the most secure cipher
45- # available.
46- for cipher_suite_id in ["17", "3", "8", "12"]:
47- if cipher_suite_id not in cipher_suite_ids:
48- continue
49- elif cipher_suite_ids[cipher_suite_id] != "Administrator":
50- print(
51- 'INFO: Enabling IPMI cipher suite id "%s" '
52- "for MAAS use..." % cipher_suite_id
53- )
54- try:
55- self._bmc_set(
56- "Rmcpplus_Conf_Privilege",
57- "Maximum_Privilege_Cipher_Suite_Id_%s"
58- % cipher_suite_id,
59- "Administrator",
60- )
61- except (CalledProcessError, TimeoutExpired):
62- # Some machines will show what ciphers are available
63- # but not allow their value to be changed. The ARM64
64- # machine in the MAAS CI is like this.
65- print(
66- "WARNING: Unable to enable secure IPMI cipher "
67- 'suite id "%s"!' % cipher_suite_id
68- )
69- # Try the next secure cipher
70- continue
71- self._cipher_suite_id = cipher_suite_id
72- # Enable the most secure cipher suite id and leave the
73- # other secure cipher suite ids in their current state.
74- # Most IPMI tools, such as freeipmi-tools, use cipher
75- # suite id 3 as its default. If the user has 3 enabled
76- # while 17 is available we want to keep 3 in the same
77- # state to not break other tools.
78- break
79-
80- # Disable insecure IPMI cipher suites.
81- for cipher_suite_id, state in cipher_suite_ids.items():
82- if cipher_suite_id in ["17", "3", "8", "12"]:
83- continue
84- elif state != "Unused":
85- print(
86- 'INFO: Disabling insecure IPMI cipher suite id "%s"'
87- % cipher_suite_id
88- )
89- try:
90- self._bmc_set(
91- "Rmcpplus_Conf_Privilege",
92- "Maximum_Privilege_Cipher_Suite_Id_%s"
93- % cipher_suite_id,
94- "Unused",
95- )
96- except (CalledProcessError, TimeoutExpired):
97- # Some machines will show what ciphers are available
98- # but not allow their value to be changed. The ARM64
99- # machine in the MAAS CI is like this.
100- print(
101- "WARNING: Unable to disable insecure IPMI cipher "
102- 'suite id "%s"!' % cipher_suite_id
103- )
104-
105 def _get_ipmitool_cipher_suite_ids(self):
106- supported_cipher_suite_ids = None
107- cipher_suite_privs = None
108+ print(
109+ "INFO: Gathering supported cipher suites and current configuration..."
110+ )
111+ supported_cipher_suite_ids = []
112+ current_cipher_suite_privs = None
113 _, output = self._get_ipmitool_lan_print()
114+
115 for line in output.splitlines():
116- key, value = line.split(":", maxsplit=1)
117+ try:
118+ key, value = line.split(":", 1)
119+ except ValueError:
120+ continue
121 key = key.strip()
122 value = value.strip()
123 if key == "RMCP+ Cipher Suites":
124 try:
125- supported_cipher_suite_ids = [
126- int(i) for i in value.split(",")
127- ]
128+ # Some BMCs return an unordered list.
129+ supported_cipher_suite_ids = sorted(
130+ [int(i) for i in value.split(",")]
131+ )
132 except ValueError:
133- return 0, [], ""
134+ print(
135+ "ERROR: ipmitool returned RMCP+ Cipher Suites with "
136+ "invalid characters: %s" % value,
137+ file=sys.stderr,
138+ )
139+ return [], None
140 elif key == "Cipher Suite Priv Max":
141- cipher_suite_privs = value
142+ current_cipher_suite_privs = value
143+ if supported_cipher_suite_ids and current_cipher_suite_privs:
144+ break
145
146- if supported_cipher_suite_ids and cipher_suite_privs:
147- return (
148- max(
149- [
150- i
151- for i in supported_cipher_suite_ids
152- if i in [17, 3, 8, 12]
153- ]
154- ),
155- supported_cipher_suite_ids,
156- cipher_suite_privs,
157- )
158- else:
159- return 0, [], ""
160+ return supported_cipher_suite_ids, current_cipher_suite_privs
161
162- def _config_ipmitool_cipher_suite_ids(
163- self,
164- max_cipher_suite_id,
165- supported_cipher_suite_ids,
166- cipher_suite_privs,
167+ def _configure_ipmitool_cipher_suite_ids(
168+ self, cipher_suite_id, current_suite_privs
169 ):
170 new_cipher_suite_privs = ""
171- for i, v in enumerate(cipher_suite_privs):
172- if i < len(supported_cipher_suite_ids):
173- cipher_suite_id = supported_cipher_suite_ids[i]
174- if cipher_suite_id in [17, 3, 8, 12]:
175- if cipher_suite_id == max_cipher_suite_id and v != "a":
176- print(
177- 'INFO: Enabling IPMI cipher suite id "%s" '
178- "for MAAS use..." % cipher_suite_id
179- )
180- new_cipher_suite_privs += "a"
181- else:
182- new_cipher_suite_privs += v
183- else:
184- if v != "X":
185- print(
186- "INFO: Disabling insecure IPMI cipher suite id "
187- '"%s"' % cipher_suite_id
188- )
189- new_cipher_suite_privs = "X"
190+ for i, c in enumerate(current_suite_privs):
191+ if i == cipher_suite_id and c != "a":
192+ print(
193+ "INFO: Enabling cipher suite %s for MAAS use..."
194+ % cipher_suite_id
195+ )
196+ new_cipher_suite_privs += "a"
197+ elif i not in [17, 3, 8, 12] and c != "X":
198+ print("INFO: Disabling insecure cipher suite %s..." % i)
199+ new_cipher_suite_privs += "X"
200 else:
201- # 15 characters are usually given even if there
202- # aren't 15 ciphers supported. Copy the current value
203- # incase there is some OEM use for them.
204- new_cipher_suite_privs += v
205-
206- if cipher_suite_privs == new_cipher_suite_privs:
207- # Cipher suites are already properly configured, nothing
208- # to do.
209- self._cipher_suite_id = str(max_cipher_suite_id)
210- return
211+ # Leave secure ciphers as is. Most tools default to 3 while
212+ # 17 is considered the most secure.
213+ new_cipher_suite_privs += c
214
215- channel, _ = self._get_ipmitool_lan_print()
216- try:
217+ if new_cipher_suite_privs != current_suite_privs:
218+ channel, _ = self._get_ipmitool_lan_print()
219 check_call(
220 [
221 "ipmitool",
222@@ -667,54 +561,67 @@ class IPMI(BMCConfig):
223 ],
224 timeout=60,
225 )
226- except (CalledProcessError, TimeoutExpired):
227- print(
228- "WARNING: Unable to configure IPMI cipher suites with "
229- "ipmitool!"
230- )
231- else:
232- self._cipher_suite_id = str(max_cipher_suite_id)
233+ return new_cipher_suite_privs
234
235 def _config_cipher_suite_id(self):
236 print("INFO: Configuring IPMI cipher suite ids...")
237
238- # BMC firmware can be buggy and different tools surface these bugs
239- # in different ways. bmc-config works on all machines in the MAAS
240- # CI while ipmitool doesn't detect anything on the ARM64 machine.
241- # However a user has reported that ipmitool detects cipher 17 on
242- # his system while bmc-config doesn't. To make sure MAAS uses the
243- # most secure cipher suite id check both.
244- # https://discourse.maas.io/t/ipmi-cipher-suite-c17-support/3293/11
245-
246- (
247- bmc_config_max,
248- bmc_config_ids,
249- ) = self._get_bmc_config_cipher_suite_ids()
250 (
251- ipmitool_max,
252- ipmitool_ids,
253- ipmitool_privs,
254+ supported_cipher_suite_ids,
255+ current_cipher_suite_privs,
256 ) = self._get_ipmitool_cipher_suite_ids()
257+ print(
258+ "INFO: BMC supports the following ciphers - %s"
259+ % supported_cipher_suite_ids
260+ )
261
262- if bmc_config_max >= ipmitool_max:
263- self._config_bmc_config_cipher_suite_ids(bmc_config_ids)
264- else:
265- self._config_ipmitool_cipher_suite_ids(
266- ipmitool_max, ipmitool_ids, ipmitool_privs
267+ # First find the most secure cipher suite id MAAS will use to
268+ # communicate to the BMC with.
269+ # 3 - HMAC-SHA1::HMAC-SHA1-96::AES-CBC-128
270+ # 8 - HMAC-MD5::HMAC-MD5-128::AES-CBC-128
271+ # 12 - HMAC-MD5::MD5-128::AES-CBC-128
272+ # 17 - HMAC-SHA256::HMAC_SHA256_128::AES-CBC-128
273+ # This is not in order as MAAS prefers to use the most secure cipher
274+ # available.
275+ cipher_suite_id = None
276+ for i in [17, 3, 8, 12]:
277+ if i in supported_cipher_suite_ids:
278+ cipher_suite_id = i
279+ break
280+ if cipher_suite_id is None:
281+ # Some BMC's don't allow this to be viewed or configured, such
282+ # as the PPC64 machine in the MAAS CI.
283+ print(
284+ "WARNING: No IPMI supported cipher suite found! "
285+ "MAAS will use freeipmi-tools default."
286 )
287+ return
288
289- if self._cipher_suite_id:
290- print(
291- 'INFO: MAAS will use IPMI cipher suite id "%s" for '
292- "BMC communication" % self._cipher_suite_id
293+ print(
294+ "INFO: Current cipher suite configuration - %s"
295+ % current_cipher_suite_privs
296+ )
297+ try:
298+ new_cipher_suite_privs = self._configure_ipmitool_cipher_suite_ids(
299+ cipher_suite_id, current_cipher_suite_privs
300 )
301- else:
302+ except (CalledProcessError, TimeoutExpired):
303 # Some BMC's don't allow this to be viewed or configured, such
304 # as the PPC64 machine in the MAAS CI.
305 print(
306- "WARNING: No IPMI cipher suite found! "
307+ "WARNING: Unable to configure IPMI cipher suites! "
308 "MAAS will use freeipmi-tools default."
309 )
310+ else:
311+ print(
312+ "INFO: New cipher suite configuration - %s"
313+ % new_cipher_suite_privs
314+ )
315+ print(
316+ 'INFO: MAAS will use IPMI cipher suite id "%s" for '
317+ "BMC communication" % cipher_suite_id
318+ )
319+ self._cipher_suite_id = str(cipher_suite_id)
320
321 def _config_kg(self):
322 if self._kg:
323diff --git a/src/metadataserver/builtin_scripts/commissioning_scripts/tests/test_bmc_config.py b/src/metadataserver/builtin_scripts/commissioning_scripts/tests/test_bmc_config.py
324index 4cdcb63..d1674af 100644
325--- a/src/metadataserver/builtin_scripts/commissioning_scripts/tests/test_bmc_config.py
326+++ b/src/metadataserver/builtin_scripts/commissioning_scripts/tests/test_bmc_config.py
327@@ -1,4 +1,4 @@
328-# Copyright 2020 Canonical Ltd. This software is licensed under the
329+# Copyright 2020-2021 Canonical Ltd. This software is licensed under the
330 # GNU Affero General Public License version 3 (see the file LICENSE).
331
332 """Test bmc_config functions."""
333@@ -641,280 +641,182 @@ EndSection
334
335 self.assertThat(mock_bmc_set_keys, MockNotCalled())
336
337- def test_get_bmc_config_cipher_suite_ids(self):
338- self.ipmi._bmc_config = {
339- "Rmcpplus_Conf_Privilege": {
340- "Maximum_Privilege_Cipher_Suite_Id_0": "Administrator",
341- "Maximum_Privilege_Cipher_Suite_Id_17": "Unused",
342- "Maximum_Privilege_Cipher_Suite_Id_3": "Unused",
343- "Maximum_Privilege_Cipher_Suite_Id_42": "Unused",
344- "foo": "bar",
345- }
346- }
347+ def test_get_ipmitool_cipher_suite_ids(self):
348+ supported_cipher_suite_ids = [
349+ i for i in range(0, 20) if factory.pick_bool()
350+ ]
351+ cipher_suite_privs = "".join(
352+ [
353+ random.choice(["X", "c", "u", "o", "a", "O"])
354+ for _ in range(0, 16)
355+ ]
356+ )
357+ ipmitool_output = (
358+ # Validate bmc-config ignores lines which are not key value
359+ # pairs.
360+ factory.make_string()
361+ + "\n"
362+ # Validate bmc-config ignores unknown key value pairs.
363+ + factory.make_string()
364+ + " : "
365+ + factory.make_string()
366+ + "\n"
367+ + "RMCP+ Cipher Suites : "
368+ + ",".join([str(i) for i in supported_cipher_suite_ids])
369+ + "\n"
370+ + "Cipher Suite Priv Max : "
371+ + cipher_suite_privs
372+ + "\n"
373+ + factory.make_string()
374+ + " : "
375+ + factory.make_string()
376+ + "\n"
377+ )
378+ self.patch(self.ipmi, "_get_ipmitool_lan_print").return_value = (
379+ random.randint(0, 10),
380+ ipmitool_output,
381+ )
382
383 (
384- max_cipher_suite_id,
385- cipher_suite_ids,
386- ) = self.ipmi._get_bmc_config_cipher_suite_ids()
387+ detected_cipher_suite_ids,
388+ detected_cipher_suite_privs,
389+ ) = self.ipmi._get_ipmitool_cipher_suite_ids()
390
391- self.assertEqual(17, max_cipher_suite_id)
392 self.assertEqual(
393- {
394- "0": "Administrator",
395- "17": "Unused",
396- "3": "Unused",
397- },
398- cipher_suite_ids,
399- )
400-
401- def test_config_bmc_config_cipher_suite_ids(self):
402- mock_bmc_set = self.patch(self.ipmi, "_bmc_set")
403- cipher_suite_ids = {
404- "3": "Unused",
405- "17": "Unused",
406- "0": "Administrator",
407- }
408-
409- self.ipmi._config_bmc_config_cipher_suite_ids(cipher_suite_ids)
410-
411- self.assertThat(
412- mock_bmc_set,
413- MockCallsMatch(
414- call(
415- "Rmcpplus_Conf_Privilege",
416- "Maximum_Privilege_Cipher_Suite_Id_17",
417- "Administrator",
418- ),
419- call(
420- "Rmcpplus_Conf_Privilege",
421- "Maximum_Privilege_Cipher_Suite_Id_0",
422- "Unused",
423- ),
424- ),
425+ supported_cipher_suite_ids,
426+ detected_cipher_suite_ids,
427+ ipmitool_output,
428 )
429- self.assertEqual("17", self.ipmi._cipher_suite_id)
430-
431- def test_config_bmc_config_cipher_suite_ids_does_nothing(self):
432- mock_bmc_set = self.patch(self.ipmi, "_bmc_set")
433- cipher_suite_ids = {
434- "0": "Unused",
435- "3": "Unused",
436- "17": "Administrator",
437- }
438-
439- self.ipmi._config_bmc_config_cipher_suite_ids(cipher_suite_ids)
440-
441- self.assertThat(mock_bmc_set, MockNotCalled())
442- self.assertEqual("17", self.ipmi._cipher_suite_id)
443-
444- def test_config_bmc_config_cipher_suite_ids_ignores_failures(self):
445- mock_bmc_set = self.patch(self.ipmi, "_bmc_set")
446- mock_bmc_set.side_effect = random.choice(
447- [
448- CalledProcessError(
449- cmd="cmd", returncode=random.randint(1, 255)
450- ),
451- TimeoutExpired(cmd="cmd", timeout=random.randint(1, 100)),
452- ]
453+ self.assertEqual(
454+ cipher_suite_privs, detected_cipher_suite_privs, ipmitool_output
455 )
456- cipher_suite_ids = {
457- "3": "Unused",
458- "17": "Unused",
459- "0": "Administrator",
460- }
461-
462- self.ipmi._config_bmc_config_cipher_suite_ids(cipher_suite_ids)
463-
464- self.assertEqual("", self.ipmi._cipher_suite_id)
465
466- def test_get_ipmitool_cipher_suite_ids(self):
467- mock_get_ipmitool_lan_print = self.patch(
468- self.ipmi, "_get_ipmitool_lan_print"
469- )
470- mock_get_ipmitool_lan_print.return_value = (
471- "2",
472- "RMCP+ Cipher Suites : 0,3,17\n"
473- "Cipher Suite Priv Max : aXXXXXXXXXXXXXX\n",
474+ def test_get_ipmitool_cipher_suite_ids_ignores_bad_data(self):
475+ self.patch(self.ipmi, "_get_ipmitool_lan_print").return_value = (
476+ random.randint(0, 10),
477+ "RMCP+ Cipher Suites : abc\n",
478 )
479
480 (
481- max_cipher_suite_id,
482- supported_cipher_suite_ids,
483- cipher_suite_privs,
484+ detected_cipher_suite_ids,
485+ detected_cipher_suite_privs,
486 ) = self.ipmi._get_ipmitool_cipher_suite_ids()
487
488- self.assertEqual(17, max_cipher_suite_id)
489- self.assertEqual([0, 3, 17], supported_cipher_suite_ids)
490- self.assertEqual("aXXXXXXXXXXXXXX", cipher_suite_privs)
491+ self.assertEqual([], detected_cipher_suite_ids)
492+ self.assertIsNone(detected_cipher_suite_privs)
493
494- def test_get_ipmitool_cipher_suite_ids_ignores_invalid(self):
495- mock_get_ipmitool_lan_print = self.patch(
496- self.ipmi, "_get_ipmitool_lan_print"
497- )
498- mock_get_ipmitool_lan_print.return_value = (
499- "2",
500- "RMCP+ Cipher Suites : Not Available\n"
501- "Cipher Suite Priv Max : Not Available\n",
502+ def test_get_ipmitool_cipher_suite_ids_returns_none_when_not_found(self):
503+ self.patch(self.ipmi, "_get_ipmitool_lan_print").return_value = (
504+ random.randint(0, 10),
505+ factory.make_string() + " : " + factory.make_string() + "\n",
506 )
507
508 (
509- max_cipher_suite_id,
510- supported_cipher_suite_ids,
511- cipher_suite_privs,
512+ detected_cipher_suite_ids,
513+ detected_cipher_suite_privs,
514 ) = self.ipmi._get_ipmitool_cipher_suite_ids()
515
516- self.assertEqual(0, max_cipher_suite_id)
517- self.assertEqual([], supported_cipher_suite_ids)
518- self.assertEqual("", cipher_suite_privs)
519+ self.assertEqual([], detected_cipher_suite_ids)
520+ self.assertIsNone(detected_cipher_suite_privs)
521
522- def test_config_ipmitool_cipher_suite_ids(self):
523- mock_get_ipmitool_lan_print = self.patch(
524- self.ipmi, "_get_ipmitool_lan_print"
525- )
526- mock_get_ipmitool_lan_print.return_value = (
527- "2",
528- factory.make_name("output"),
529+ def test_configure_ipmitool_cipher_suite_ids(self):
530+ channel = random.randint(0, 10)
531+ self.patch(self.ipmi, "_get_ipmitool_lan_print").return_value = (
532+ channel,
533+ "",
534 )
535
536- self.ipmi._config_ipmitool_cipher_suite_ids(
537- 17, [0, 3, 17], "aXXXXXXXXXXXXXX"
538+ new_cipher_suite_privs = (
539+ self.ipmi._configure_ipmitool_cipher_suite_ids(
540+ 3, "aaaXaaaaaaaaaaaaa"
541+ )
542 )
543
544- self.assertThat(
545- self.mock_check_call,
546- MockCalledOnceWith(
547- [
548- "ipmitool",
549- "lan",
550- "set",
551- "2",
552- "cipher_privs",
553- "XXaXXXXXXXXXXXX",
554- ],
555- timeout=60,
556- ),
557+ self.assertEqual("XXXaXXXXaXXXaXXXX", new_cipher_suite_privs)
558+ self.mock_check_call.assert_called_once_with(
559+ [
560+ "ipmitool",
561+ "lan",
562+ "set",
563+ channel,
564+ "cipher_privs",
565+ "XXXaXXXXaXXXaXXXX",
566+ ],
567+ timeout=60,
568 )
569- self.assertEqual("17", self.ipmi._cipher_suite_id)
570
571- def test_config_ipmitool_cipher_suite_ids_does_nothing(self):
572- mock_get_ipmitool_lan_print = self.patch(
573- self.ipmi, "_get_ipmitool_lan_print"
574- )
575- mock_get_ipmitool_lan_print.return_value = (
576- "2",
577- factory.make_name("output"),
578+ def test_configure_ipmitool_cipher_suite_ids_does_nothing_when_set(self):
579+ channel = random.randint(0, 10)
580+ self.patch(self.ipmi, "_get_ipmitool_lan_print").return_value = (
581+ channel,
582+ "",
583 )
584
585- self.ipmi._config_ipmitool_cipher_suite_ids(
586- 17, [0, 3, 17], "XXaXXXXXXXXXXXX"
587+ new_cipher_suite_privs = (
588+ self.ipmi._configure_ipmitool_cipher_suite_ids(
589+ 3, "XXXaXXXXXXXXXXXX"
590+ )
591 )
592
593- self.assertThat(self.mock_check_call, MockNotCalled())
594- self.assertEqual("17", self.ipmi._cipher_suite_id)
595+ self.assertEqual("XXXaXXXXXXXXXXXX", new_cipher_suite_privs)
596+ self.mock_check_call.assert_not_called()
597
598- def test_config_ipmitool_cipher_suite_ids_ignores_errors(self):
599- self.mock_check_call.side_effect = random.choice(
600- [
601- CalledProcessError(
602- cmd="cmd", returncode=random.randint(1, 255)
603- ),
604- TimeoutExpired(cmd="cmd", timeout=random.randint(1, 100)),
605- ]
606- )
607- mock_get_ipmitool_lan_print = self.patch(
608- self.ipmi, "_get_ipmitool_lan_print"
609- )
610- mock_get_ipmitool_lan_print.return_value = (
611- "2",
612- factory.make_name("output"),
613+ def test_config_cipher_suite_id(self):
614+ self.patch(self.ipmi, "_get_ipmitool_lan_print").return_value = (
615+ random.randint(0, 10),
616+ (
617+ "RMCP+ Cipher Suites : 0,3,17\n"
618+ + "Cipher Suite Priv Max : XXXaXXXXXXXXXXXX\n"
619+ ),
620 )
621
622- self.ipmi._config_ipmitool_cipher_suite_ids(
623- 17, [0, 3, 17], "aXXXXXXXXXXXXXX"
624- )
625+ self.ipmi._config_cipher_suite_id()
626
627- self.assertThat(
628- self.mock_check_call,
629- MockCalledOnceWith(
630- [
631- "ipmitool",
632- "lan",
633- "set",
634- "2",
635- "cipher_privs",
636- "XXaXXXXXXXXXXXX",
637- ],
638- timeout=60,
639- ),
640- )
641- self.assertEqual("", self.ipmi._cipher_suite_id)
642+ self.assertEqual("17", self.ipmi._cipher_suite_id)
643
644- def test_config_cipher_suite_id_bmc_config(self):
645- self.ipmi._bmc_config = {
646- "Rmcpplus_Conf_Privilege": {
647- "Maximum_Privilege_Cipher_Suite_Id_0": "Administrator",
648- "Maximum_Privilege_Cipher_Suite_Id_3": "Unused",
649- "Maximum_Privilege_Cipher_Suite_Id_17": "Unused",
650- }
651- }
652- mock_get_ipmitool_lan_print = self.patch(
653- self.ipmi, "_get_ipmitool_lan_print"
654- )
655- mock_get_ipmitool_lan_print.return_value = (
656- "2",
657- "RMCP+ Cipher Suites : 0,3,17\n"
658- "Cipher Suite Priv Max : aXXXXXXXXXXXXXX\n",
659- )
660- mock_config_bmc_config_cipher_suite_ids = self.patch(
661- self.ipmi, "_config_bmc_config_cipher_suite_ids"
662- )
663- mock_config_ipmitool_cipher_suite_ids = self.patch(
664- self.ipmi, "_config_ipmitool_cipher_suite_ids"
665+ def test_config_cipher_suite_id_does_nothing_if_not_detected(self):
666+ self.patch(self.ipmi, "_get_ipmitool_lan_print").return_value = (
667+ random.randint(0, 10),
668+ "",
669 )
670
671 self.ipmi._config_cipher_suite_id()
672
673- self.assertThat(
674- mock_config_bmc_config_cipher_suite_ids,
675- MockCalledOnceWith(
676- {"0": "Administrator", "3": "Unused", "17": "Unused"}
677- ),
678- )
679- self.assertThat(mock_config_ipmitool_cipher_suite_ids, MockNotCalled())
680+ self.mock_check_call.assert_not_called()
681+ self.assertEqual("", self.ipmi._cipher_suite_id)
682
683- def test_config_cipher_suite_id_ipmitool(self):
684- # Regression test for
685- # https://discourse.maas.io/t/ipmi-cipher-suite-c17-support/3293/11
686- self.ipmi._bmc_config = {
687- "Rmcpplus_Conf_Privilege": {
688- "Maximum_Privilege_Cipher_Suite_Id_0": "Administrator",
689- "Maximum_Privilege_Cipher_Suite_Id_3": "Unused",
690- }
691- }
692- mock_get_ipmitool_lan_print = self.patch(
693- self.ipmi, "_get_ipmitool_lan_print"
694- )
695- mock_get_ipmitool_lan_print.return_value = (
696- "2",
697- "RMCP+ Cipher Suites : 0,3,17\n"
698- "Cipher Suite Priv Max : aXXXXXXXXXXXXXX\n",
699- )
700- mock_config_bmc_config_cipher_suite_ids = self.patch(
701- self.ipmi, "_config_bmc_config_cipher_suite_ids"
702+ def test_config_cipher_suite_id_doesnt_set_id_on_error(self):
703+ channel = random.randint(0, 10)
704+ self.patch(self.ipmi, "_get_ipmitool_lan_print").return_value = (
705+ channel,
706+ (
707+ "RMCP+ Cipher Suites : 0,3\n"
708+ + "Cipher Suite Priv Max : aXXXXXXXXXXXXXXX\n"
709+ ),
710 )
711- mock_config_ipmitool_cipher_suite_ids = self.patch(
712- self.ipmi, "_config_ipmitool_cipher_suite_ids"
713+ self.mock_check_call.side_effect = random.choice(
714+ [
715+ CalledProcessError(
716+ cmd="cmd", returncode=random.randint(1, 255)
717+ ),
718+ TimeoutExpired(cmd="cmd", timeout=random.randint(1, 100)),
719+ ]
720 )
721
722 self.ipmi._config_cipher_suite_id()
723
724- self.assertThat(
725- mock_config_bmc_config_cipher_suite_ids, MockNotCalled()
726- )
727- self.assertThat(
728- mock_config_ipmitool_cipher_suite_ids,
729- MockCalledOnceWith(17, [0, 3, 17], "aXXXXXXXXXXXXXX"),
730+ self.mock_check_call.assert_called_once_with(
731+ [
732+ "ipmitool",
733+ "lan",
734+ "set",
735+ channel,
736+ "cipher_privs",
737+ "XXXaXXXXXXXXXXXX",
738+ ],
739+ timeout=60,
740 )
741+ self.assertEqual("", self.ipmi._cipher_suite_id)
742
743 def test_config_kg_set(self):
744 mock_bmc_set = self.patch(self.ipmi, "_bmc_set")

Subscribers

People subscribed via source and target branches