Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions ironic_python_agent/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -513,6 +513,9 @@ def process_lookup_data(self, content):

# Update config with values from Ironic
config = content.get('config', {})
if config.get('enable_bios_bootloader_install'):
cfg.CONF.set_override('enable_bios_bootloader_install',
config['enable_bios_bootloader_install'])
if config.get('agent_containers'):
for opt, val in config['agent_containers'].items():
cfg.CONF.set_override(opt, val, group='container')
Expand Down
7 changes: 7 additions & 0 deletions ironic_python_agent/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,13 @@
help='This disables bootc deployment methods in the ramdisk '
'because the bootc command inside of the ramdisk '
'comes from the supplied image to be deployed.'),
cfg.BoolOpt('enable_bios_bootloader_install',
default=False,
help='Enables support for partition images which require a '
'legacy bootloader -- and a call to ``grub-install``. '
'Generally, this should remain disabled for maximum '
'security, however, this option allows it to be '
're-enabled for compatibility.'),
]

disk_utils_opts = [
Expand Down
31 changes: 20 additions & 11 deletions ironic_python_agent/extensions/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -723,15 +723,24 @@ def install_bootloader(self, root_uuid, efi_system_part_uuid=None,
' Assuming a whole disk image')
return

# In case we can't use efibootmgr for uefi we will continue using grub2
LOG.debug('Using grub2-install to set up boot files')
try:
_install_grub2(device,
root_uuid=root_uuid,
efi_system_part_uuid=efi_system_part_uuid,
prep_boot_part_uuid=prep_boot_part_uuid,
target_boot_mode=target_boot_mode)
except Exception as e:
LOG.error('Error setting up bootloader. Error %s', e)
if CONF.enable_bios_bootloader_install:
# In case we can't use efibootmgr for uefi we will continue
# using grub2
LOG.debug('Using grub2-install to set up boot files')
try:
_install_grub2(device,
root_uuid=root_uuid,
efi_system_part_uuid=efi_system_part_uuid,
prep_boot_part_uuid=prep_boot_part_uuid,
target_boot_mode=target_boot_mode)
except Exception as e:
LOG.error('Error setting up bootloader. Error %s', e)
if not ignore_failure:
raise
else:
msg = ("Install of legacy BIOS bootloaders disabled by "
"CONF.enable_bios_bootloader_install as part of "
"CVE-2026-43003 mitigation.")
LOG.error(msg)
if not ignore_failure:
raise
raise errors.InvalidImage(details=msg)
32 changes: 32 additions & 0 deletions ironic_python_agent/tests/unit/extensions/test_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ def setUp(self):
self.fake_efi_system_part_uuid = '45AB-2312'
self.fake_prep_boot_part_uuid = '76937797-3253-8843-999999999999'
self.fake_dir = '/tmp/fake-dir'
self.config(enable_bios_bootloader_install=True)

@mock.patch.object(image, '_install_grub2', autospec=True)
def test__install_bootloader_bios(self, mock_grub2,
Expand All @@ -68,8 +69,39 @@ def test__install_bootloader_bios(self, mock_grub2,
self.fake_dev, root_uuid=self.fake_root_uuid,
efi_system_part_uuid=None, prep_boot_part_uuid=None,
target_boot_mode='bios'

)

@mock.patch.object(image, '_install_grub2', autospec=True)
def test__install_bootloader_bios_disabled(self, mock_grub2,
mock_execute, mock_dispatch):
self.config(enable_bios_bootloader_install=False)
mock_dispatch.side_effect = [
self.fake_dev, hardware.BootInfo(current_boot_mode='bios')
]
self.agent_extension.install_bootloader(
root_uuid=self.fake_root_uuid).join()
mock_dispatch.assert_any_call('get_os_install_device')
mock_dispatch.assert_any_call('get_boot_info')
self.assertEqual(2, mock_dispatch.call_count)
mock_grub2.assert_not_called()

@mock.patch.object(image, '_install_grub2', autospec=True)
def test__install_bootloader_bios_disabled_dont_ignore_failures(
self, mock_grub2, mock_execute, mock_dispatch):
self.config(enable_bios_bootloader_install=False)
self.config(ignore_bootloader_failure=False)
mock_dispatch.side_effect = [
self.fake_dev, hardware.BootInfo(current_boot_mode='bios')
]
result = self.agent_extension.install_bootloader(
root_uuid=self.fake_root_uuid).join()
mock_dispatch.assert_any_call('get_os_install_device')
mock_dispatch.assert_any_call('get_boot_info')
self.assertEqual(2, mock_dispatch.call_count)
self.assertIsNotNone(result.command_error)
mock_grub2.assert_not_called()

@mock.patch.object(efi_utils, 'manage_uefi', autospec=True)
@mock.patch.object(image, '_install_grub2', autospec=True)
def test__install_bootloader_uefi(self, mock_grub2, mock_uefi,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
security:
- |
Disable installation of bootloaders (via grub-install) by default in order
to improve security posture by adding a new configuration option
`enable_bios_bootloader_install` which defaults to `False`. Operators
who still need this functionality can re-enable installation of
bootloaders by setting `enable_bios_bootloader_install` to `True`.
Addresses CVE-2026-43003.