Skip to content

feat: add test for secure boot key enrollment in setup mode#704

Open
adsuna wants to merge 1 commit intovirt-s1:masterfrom
adsuna:dev
Open

feat: add test for secure boot key enrollment in setup mode#704
adsuna wants to merge 1 commit intovirt-s1:masterfrom
adsuna:dev

Conversation

@adsuna
Copy link
Copy Markdown
Contributor

@adsuna adsuna commented Mar 30, 2026

Add test_uefi_enable_secureboot to test_lifecycle.py to cover enabling Secure Boot on UEFI systems in setup mode.

Test steps:

  • verifies the system is UEFI boot and in setup mode
  • installs required packages (openssl, efivar, python3-virt-firmware, edk2-ovmf)
  • generates a custom db certificate to validate the full enrollment pipeline
  • uses virt-fw-vars --enroll-redhat to build a UEFI variable store with RH PK/KEK, Microsoft (2011+2023) db certs, the custom db cert, and dbx list.
  • converts vars to esl format and writes to the UEFI db.
  • reboots and verifies secure boot is enabled and verifies custom db is present in the keyring.
  • add teardown cleanup function for the test.

Summary by Sourcery

Add a lifecycle test that enables Secure Boot on UEFI systems in setup mode and adjusts teardown to fully recreate the VM after this test.

New Features:

  • Add test_uefi_enable_secureboot to validate enabling Secure Boot via virt-fw-vars enrollment of Red Hat and Microsoft keys plus a custom db certificate.

Tests:

  • Introduce an end-to-end Secure Boot enrollment test that provisions UEFI variables, reboots, and verifies Secure Boot state and custom certificate presence.
  • Extend TestLifeCycle.tearDown to recreate the VM after running the Secure Boot enrollment test to restore a clean environment.

Signed-off-by: Aditya Nair <adnair@redhat.com>
@sourcery-ai
Copy link
Copy Markdown

sourcery-ai bot commented Mar 30, 2026

Reviewer's Guide

Adds a new lifecycle test that enables Secure Boot on UEFI systems running in setup mode using virt-fw-vars/efivar, and introduces a targeted teardown path to fully recreate the VM after this test runs.

File-Level Changes

Change Details Files
Add UEFI Secure Boot enrollment lifecycle test that provisions PK/KEK/db/dbx via virt-fw-vars and efivar, reboots, and validates Secure Boot and custom db key presence.
  • Introduce test_uefi_enable_secureboot method with detailed metadata and documentation of steps
  • Check for UEFI boot and Setup Mode using sysfs and mokutil, skipping if prerequisites are not met
  • Install required tooling (openssl, efivar, keyutils, python3-virt-firmware, edk2-ovmf) via dnf
  • Generate a temporary working directory, a custom db RSA certificate, and GUID for enrollment
  • Use virt-fw-vars with --enroll-redhat, custom db certificate, and latest DBXUpdate binary to build UEFI VARS image
  • Convert auth files to ESL format and write PK, KEK, db, and dbx variables using efivar
  • Reboot SUT, re-establish SSH connection, assert Secure Boot is enabled, and verify custom cert in kernel keyring
  • Clean up temporary working directory at the end of the test
os_tests/tests/test_lifecycle.py
Extend tearDown to fully recreate the VM after running the UEFI Secure Boot enablement test to restore firmware state.
  • Add conditional branch in tearDown that detects test_uefi_enable_secureboot by test id
  • If VM exists, delete and recreate it, then reinitialize SSH connection
os_tests/tests/test_lifecycle.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Copy Markdown

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 2 issues, and left some high level feedback:

  • The new tearDown branch for test_uefi_enable_secureboot duplicates the existing vm_delete VM-recreation logic; consider consolidating this into a shared helper or a single conditional path to avoid repetition and reduce chances of future divergence.
  • Within test_uefi_enable_secureboot, there are multiple cd {work_dir} && ... command invocations; wrapping those operations in a small helper or building absolute paths (e.g., using os.path.join) would make the test easier to read and less error-prone if the working directory changes.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The new tearDown branch for `test_uefi_enable_secureboot` duplicates the existing `vm_delete` VM-recreation logic; consider consolidating this into a shared helper or a single conditional path to avoid repetition and reduce chances of future divergence.
