Skip to content

Commit

Permalink
Encrypted guided LVM
Browse files Browse the repository at this point in the history
Allow selecting encryption in the "Advanced features" dialog, and show
the "Choose a security key" page when appropriate.

Ref: canonical#33, canonical#34
  • Loading branch information
jpnurmi committed Sep 22, 2022
1 parent b871d8b commit 41a818c
Show file tree
Hide file tree
Showing 5 changed files with 176 additions and 40 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import 'package:ubuntu_desktop_installer/l10n.dart';
import 'package:ubuntu_desktop_installer/main.dart' as app;
import 'package:ubuntu_desktop_installer/pages.dart';
import 'package:ubuntu_desktop_installer/pages/connect_to_internet/connect_model.dart';
import 'package:ubuntu_desktop_installer/pages/installation_type/installation_type_model.dart';
import 'package:ubuntu_desktop_installer/pages/updates_other_software/updates_other_software_model.dart';
import 'package:ubuntu_desktop_installer/routes.dart';
import 'package:ubuntu_desktop_installer/services.dart';
Expand Down Expand Up @@ -93,6 +94,71 @@ void main() {
);
});

testWidgets('guided lvm + encryption', (tester) async {
const identity = IdentityData(
realname: 'User',
hostname: 'ubuntu',
username: 'user',
);

await app.main(<String>[]);
await tester.pumpAndSettle();

await testWelcomePage(tester);
await tester.pumpAndSettle();

await testTryOrInstallPage(tester, option: Option.installUbuntu);
await tester.pumpAndSettle();

await testKeyboardLayoutPage(tester);
await tester.pumpAndSettle();

await testConnectToInternetPage(tester, mode: ConnectMode.none);
await tester.pumpAndSettle();

await testUpdatesOtherSoftwarePage(tester);
await tester.pumpAndSettle();

await testInstallationTypePage(
tester,
type: InstallationType.erase,
advancedFeature: AdvancedFeature.lvm,
useEncryption: true,
);
await tester.pumpAndSettle();

await testChooseSecurityKeyPage(tester, securityKey: 'password');
await tester.pumpAndSettle();

await testWriteChangesToDiskPage(tester);
await tester.pumpAndSettle();

await testWhereAreYouPage(tester);
await tester.pump();

await testWhoAreYouPage(
tester,
identity: identity,
password: 'password',
);
await tester.pump();

await testChooseYourLookPage(tester);
await tester.pump();

await testInstallationSlidesPage(tester);
await tester.pumpAndSettle();

await testInstallationCompletePage(tester);
await tester.pumpAndSettle();

await verifyConfig(
identity: identity,
useLvm: true,
useEncryption: true,
);
});

