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:
commit
1b1e89b464
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
reason=GuidedDisallowedCapabilityReason.TOO_SMALL,
|
||||
)
|
||||
)
|
||||
else:
|
||||
r.allowed = list(self.capability_info.allowed)
|
||||
r = self.capability_info.copy()
|
||||
if gap_size < install_min:
|
||||
r.disallow_all(
|
||||
reason=GuidedDisallowedCapabilityReason.TOO_SMALL,
|
||||
)
|
||||
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,12 +414,40 @@ 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:
|
||||
self._variation_info[name] = info
|
||||
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
|
||||
self._variation_info[name] = VariationInfo.dd(
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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):
|
||||
|
|
Loading…
Reference in New Issue