- Within `test_uefi_enable_secureboot`, there are multiple `cd {work_dir} && ...` command invocations; wrapping those operations in a small helper or building absolute paths (e.g., using `os.path.join`) would make the test easier to read and less error-prone if the working directory changes.

## Individual Comments

### Comment 1
<location path="os_tests/tests/test_lifecycle.py" line_range="1014-1015" />
<code_context>
+        self.assertIn('SecureBoot enabled', sb_state_after,
+                      msg='Secure Boot should be enabled after reboot, got: {}'.format(sb_state_after))
+
+        utils_lib.run_cmd(self, 'sudo keyctl list %:.platform', expect_ret=0,
+                          expect_kw='Signature Database key',
+                          msg='Verify custom cert in kernel keyring')
+
</code_context>
<issue_to_address>
**suggestion (testing):** The test only validates the custom db cert, not that the expected Microsoft / Red Hat CAs are present in db as described in the test plan.

The `expect_result` docstring says Secure Boot should be enabled with both the custom cert and Microsoft CAs in db, but the test only checks for `Signature Database key` (the custom cert) in the keyring. Please extend the assertions to also confirm the expected Microsoft/Red Hat certs are present (e.g., by grepping for known subjects or key IDs) so the test validates the full enrollment pipeline, including vendor keys.

Suggested implementation:

```python
        self.assertIn('SecureBoot enabled', sb_state_after,
                      msg='Secure Boot should be enabled after reboot, got: {}'.format(sb_state_after))

        # Verify custom and vendor certificates (Microsoft / Red Hat) are present in the platform keyring
        keyring_output = utils_lib.run_cmd(
            self,
            'sudo keyctl list %:.platform',
            expect_ret=0,
            msg='List platform keyring to verify enrolled certificates'
        )

        # Custom DB certificate from the test
        self.assertIn(
            'Signature Database key',
            keyring_output,
            msg='Custom DB certificate (Signature Database key) should be present in the platform keyring, got: {}'.format(
                keyring_output
            ),
        )

        # Vendor certificates – validate that both Microsoft and Red Hat keys are present to
        # cover the full enrollment pipeline as described in the test plan.
        expected_vendor_markers = [
            # Common Microsoft DB cert subjects / labels
            'Microsoft Windows Production PCA',
            'Microsoft Corporation UEFI CA',
            # Common Red Hat Secure Boot DB cert subjects / labels
            'Red Hat Secure Boot CA',
            'Red Hat Secure Boot (CA key 1)',
        ]

        for marker in expected_vendor_markers:
            if marker in keyring_output:
                continue
        self.assertTrue(
            any(marker in keyring_output for marker in expected_vendor_markers if 'Microsoft' in marker),
            msg='Expected at least one Microsoft Secure Boot DB certificate in platform keyring, got: {}'.format(
                keyring_output
            ),
        )
        self.assertTrue(
            any(marker in keyring_output for marker in expected_vendor_markers if 'Red Hat' in marker),
            msg='Expected at least one Red Hat Secure Boot DB certificate in platform keyring, got: {}'.format(
                keyring_output
            ),
        )

```

1. If your environment uses different subject strings or labels for the Microsoft / Red Hat DB certificates, update the `expected_vendor_markers` list to match the actual key names as they appear in `keyctl list %:.platform`.
2. The small `for marker in expected_vendor_markers` loop is currently only used to short-circuit on matches; you can remove it if you prefer and rely solely on the two `assertTrue(any(...))` checks.
3. If `utils_lib.run_cmd` already supports a list for `expect_kw`, you could alternatively pass `expect_kw=[...]` instead of doing manual `assertIn` checks, but the current implementation is explicit and independent of that behavior.
</issue_to_address>

### Comment 2
<location path="os_tests/tests/test_lifecycle.py" line_range="963-972" />
<code_context>
+        work_dir = '/tmp/secureboot_setup'
</code_context>
<issue_to_address>
**suggestion (testing):** Test leaves artifacts and changed state if it fails before the final cleanup; consider using a `try/finally` or `addCleanup`.

