Merge ~ltrager/maas:improve_ipmi_cipher_detection_2.9 into maas:2.9
- Git
- lp:~ltrager/maas
- improve_ipmi_cipher_detection_2.9
- Merge into 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) |
Related bugs: |
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
Description of the change
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
1 | diff --git a/src/metadataserver/builtin_scripts/commissioning_scripts/bmc_config.py b/src/metadataserver/builtin_scripts/commissioning_scripts/bmc_config.py |
2 | index 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: |
323 | diff --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 |
324 | index 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") |