testWidgets('manual partitioning', (tester) async {
final storage = [
testDisk(
Expand Down Expand Up @@ -338,13 +404,53 @@ Future<void> testUpdatesOtherSoftwarePage(
Future<void> testInstallationTypePage(
WidgetTester tester, {
InstallationType? type,
AdvancedFeature? advancedFeature,
bool? useEncryption,
}) async {
await expectPage(
tester, InstallationTypePage, (lang) => lang.installationTypeTitle);

if (type != null) {
await tester.tapRadioButton<InstallationType>(type);
}
if (advancedFeature != null) {
await tester.tapButton(label: tester.lang.installationTypeAdvancedLabel);
await tester.pumpAndSettle();

await tester.tapRadioButton<AdvancedFeature>(advancedFeature);
await tester.pump();

if (useEncryption != null) {
await tester.toggleCheckbox(
label: tester.lang.installationTypeEncrypt('Ubuntu'),
value: true,
);
}

await tester.tapButton(label: tester.lang.okButtonText);
}

await tester.pumpAndSettle();

await tester.tapContinue();
}

Future<void> testChooseSecurityKeyPage(
WidgetTester tester, {
required String securityKey,
}) async {
await expectPage(
tester, ChooseSecurityKeyPage, (lang) => lang.chooseSecurityKeyTitle);

await tester.enterTextValue(
label: tester.lang.chooseSecurityKey,
value: securityKey,
);
await tester.enterTextValue(
label: tester.lang.confirmSecurityKey,
value: securityKey,
);

await tester.pumpAndSettle();

await tester.tapContinue();
Expand Down Expand Up @@ -556,6 +662,8 @@ Future<void> verifyConfig({
String? locale,
String? timezone,
List<Disk>? storage,
bool? useLvm,
bool? useEncryption,
}) async {
final path = p.join(
await subiquityPath,
Expand Down Expand Up @@ -596,9 +704,9 @@ Future<void> verifyConfig({
expect(actualTimezone, equals(timezone));
}

if (storage != null) {
final actualStorage = yaml['autoinstall']['storage']['config'] as YamlList;
final actualStorage = yaml['autoinstall']['storage']['config'] as YamlList;

if (storage != null) {
for (final disk in storage) {
final actualDisk = actualStorage.firstWhereOrNull(
(d) => d['type'] == 'disk' && d['path'] == disk.path);
Expand All @@ -617,4 +725,20 @@ Future<void> verifyConfig({
}
}
}

if (useLvm != null) {
expect(
actualStorage
.where((config) => config['type'] == 'lvm_volgroup')
.isNotEmpty,
useLvm);
}

if (useEncryption != null) {
expect(
actualStorage
.where((config) => config['type'] == 'dm_crypt')
.isNotEmpty,
useEncryption);
}
}
42 changes: 21 additions & 21 deletions packages/ubuntu_desktop_installer/lib/installer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -282,37 +282,20 @@ class _UbuntuDesktopInstallerWizard extends StatelessWidget {
),
Routes.installationType: WizardRoute(
builder: InstallationTypePage.create,
onNext: (settings) {
if (settings.arguments == InstallationType.manual) {
return Routes.allocateDiskSpace;
} else if (service.guidedTarget == null) {
if (settings.arguments == InstallationType.erase) {
return Routes.selectGuidedStorage;
} else if (settings.arguments == InstallationType.alongside) {
return Routes.installAlongside;
}
} else if (service.useEncryption) {
return Routes.chooseSecurityKey;
}
return Routes.writeChangesToDisk;
},
onNext: (settings) => _nextStorageRoute(service, settings.arguments),
),
Routes.installAlongside: WizardRoute(
builder: InstallAlongsidePage.create,
onReplace: (_) => Routes.allocateDiskSpace,
onNext: (_) => service.useEncryption
? Routes.chooseSecurityKey
: Routes.writeChangesToDisk,
onNext: (settings) => _nextStorageRoute(service, settings.arguments),
),
Routes.selectGuidedStorage: WizardRoute(
builder: SelectGuidedStoragePage.create,
onNext: (_) => service.useEncryption
? Routes.chooseSecurityKey
: Routes.writeChangesToDisk,
onNext: (settings) => _nextStorageRoute(service, settings.arguments),
),
Routes.chooseSecurityKey: WizardRoute(
builder: ChooseSecurityKeyPage.create,
onNext: (_) => Routes.writeChangesToDisk,
onNext: (settings) => _nextStorageRoute(service, settings.arguments),
),
Routes.allocateDiskSpace: const WizardRoute(
builder: AllocateDiskSpacePage.create,
Expand Down Expand Up @@ -349,6 +332,23 @@ class _UbuntuDesktopInstallerWizard extends StatelessWidget {
],
);
}

String? _nextStorageRoute(DiskStorageService service, dynamic arguments) {
if (arguments == InstallationType.manual) {
return Routes.allocateDiskSpace;
}
if (service.guidedTarget == null) {
if (arguments == InstallationType.erase) {
return Routes.selectGuidedStorage;
} else if (arguments == InstallationType.alongside) {
return Routes.installAlongside;
}
}
if (service.useEncryption && service.securityKey == null) {
return Routes.chooseSecurityKey;
}
return Routes.writeChangesToDisk;
}
}

class _UbuntuDesktopInstallerWizardObserver extends WizardObserver {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,18 +44,17 @@ Future<void> showAdvancedFeaturesDialog(
groupValue: advancedFeature.value,
onChanged: (v) => advancedFeature.value = v!,
),
// https://github.com/canonical/ubuntu-desktop-installer/issues/373
// Padding(
// padding: kContentIndentation,
// child: CheckButton(
// title: Text(lang.installationTypeEncrypt('Ubuntu')),
// subtitle: Text(lang.installationTypeEncrypt(flavor.name)),
// value: encryption.value,
// onChanged: model.advancedFeature == AdvancedFeature.lvm
// ? (v) => encryption.value = v!
// : null,
// ),
// ),
Padding(
padding: kContentIndentation,
child: CheckButton(
title: Text(lang.installationTypeEncrypt(flavor.name)),
subtitle: Text(lang.installationTypeEncryptInfo),
value: encryption.value,
onChanged: advancedFeature.value == AdvancedFeature.lvm
? (v) => encryption.value = v!
: null,
),
),
const SizedBox(height: kContentSpacing),
// https://github.com/canonical/ubuntu-desktop-installer/issues/373
// RadioButton<AdvancedFeature>(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,8 @@ class InstallationTypeModel extends SafeChangeNotifier {
/// if appropriate (single guided storage).
Future<void> save() async {
_diskService.useLvm = advancedFeature == AdvancedFeature.lvm;
_diskService.useEncryption =
encryption && advancedFeature == AdvancedFeature.lvm;

// automatically pre-select a guided storage target if possible
_diskService.guidedTarget = preselectTarget(installationType);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,11 +136,7 @@ class _InstallationTypePageState extends State<InstallationTypePage> {
child: Text(lang.installationTypeAdvancedLabel),
),
const SizedBox(width: kContentSpacing),
Text(model.advancedFeature == AdvancedFeature.lvm
? lang.installationTypeLVMSelected
: model.advancedFeature == AdvancedFeature.zfs
? lang.installationTypeZFSSelected
: lang.installationTypeNoneSelected),
Text(model.advancedFeature.localize(lang, model.encryption)),
],
),
),
Expand Down Expand Up @@ -170,6 +166,21 @@ class _InstallationTypePageState extends State<InstallationTypePage> {
}
}

extension _AdvancedFeatureL10n on AdvancedFeature {
String localize(AppLocalizations lang, bool encryption) {
switch (this) {
case AdvancedFeature.none:
return lang.installationTypeNoneSelected;
case AdvancedFeature.lvm:
return encryption
? lang.installationTypeLVMEncryptionSelected
: lang.installationTypeLVMSelected;
case AdvancedFeature.zfs:
return lang.installationTypeZFSSelected;
}
}
}

extension _OsProberList on List<OsProber> {
/// Whether the system has any OS installed multiple times.
bool get hasDuplicates =>
Expand Down

0 comments on commit 41a818c

Please sign in to comment.