Because the temp directory and certs are only removed on success, any failure after `work_dir` is created will leave them behind and can affect later test runs. Using a `try/finally` or `self.addCleanup` to register cleanup for the directory and other temp resources will keep the test isolated and avoid state leakage, which is important given it modifies UEFI vars and keys.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +1014 to +1015
utils_lib.run_cmd(self, 'sudo keyctl list %:.platform', expect_ret=0,
expect_kw='Signature Database key',
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (testing): The test only validates the custom db cert, not that the expected Microsoft / Red Hat CAs are present in db as described in the test plan.

The expect_result docstring says Secure Boot should be enabled with both the custom cert and Microsoft CAs in db, but the test only checks for Signature Database key (the custom cert) in the keyring. Please extend the assertions to also confirm the expected Microsoft/Red Hat certs are present (e.g., by grepping for known subjects or key IDs) so the test validates the full enrollment pipeline, including vendor keys.

Suggested implementation:

        self.assertIn('SecureBoot enabled', sb_state_after,
                      msg='Secure Boot should be enabled after reboot, got: {}'.format(sb_state_after))

        # Verify custom and vendor certificates (Microsoft / Red Hat) are present in the platform keyring
        keyring_output = utils_lib.run_cmd(
            self,
            'sudo keyctl list %:.platform',
            expect_ret=0,
            msg='List platform keyring to verify enrolled certificates'
        )

        # Custom DB certificate from the test
        self.assertIn(
            'Signature Database key',
            keyring_output,
            msg='Custom DB certificate (Signature Database key) should be present in the platform keyring, got: {}'.format(
                keyring_output
            ),
        )

        # Vendor certificates – validate that both Microsoft and Red Hat keys are present to
        # cover the full enrollment pipeline as described in the test plan.
        expected_vendor_markers = [
            # Common Microsoft DB cert subjects / labels
            'Microsoft Windows Production PCA',
            'Microsoft Corporation UEFI CA',
            # Common Red Hat Secure Boot DB cert subjects / labels
            'Red Hat Secure Boot CA',
            'Red Hat Secure Boot (CA key 1)',
        ]

        for marker in expected_vendor_markers:
            if marker in keyring_output:
                continue
        self.assertTrue(
            any(marker in keyring_output for marker in expected_vendor_markers if 'Microsoft' in marker),
            msg='Expected at least one Microsoft Secure Boot DB certificate in platform keyring, got: {}'.format(
                keyring_output
            ),
        )
        self.assertTrue(
            any(marker in keyring_output for marker in expected_vendor_markers if 'Red Hat' in marker),
            msg='Expected at least one Red Hat Secure Boot DB certificate in platform keyring, got: {}'.format(
                keyring_output
            ),
        )
  1. If your environment uses different subject strings or labels for the Microsoft / Red Hat DB certificates, update the expected_vendor_markers list to match the actual key names as they appear in keyctl list %:.platform.
  2. The small for marker in expected_vendor_markers loop is currently only used to short-circuit on matches; you can remove it if you prefer and rely solely on the two assertTrue(any(...)) checks.
  3. If utils_lib.run_cmd already supports a list for expect_kw, you could alternatively pass expect_kw=[...] instead of doing manual assertIn checks, but the current implementation is explicit and independent of that behavior.

Comment on lines +963 to +972
work_dir = '/tmp/secureboot_setup'
utils_lib.run_cmd(self, 'mkdir -p {}'.format(work_dir), expect_ret=0)

ovmf_dir = '/usr/share/edk2/ovmf'
dbx = utils_lib.run_cmd(self,
'ls -t {}/DBXUpdate*.bin 2>/dev/null | head -1'.format(ovmf_dir),
msg='Find DBXUpdate binary').strip()
if not dbx:
self.skipTest('DBXUpdate binary not found in {}'.format(ovmf_dir))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (testing): Test leaves artifacts and changed state if it fails before the final cleanup; consider using a try/finally or addCleanup.

Because the temp directory and certs are only removed on success, any failure after work_dir is created will leave them behind and can affect later test runs. Using a try/finally or self.addCleanup to register cleanup for the directory and other temp resources will keep the test isolated and avoid state leakage, which is important given it modifies UEFI vars and keys.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant