1+ #!/usr/bin/env python3
2+ # OpenPOWER Automated Test Project
3+ #
4+ # Contributors Listed Below - COPYRIGHT 2025
5+ # [+] International Business Machines Corp.
6+ # Author: Krishan Gopal Saraswat <krishang@linux.ibm.com>
7+ #
8+ # Licensed under the Apache License, Version 2.0 (the "License");
9+ # you may not use this file except in compliance with the License.
10+ # You may obtain a copy of the License at
11+ #
12+ # http://www.apache.org/licenses/LICENSE-2.0
13+ #
14+ # Unless required by applicable law or agreed to in writing, software
15+ # distributed under the License is distributed on an "AS IS" BASIS,
16+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
17+ # implied. See the License for the specific language governing
18+ # permissions and limitations under the License.
19+
20+ '''
21+ This test validates the Dynamic Key Guest Secure Boot process by
22+ checking kernel configurations, verifying kernel and GRUB signatures,
23+ setting up the required environment, and toggling the secure boot
24+ state on and off.
25+ '''
26+
27+ import unittest
28+ import os
29+ import time
30+ import OpTestConfiguration
31+ import OpTestLogger
32+ from common .OpTestUtil import OpTestUtil
33+ import shlex
34+
35+ log = OpTestLogger .optest_logger_glob .get_logger (__name__ )
36+
37+ class DynamicKeyGuestSecureBoot (unittest .TestCase ):
38+ def setUp (self ):
39+ self .conf = OpTestConfiguration .conf
40+ self .util = OpTestUtil (OpTestConfiguration .conf )
41+ self .cv_HOST = self .conf .host ()
42+ self .cv_SYSTEM = self .conf .system ()
43+ # HMC object for performing HMC-level operations
44+ try :
45+ self .cv_HMC = self .cv_SYSTEM .hmc
46+ except Exception :
47+ self .cv_HMC = None
48+ self .connection = self .cv_SYSTEM .cv_HOST .get_ssh_connection ()
49+ self .host_cmd_timeout = self .conf .args .host_cmd_timeout
50+ self .kernel_version = self .connection .run_command ("cd /boot && uname -r" )[0 ]
51+
52+ def check_kernel_config (self ):
53+ try :
54+ # List of required kernel configurations
55+ required_configs = [
56+ "CONFIG_PPC_MEM_KEYS" ,
57+ "CONFIG_TRUSTED_KEYS" ,
58+ "CONFIG_PPC_SECURE_BOOT" ,
59+ "CONFIG_IMA_SECURE_AND_OR_TRUSTED_BOOT" ,
60+ "CONFIG_IMA_MEASURE_ASYMMETRIC_KEYS" ,
61+ "CONFIG_LOAD_PPC_KEYS" ,
62+ "CONFIG_INTEGRITY_TRUSTED_KEYRING" ,
63+ "CONFIG_INTEGRITY_PLATFORM_KEYRING" ,
64+ "CONFIG_INTEGRITY_MACHINE_KEYRING" ,
65+ "CONFIG_SECONDARY_TRUSTED_KEYRING" ,
66+ "CONFIG_SYSTEM_BLACKLIST_KEYRING" ,
67+ "CONFIG_MODULE_SIG_KEY_TYPE_RSA"
68+ ]
69+
70+ # First check if kernel config file exists
71+ config_file = f"/boot/config-{ self .kernel_version } "
72+ try :
73+ self .cv_HOST .host_run_command (f"test -e { config_file } " )
74+ log .info (f"Found kernel config file: { config_file } " )
75+ except Exception as e :
76+ self .fail (f"Kernel config file not found at { config_file } : { str (e )} " )
77+
78+ # Check each required configuration
79+ missing_configs = []
80+ for config in required_configs :
81+ try :
82+ result = self .cv_HOST .host_check_config (self .kernel_version , config )
83+ if result not in ['y' , 'm' ]:
84+ missing_configs .append (f"{ config } (not set)" )
85+ else :
86+ log .info (f"Found { config } ={ result } " )
87+ except Exception as e :
88+ if "not set" in str (e ):
89+ missing_configs .append (f"{ config } (not set)" )
90+ else :
91+ log .error (f"Error checking config { config } : { str (e )} " )
92+ missing_configs .append (f"{ config } (error: { str (e )} )" )
93+
94+ # If any configurations are missing, fail the test with detailed message
95+ if missing_configs :
96+ error_msg = "Missing or incorrectly configured kernel options:\n "
97+ for config in missing_configs :
98+ error_msg += f"- { config } \n "
99+ error_msg += "\n Please ensure these kernel configurations are enabled (y) or built as modules (m)."
100+ self .fail (error_msg )
101+
102+ log .info ("All required kernel configurations are present" )
103+
104+ except Exception as e :
105+ self .fail (f"Error checking kernel configurations: { str (e )} " )
106+
107+
108+ def kernel_grub_signature_check (self ):
109+ """
110+ Check kernel and grub signatures.
111+ """
112+ try :
113+ if not getattr (self , 'util' , None ):
114+ self .util = OpTestUtil (self .conf )
115+ self .kernel_signature = self .util .check_kernel_signature ()
116+ self .grub_filename = self .util .get_grub_file ()
117+ try :
118+ self .grub_signature = self .util .check_grub_signature (self .grub_filename )
119+ except Exception as e :
120+ log .error ("Grub signature check failed for '%s': %s" , self .grub_filename , e )
121+ self .fail (f"Grub signature check failed for '{ self .grub_filename } ': { e } " )
122+ log .info ("Kernel signed: %s, Grub file: %s, Grub signed: %s" ,
123+ self .kernel_signature , self .grub_filename , self .grub_signature )
124+ return True
125+
126+ except Exception as e :
127+ log .error ("Kernel/Grub signature check failed: %s" , e )
128+ self .fail (f"Kernel/Grub signature check failed: { e } " )
129+
130+ def execute_hmc_command (self , command ):
131+ """
132+ Execute HMC command and handle errors
133+ """
134+ self .ensure_hmc_available ()
135+ try :
136+ result = self .cv_HMC .run_command (command )
137+ log .info (f"Command executed successfully on HMC: { command } " )
138+ return result
139+ except Exception as e :
140+ error_msg = f"Failed to execute HMC command on HMC '{ command } ': { str (e )} "
141+ log .error (error_msg )
142+ self .fail (error_msg )
143+
144+ def ensure_hmc_available (self ):
145+ """
146+ Ensure the HMC object is available.
147+ """
148+ if not getattr (self , 'cv_HMC' , None ):
149+ msg = (
150+ "HMC object is not available (self.cv_HMC is None). "
151+ )
152+ log .error (msg )
153+ self .fail (msg )
154+
155+ def get_secure_boot_status (self , host_name , system_name ):
156+ """
157+ Get current secure boot and keystore status
158+ """
159+ self .ensure_hmc_available ()
160+ command = f"lssyscfg -r lpar -m { system_name } --filter \" lpar_names={ host_name } \" | sed s/,/\\ \n /g | grep \" secure_boot\\ |keystore\" "
161+ return self .execute_hmc_command (command )
162+
163+ def shutdown_lpar (self , host_name , system_name , sleep_time = 5 ):
164+ """
165+ Shutdown LPAR and wait
166+ """
167+ self .ensure_hmc_available ()
168+ command = f"chsysstate -o shutdown -r lpar -n { host_name } -m { system_name } "
169+ self .execute_hmc_command (command )
170+ time .sleep (sleep_time )
171+
172+ def start_lpar (self , host_name , system_name , sleep_time = 5 ):
173+ """
174+ Start LPAR and wait
175+ """
176+ self .ensure_hmc_available ()
177+ command = f"chsysstate -o on -r lpar -n { host_name } -m { system_name } "
178+ self .execute_hmc_command (command )
179+ time .sleep (sleep_time )
180+
181+ def configure_secure_boot (self , host_name , system_name , config_params ):
182+ """
183+ Configure secure boot parameters
184+ """
185+ self .ensure_hmc_available ()
186+ command = f"chsyscfg -r lpar -m { system_name } -i \" name={ host_name } , { config_params } \" "
187+ self .execute_hmc_command (command )
188+
189+ def setup_dynamic_secure_boot (self , host_name , system_name ):
190+ """
191+ Setup dynamic secure boot environment
192+ """
193+ try :
194+ log .info (f"Starting dynamic secure boot setup for host: { host_name } , system: { system_name } " )
195+ # Ensure HMC is available
196+ self .ensure_hmc_available ()
197+ # Step: Shutdown LPAR and wait ~30s
198+ self .shutdown_lpar (host_name , system_name , sleep_time = 30 )
199+ # Configure combined secure-boot parameters
200+ config_params = (
201+ "keystore_signed_updates_without_verification=1,"
202+ "keystore_signed_updates=1,"
203+ "linux_dynamic_key_secure_boot=1,"
204+ "keystore_kbytes=64"
205+ )
206+ self .configure_secure_boot (host_name , system_name , config_params )
207+ # Allow the HMC to settle
208+ time .sleep (10 )
209+ # Get and log current status
210+ status = self .get_secure_boot_status (host_name , system_name )
211+ log .info ("Current secure boot status:" )
212+ for line in status :
213+ log .info (line )
214+ # Start the LPAR
215+ self .start_lpar (host_name , system_name )
216+ log .info ("Dynamic secure boot environment setup completed successfully" )
217+ except Exception as e :
218+ error_msg = f"Failed to setup dynamic secure boot environment: { str (e )} "
219+ log .error (error_msg )
220+ self .fail (error_msg )
221+
222+ def reset_secure_boot (self , host_name , system_name ):
223+ """
224+ Reset all secure boot settings to zero by running HMC commands to
225+ configure the LPAR secure-boot-related parameters to zero.
226+ """
227+ try :
228+ log .info (f"Resetting secure boot settings to zero for host: { host_name } , system: { system_name } " )
229+
230+ # Run guest-side secvarctl to generate auth files.
231+ from testcases .OpTestSecvarctl import SecvarctlTest
232+ sec = SecvarctlTest ()
233+ sec .connection = self .connection
234+
235+ # Search for the parent workspace that contains the cloned repo
236+ try_paths = []
237+ if getattr (self , 'home' , None ):
238+ try_paths .append (os .path .join (self .home , 'secvarctl_*' ))
239+ try_paths .append ('/home/secvarctl_*' )
240+
241+ repo_root = None
242+ for p in try_paths :
243+ rc = self .connection .run_command (f"bash -c 'ls -d { p } 2>/dev/null | head -n1' || true" )
244+ if rc and rc [0 ].strip ():
245+ repo_root = rc [0 ].strip ()
246+ break
247+
248+ auth_dir = None
249+ if repo_root :
250+ candidate = os .path .join (repo_root , 'secvarctl' , 'test' , 'testdata' , 'guest' , 'authfiles' )
251+ rc = self .connection .run_command (f"bash -c 'ls -d { candidate } 2>/dev/null | head -n1' || true" )
252+ if rc and rc [0 ].strip ():
253+ auth_dir = rc [0 ].strip ()
254+
255+ if auth_dir :
256+ log .info (f"Found authfiles at { auth_dir } ; running secvarctl write PK reset_PK_by_PK.auth" )
257+ try :
258+ cmd = f"bash -c 'cd { shlex .quote (auth_dir )} && secvarctl write PK reset_PK_by_PK.auth'"
259+ out = self .connection .run_command (cmd )
260+ for l in out :
261+ log .info (l )
262+ except Exception as e :
263+ self .fail (f"Failed to run secvarctl write on guest: { e } " )
264+ else :
265+ log .warning ("secvarctl authfiles directory not found" )
266+
267+ # After PK reset, zero the HMC secure-boot parameters
268+ self .ensure_hmc_available ()
269+ # Shutdown LPAR and wait ~30s
270+ self .shutdown_lpar (host_name , system_name , sleep_time = 30 )
271+ # Configure secure-boot related params to zero
272+ config_params = (
273+ "keystore_signed_updates_without_verification=0,"
274+ "keystore_signed_updates=0,"
275+ "linux_dynamic_key_secure_boot=0,"
276+ "keystore_kbytes=0"
277+ )
278+ self .configure_secure_boot (host_name , system_name , config_params )
279+ # Allow HMC to settle
280+ time .sleep (10 )
281+ # Get and log current status
282+ output = self .get_secure_boot_status (host_name , system_name )
283+ log .info ("Current secure boot status after resetting to zero:" )
284+ for line in output :
285+ log .info (line )
286+ # Start the LPAR
287+ self .start_lpar (host_name , system_name )
288+
289+ # Cleanup via SecvarctlTest.tearDown()
290+ try :
291+ if repo_root :
292+ sec .home = repo_root
293+ sec .tearDown ()
294+ except Exception as e :
295+ log .warning (f"secvarctl tearDown reported an error: { e } " )
296+
297+ log .info ("Secure boot reset completed and secvarctl artifacts cleaned up" )
298+ except Exception as e :
299+ error_msg = f"Failed to reset secure boot settings: { str (e )} "
300+ log .error (error_msg )
301+ self .fail (error_msg )
302+
303+ def enable_secure_boot (self , host_name , system_name ):
304+ """
305+ Enable secure boot (set to mode 2)
306+ """
307+ try :
308+ log .info (f"Enabling secure boot for host: { host_name } , system: { system_name } " )
309+ self .shutdown_lpar (host_name , system_name )
310+ self .configure_secure_boot (host_name , system_name , "secure_boot=2" )
311+ self .start_lpar (host_name , system_name )
312+ log .info ("Secure boot enabled successfully" )
313+
314+ except Exception as e :
315+ error_msg = f"Failed to enable secure boot: { str (e )} "
316+ log .error (error_msg )
317+ self .fail (error_msg )
318+
319+ def disable_secure_boot (self , host_name , system_name ):
320+ """
321+ Disable secure boot (set to mode 0)
322+ """
323+ try :
324+ log .info (f"Disabling secure boot for host: { host_name } , system: { system_name } " )
325+ self .shutdown_lpar (host_name , system_name , sleep_time = 30 )
326+ self .configure_secure_boot (host_name , system_name , "secure_boot=0" )
327+ time .sleep (5 )
328+ self .start_lpar (host_name , system_name )
329+ log .info ("Secure boot disabled successfully" )
330+ except Exception as e :
331+ error_msg = f"Failed to disable secure boot: { str (e )} "
332+ log .error (error_msg )
333+ self .fail (error_msg )
334+
335+ def runTest (self ):
336+ # Get host and system names from configuration
337+ host_name = self .conf .args .lpar_name
338+ system_name = self .conf .args .system_name
339+ # Check required kernel configurations are present
340+ self .check_kernel_config ()
341+ # Check kernel and grub signatures
342+ self .kernel_grub_signature_check ()
343+ # Setup dynamic secure boot
344+ self .setup_dynamic_secure_boot (host_name , system_name )
345+ # Enable secure boot
346+ self .enable_secure_boot (host_name , system_name )
347+ # Reset secure boot
348+ self .reset_secure_boot (host_name , system_name )
349+ # Disable secure boot
350+ self .disable_secure_boot (host_name , system_name )
0 commit comments