Message ID | 20210728162716.79507-1-dimitri.ledkov@canonical.com |
---|---|
State | New |
Headers | show |
Series | [autotest-client-tests] UBUNTU: SAUCE: ubuntu_boot: implement revocation list checks | expand |
This feature looks like a great addition, however, giving how critical the ubuntu_boot a-c-t is (This test is now a gate for initial kernel builds), we need to be careful how we update it. Upon testing this patch against a test harness using Focal amd64 vm: 01:08:17 INFO | START ubuntu_boot.kernel_revocation_list ubuntu_boot.kernel_revocation_list timestamp=1627520897 timeout=300 localtime=Jul 29 01:08:17 01:08:17 DEBUG| Persistent state client._record_indent now set to 2 01:08:17 DEBUG| Persistent state client.unexpected_reboot now set to ('ubuntu_boot.kernel_revocation_list', 'ubuntu_boot.kernel_revocation_list') 01:08:17 DEBUG| Waiting for pid 2357 for 300 seconds 01:08:17 WARNI| System python is too old, crash handling disabled 01:08:17 ERROR| Exception escaping from test: Traceback (most recent call last): File "/home/ubuntu/autotest/client/shared/test.py", line 411, in _exec _call_test_function(self.execute, *p_args, **p_dargs) File "/home/ubuntu/autotest/client/shared/test.py", line 830, in _call_test_function raise error.UnhandledTestFail(e) UnhandledTestFail: Unhandled AttributeError: 'tuple' object has no attribute 'release' Traceback (most recent call last): File "/home/ubuntu/autotest/client/shared/test.py", line 823, in _call_test_function return func(*args, **dargs) File "/home/ubuntu/autotest/client/shared/test.py", line 291, in execute postprocess_profiled_run, args, dargs) File "/home/ubuntu/autotest/client/shared/test.py", line 212, in _call_run_once self.run_once(*args, **dargs) File "/home/ubuntu/autotest/client/tests/ubuntu_boot/ubuntu_boot.py", line 97, in run_once self.kernel_revocation_list() File "/home/ubuntu/autotest/client/tests/ubuntu_boot/ubuntu_boot.py", line 60, in kernel_revocation_list config_file = "/boot/config-" + os.uname().release AttributeError: 'tuple' object has no attribute 'release' Full output here: https://pastebin.canonical.com/p/Zr2jjzsNcr/ Unfortunately autotest only supports python2, which is why this dumped an Exception, there is no os.uname().release. I think the best way to handle this: 1.) Re-write it all for python2 (no one wants to do that) 2.) Isolate the function into it's own py3 script and call it from ubuntu_boot.py using something like, utils.system('python3 <my_python3_script>' ). This is probably the easiest, any errors that are returned from the python3 script can be handled in "ubuntu_boot.py" using the autotest.client library. -Sean On 7/28/21 12:27 PM, Dimitri John Ledkov wrote: > Implement revocation list checks. If kernel supports revocation lists, > check that 2012 canonical signing key is revoked. > > Most kernels will skip this test, those kernels that have support for > revocation lists will check that it is correctly configured and > visible at runtime. > > Signed-off-by: Dimitri John Ledkov <dimitri.ledkov@canonical.com> > --- > > Note, tested the function in question partially on v5.10 and v5.13 > kernels. I failed at using the test harness directly to partially > execute this test case alone. Thus I am not sure if it runs with > python3 or python2, as I was getting exceptions raised from autotest > itself. > > ubuntu_boot/control.ubuntu | 1 + > ubuntu_boot/ubuntu_boot.py | 30 +++++++++++++++++++++++++++++- > 2 files changed, 30 insertions(+), 1 deletion(-) > > diff --git a/ubuntu_boot/control.ubuntu b/ubuntu_boot/control.ubuntu > index f73d68c2d3..5f4e3a29bd 100644 > --- a/ubuntu_boot/control.ubuntu > +++ b/ubuntu_boot/control.ubuntu > @@ -11,3 +11,4 @@ DOC = ''' > job.run_test_detail('ubuntu_boot', test_name='log_check', tag='log_check', timeout=60*5) > job.run_test_detail('ubuntu_boot', test_name='boot_smoke_test', tag='boot_smoke_test', timeout=60*5) > job.run_test_detail('ubuntu_boot', test_name='kernel_tainted', tag='kernel_tainted', timeout=60*5) > +job.run_test_detail('ubuntu_boot', test_name='kernel_revocation_list', tag='kernel_revocation_list', timeout=60*5) > diff --git a/ubuntu_boot/ubuntu_boot.py b/ubuntu_boot/ubuntu_boot.py > index a67f21d49f..a986210ad3 100644 > --- a/ubuntu_boot/ubuntu_boot.py > +++ b/ubuntu_boot/ubuntu_boot.py > @@ -8,7 +8,7 @@ from autotest.client.shared import error > class ubuntu_boot(test.test): > version = 1 > def setup(self): > - pkgs = [ 'python3' ] > + pkgs = [ 'python3', 'keyutils' ] > cmd = 'yes "" | DEBIAN_FRONTEND=noninteractive apt-get install --yes --force-yes ' + ' '.join(pkgs) > self.results = utils.system_output(cmd, retain_output=True) > > @@ -58,6 +58,31 @@ class ubuntu_boot(test.test): > result = utils.system('python3 %s/kernel_taint_test.py' % self.bindir, ignore_status=True) > return result > > + def kernel_revocation_list(self): > + '''Test for kernel builtin revoked keys''' > + config_file = "/boot/config-" + os.uname().release > + revocation_list_available = False > + for line in open(config_file): > + if re.search("CONFIG_SYSTEM_REVOCATION_LIST", line): > + revocation_list_available = True > + break > + if not revocation_list_available: > + print('SKIP: Kernel Revocation List NA.') > + raise error.TestNAError() > + revocations = utils.system_output("keyctl list %:.blacklist", retain_output=True) > + patterns = [ > + b'.* asymmetric: Canonical Ltd. Secure Boot Signing: 61482aa2830d0ab2ad5af10b7250da9033ddcef0', > + ] > + missing_patterns = False > + for pat in patterns: > + print('Scanning for pattern "{}"'.format(pat)) > + if not re.search(pat, revocations): > + print('Pattern not found.') > + missing_patterns = True > + if missing_patterns: > + raise error.TestFail() > + print('GOOD: Kernel Revocation List.') > + > def run_once(self, test_name, exit_on_error=True): > if test_name == 'log_check': > if not self.log_check(): > @@ -71,6 +96,9 @@ class ubuntu_boot(test.test): > else: > print('GOOD: Kernel not tainted.') > return > + elif test_name == 'kernel_revocation_list': > + self.kernel_revocation_list() > + return > > cmd = "uname -a" > utils.system(cmd)
On 29/07/2021 03:39, Sean Feole wrote: > This feature looks like a great addition, however, giving how critical > the ubuntu_boot a-c-t is (This test is now a gate for initial kernel > builds), we need to be careful how we update it. > > Upon testing this patch against a test harness using Focal amd64 vm: > > > 01:08:17 INFO | START ubuntu_boot.kernel_revocation_list > ubuntu_boot.kernel_revocation_list timestamp=1627520897 timeout=300 > localtime=Jul 29 01:08:17 01:08:17 DEBUG| Persistent state > client._record_indent now set to 2 01:08:17 DEBUG| Persistent state > client.unexpected_reboot now set to > ('ubuntu_boot.kernel_revocation_list', > 'ubuntu_boot.kernel_revocation_list') 01:08:17 DEBUG| Waiting for pid > 2357 for 300 seconds 01:08:17 WARNI| System python is too old, crash > handling disabled 01:08:17 ERROR| Exception escaping from test: > Traceback (most recent call last): File > "/home/ubuntu/autotest/client/shared/test.py", line 411, in _exec > _call_test_function(self.execute, *p_args, **p_dargs) File > "/home/ubuntu/autotest/client/shared/test.py", line 830, in > _call_test_function raise error.UnhandledTestFail(e) UnhandledTestFail: > Unhandled AttributeError: 'tuple' object has no attribute 'release' > Traceback (most recent call last): File > "/home/ubuntu/autotest/client/shared/test.py", line 823, in > _call_test_function return func(*args, **dargs) File > "/home/ubuntu/autotest/client/shared/test.py", line 291, in execute > postprocess_profiled_run, args, dargs) File > "/home/ubuntu/autotest/client/shared/test.py", line 212, in > _call_run_once self.run_once(*args, **dargs) File > "/home/ubuntu/autotest/client/tests/ubuntu_boot/ubuntu_boot.py", line > 97, in run_once self.kernel_revocation_list() File > "/home/ubuntu/autotest/client/tests/ubuntu_boot/ubuntu_boot.py", line > 60, in kernel_revocation_list config_file = "/boot/config-" + > os.uname().release AttributeError: 'tuple' object has no attribute 'release' > > Full output here: https://pastebin.canonical.com/p/Zr2jjzsNcr/ > > Unfortunately autotest only supports python2, which is why this dumped > an Exception, there is no os.uname().release. I think the best way to > handle this: > > 1.) Re-write it all for python2 (no one wants to do that) > > 2.) Isolate the function into it's own py3 script and call it from > ubuntu_boot.py using something like, utils.system('python3 > <my_python3_script>' ). > > This is probably the easiest, any errors that are returned from the > python3 script can be handled in "ubuntu_boot.py" using the > autotest.client library. > "release" as part of tuple is provided in Python2 and Python3 for backwards compatibility also provides a tuple, so this can be simply old Python2 code. Other popular option for such older code is to wrap it and handle both cases: get_os_uname_release(): try: return os.uname().release except AttributeError: # Do the old style This is quite common pattern for imports which are different between 2 and 3. Best regards, Krzysztof
diff --git a/ubuntu_boot/control.ubuntu b/ubuntu_boot/control.ubuntu index f73d68c2d3..5f4e3a29bd 100644 --- a/ubuntu_boot/control.ubuntu +++ b/ubuntu_boot/control.ubuntu @@ -11,3 +11,4 @@ DOC = ''' job.run_test_detail('ubuntu_boot', test_name='log_check', tag='log_check', timeout=60*5) job.run_test_detail('ubuntu_boot', test_name='boot_smoke_test', tag='boot_smoke_test', timeout=60*5) job.run_test_detail('ubuntu_boot', test_name='kernel_tainted', tag='kernel_tainted', timeout=60*5) +job.run_test_detail('ubuntu_boot', test_name='kernel_revocation_list', tag='kernel_revocation_list', timeout=60*5) diff --git a/ubuntu_boot/ubuntu_boot.py b/ubuntu_boot/ubuntu_boot.py index a67f21d49f..a986210ad3 100644 --- a/ubuntu_boot/ubuntu_boot.py +++ b/ubuntu_boot/ubuntu_boot.py @@ -8,7 +8,7 @@ from autotest.client.shared import error class ubuntu_boot(test.test): version = 1 def setup(self): - pkgs = [ 'python3' ] + pkgs = [ 'python3', 'keyutils' ] cmd = 'yes "" | DEBIAN_FRONTEND=noninteractive apt-get install --yes --force-yes ' + ' '.join(pkgs) self.results = utils.system_output(cmd, retain_output=True) @@ -58,6 +58,31 @@ class ubuntu_boot(test.test): result = utils.system('python3 %s/kernel_taint_test.py' % self.bindir, ignore_status=True) return result + def kernel_revocation_list(self): + '''Test for kernel builtin revoked keys''' + config_file = "/boot/config-" + os.uname().release + revocation_list_available = False + for line in open(config_file): + if re.search("CONFIG_SYSTEM_REVOCATION_LIST", line): + revocation_list_available = True + break + if not revocation_list_available: + print('SKIP: Kernel Revocation List NA.') + raise error.TestNAError() + revocations = utils.system_output("keyctl list %:.blacklist", retain_output=True) + patterns = [ + b'.* asymmetric: Canonical Ltd. Secure Boot Signing: 61482aa2830d0ab2ad5af10b7250da9033ddcef0', + ] + missing_patterns = False + for pat in patterns: + print('Scanning for pattern "{}"'.format(pat)) + if not re.search(pat, revocations): + print('Pattern not found.') + missing_patterns = True + if missing_patterns: + raise error.TestFail() + print('GOOD: Kernel Revocation List.') + def run_once(self, test_name, exit_on_error=True): if test_name == 'log_check': if not self.log_check(): @@ -71,6 +96,9 @@ class ubuntu_boot(test.test): else: print('GOOD: Kernel not tainted.') return + elif test_name == 'kernel_revocation_list': + self.kernel_revocation_list() + return cmd = "uname -a" utils.system(cmd)
Implement revocation list checks. If kernel supports revocation lists, check that 2012 canonical signing key is revoked. Most kernels will skip this test, those kernels that have support for revocation lists will check that it is correctly configured and visible at runtime. Signed-off-by: Dimitri John Ledkov <dimitri.ledkov@canonical.com> --- Note, tested the function in question partially on v5.10 and v5.13 kernels. I failed at using the test harness directly to partially execute this test case alone. Thus I am not sure if it runs with python3 or python2, as I was getting exceptions raised from autotest itself. ubuntu_boot/control.ubuntu | 1 + ubuntu_boot/ubuntu_boot.py | 30 +++++++++++++++++++++++++++++- 2 files changed, 30 insertions(+), 1 deletion(-)