Merge pull request #1784 from mwhudson/disallow-core_boot-if-third-party-drivers

Disallow core boot if searching for third party drivers
This commit is contained in:
Michael Hudson-Doyle 2023-09-08 13:48:05 +12:00 committed by GitHub
commit 1b1e89b464
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 120 additions and 32 deletions

View File

@ -139,9 +139,15 @@ The version of Subiquity released with 20.04 GA does not accept `null` for this
#### search_drivers
**type:** boolean
**default:** `true`
**default:** `true` (mostly, see below)
Whether the installer should search for available third-party drivers. When set to `false`, it disables the drivers screen and [section](#drivers).
Whether the installer should search for available third-party
drivers. When set to `false`, it disables the drivers screen and
[section](#drivers).
The default is true for most installs but false when a "core boot" or
"enhanced secure boot" method is selected (where third-party drivers
cannot currently be installed).
#### id
**type:** string

View File

@ -387,6 +387,8 @@ class GuidedCapability(enum.Enum):
class GuidedDisallowedCapabilityReason(enum.Enum):
TOO_SMALL = enum.auto()
CORE_BOOT_ENCRYPTION_UNAVAILABLE = enum.auto()
NOT_UEFI = enum.auto()
THIRD_PARTY_DRIVERS = enum.auto()
@attr.s(auto_attribs=True)

View File

@ -21,6 +21,7 @@ from subiquity.common.apidef import API
from subiquity.common.types import DriversPayload, DriversResponse
from subiquity.server.apt import OverlayCleanupError
from subiquity.server.controller import SubiquityController
from subiquity.server.controllers.source import SEARCH_DRIVERS_AUTOINSTALL_DEFAULT
from subiquity.server.types import InstallerChannels
from subiquity.server.ubuntu_drivers import (
CommandNotFoundError,
@ -123,6 +124,8 @@ class DriversController(SubiquityController):
await self.list_drivers_done_event.wait()
search_drivers = self.app.controllers.Source.model.search_drivers
if search_drivers is SEARCH_DRIVERS_AUTOINSTALL_DEFAULT:
search_drivers = True
return DriversResponse(
install=self.model.do_install,

View File

@ -22,7 +22,7 @@ import os
import pathlib
import select
import time
from typing import Any, Dict, List, Optional, Union
from typing import Any, Callable, Dict, List, Optional, Union
import attr
import pyudev
@ -74,6 +74,7 @@ from subiquity.models.filesystem import (
)
from subiquity.server import snapdapi
from subiquity.server.controller import SubiquityController
from subiquity.server.controllers.source import SEARCH_DRIVERS_AUTOINSTALL_DEFAULT
from subiquity.server.mounter import Mounter
from subiquity.server.snapdapi import (
StorageEncryptionSupport,
@ -123,7 +124,7 @@ class CapabilityInfo:
allowed: List[GuidedCapability] = attr.Factory(list)
disallowed: List[GuidedDisallowedCapability] = attr.Factory(list)
def combine(self, other: "CapabilityInfo"):
def combine(self, other: "CapabilityInfo") -> None:
for allowed_cap in other.allowed:
if allowed_cap not in self.allowed:
self.allowed.append(allowed_cap)
@ -140,6 +141,38 @@ class CapabilityInfo:
self.allowed.sort()
self.disallowed.sort()
def copy(self) -> "CapabilityInfo":
return CapabilityInfo(
allowed=list(self.allowed), disallowed=list(self.disallowed)
)
def disallow_if(
self,
filter: Callable[[GuidedCapability], bool],
reason: GuidedDisallowedCapabilityReason,
message: Optional[str] = None,
) -> None:
new_allowed = []
for cap in self.allowed:
if filter(cap):
self.disallowed.append(
GuidedDisallowedCapability(
capability=cap,
reason=reason,
message=message,
)
)
else:
new_allowed.append(cap)
self.allowed = new_allowed
def disallow_all(
self,
reason: GuidedDisallowedCapabilityReason,
message: Optional[str] = None,
) -> None:
self.disallow_if(lambda cap: True, reason, message)
@attr.s(auto_attribs=True)
class VariationInfo:
@ -160,22 +193,15 @@ class VariationInfo:
gap: gaps.Gap,
install_min: int,
) -> CapabilityInfo:
r = CapabilityInfo()
r.disallowed = list(self.capability_info.disallowed)
if gap is None:
gap_size = 0
else:
gap_size = gap.size
if self.capability_info.allowed and gap_size < install_min:
for capability in self.capability_info.allowed:
r.disallowed.append(
GuidedDisallowedCapability(
capability=capability,
r = self.capability_info.copy()
if gap_size < install_min:
r.disallow_all(
reason=GuidedDisallowedCapabilityReason.TOO_SMALL,
)
)
else:
r.allowed = list(self.capability_info.allowed)
return r
@classmethod
@ -267,6 +293,11 @@ class FilesystemController(SubiquityController, FilesystemManipulator):
self._configured = True
if self._info is None:
self.set_info_for_capability(GuidedCapability.DIRECT)
if (
self.app.base_model.source.search_drivers
is SEARCH_DRIVERS_AUTOINSTALL_DEFAULT
):
self.app.base_model.source.search_drivers = not self.is_core_boot_classic()
await super().configured()
self.stop_listening_udev()
@ -383,11 +414,39 @@ class FilesystemController(SubiquityController, FilesystemManipulator):
if not self.app.opts.enhanced_secureboot:
log.debug("Not offering enhanced_secureboot: commandline disabled")
continue
if self.model.bootloader != Bootloader.UEFI:
log.debug("Not offering core boot based install: not a UEFI system")
continue
info = self.info_for_system(name, label, system)
if info is not None:
if info is None:
continue
if self.model.bootloader != Bootloader.UEFI:
log.debug(
"Disabling core boot based install options on non-UEFI "
"system"
)
info.capability_info.disallow_if(
lambda cap: cap.is_core_boot(),
GuidedDisallowedCapabilityReason.NOT_UEFI,
_(
"Enhanced secure boot options only available on UEFI "
"systems."
),
)
search_drivers = self.app.base_model.source.search_drivers
if (
search_drivers is not SEARCH_DRIVERS_AUTOINSTALL_DEFAULT
and search_drivers
):
log.debug(
"Disabling core boot based install options as third-party "
"drivers selected"
)
info.capability_info.disallow_if(
lambda cap: cap.is_core_boot(),
GuidedDisallowedCapabilityReason.THIRD_PARTY_DRIVERS,
_(
"Enhanced secure boot options cannot currently install "
"third party drivers."
),
)
self._variation_info[name] = info
elif catalog_entry.type.startswith("dd-"):
min_size = variation.size

View File

@ -14,6 +14,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import contextlib
import logging
import os
from typing import Any, Optional
@ -29,6 +30,8 @@ from subiquity.common.types import SourceSelection, SourceSelectionAndSetting
from subiquity.server.controller import SubiquityController
from subiquity.server.types import InstallerChannels
log = logging.getLogger("subiquity.server.controllers.source")
def _translate(d, lang):
if lang:
@ -50,6 +53,9 @@ def convert_source(source, lang):
)
SEARCH_DRIVERS_AUTOINSTALL_DEFAULT = object()
class SourceController(SubiquityController):
model_name = "source"
@ -67,10 +73,6 @@ class SourceController(SubiquityController):
},
},
}
# Defaults to true for backward compatibility with existing autoinstall
# configurations. Back then, then users were able to install third-party
# drivers without this field.
autoinstall_default = {"search_drivers": True}
def __init__(self, app):
super().__init__(app)
@ -86,13 +88,15 @@ class SourceController(SubiquityController):
def load_autoinstall_data(self, data: Any) -> None:
if data is None:
# NOTE: The JSON schema does not allow data to be null in this
# context. However, Subiquity bypasses the schema validation when
# a section is set to null. So in practice, we can have data = None
# here.
data = {**self.autoinstall_default, "id": None}
data = {}
self.model.search_drivers = data.get("search_drivers", True)
# Defaults to almost-true for backward compatibility with existing autoinstall
# configurations. Back then, then users were able to install third-party drivers
# without this field. The "almost-true" part is that search_drivers defaults to
# False for core boot classic installs.
self.model.search_drivers = data.get(
"search_drivers", SEARCH_DRIVERS_AUTOINSTALL_DEFAULT
)
# At this point, the model has not yet loaded the sources from the
# catalog. So we store the ID and lean on self.start to select the
@ -122,10 +126,14 @@ class SourceController(SubiquityController):
cur_lang = self.app.base_model.locale.selected_language
cur_lang = cur_lang.rsplit(".", 1)[0]
search_drivers = self.model.search_drivers
if search_drivers is SEARCH_DRIVERS_AUTOINSTALL_DEFAULT:
search_drivers = True
return SourceSelectionAndSetting(
[convert_source(source, cur_lang) for source in self.model.sources],
self.model.current.id,
search_drivers=self.model.search_drivers,
search_drivers=search_drivers,
)
def get_handler(

View File

@ -1144,6 +1144,7 @@ class TestCoreBootInstallMethods(IsolatedAsyncioTestCase):
self.app.dr_cfg = DRConfig()
self.app.dr_cfg.systems_dir_exists = True
self.app.controllers.Source.get_handler.return_value = TrivialSourceHandler("")
self.app.base_model.source.search_drivers = False
self.fsc = FilesystemController(app=self.app)
self.fsc._configured = True
self.fsc.model = make_model(Bootloader.UEFI)

View File

@ -235,6 +235,15 @@ class GuidedChoiceForm(SubForm):
self.use_tpm.help = self.tpm_choice.help
self.use_tpm.help = self.tpm_choice.help.format(reason=reason)
else:
self.use_tpm.enabled = False
core_boot_disallowed = [
d for d in val.disallowed if d.capability.is_core_boot()
]
if core_boot_disallowed:
self.use_tpm.help = core_boot_disallowed[0].message
else:
self.use_tpm.help = ""
self.tpm_choice = None
def _toggle_lvm(self, sender, val):