diff --git a/products.d/ALP-Dolomite.yaml b/products.d/ALP-Dolomite.yaml index 45208d4525..60103a6393 100644 --- a/products.d/ALP-Dolomite.yaml +++ b/products.d/ALP-Dolomite.yaml @@ -63,9 +63,7 @@ security: storage: space_policy: delete encryption: - method: luks2 - pbkd_function: pbkdf2 - tpm_luks_open: true + method: tpm_fde volumes: - "/" volume_templates: diff --git a/service/lib/agama/dbus/storage/manager.rb b/service/lib/agama/dbus/storage/manager.rb index 57cf3261a5..188062579c 100644 --- a/service/lib/agama/dbus/storage/manager.rb +++ b/service/lib/agama/dbus/storage/manager.rb @@ -34,6 +34,7 @@ require "agama/dbus/storage/with_iscsi_auth" require "agama/dbus/with_service_status" require "agama/storage/volume_templates_builder" +require "agama/storage/encryption_settings" Yast.import "Arch" @@ -117,13 +118,20 @@ def available_devices proposal.available_devices.map { |d| system_devices_tree.path_for(d) } end - # List of meaningful mount points for the the current product. + # List of meaningful mount points for the current product. # # @return [Array] def product_mount_points volume_templates_builder.all.map(&:mount_path).reject(&:empty?) end + # List of possible encryption methods for the current system and product + # + # @return [Array] + def encryption_methods + Agama::Storage::EncryptionSettings.available_methods.map { |m| m.id.to_s } + end + # Path of the D-Bus object containing the calculated proposal # # @return [::DBus::ObjectPath] Proposal object path or root path if no exported proposal yet @@ -161,6 +169,8 @@ def calculate_proposal(dbus_settings) dbus_reader :product_mount_points, "as" + dbus_reader :encryption_methods, "as" + dbus_reader :result, "o" dbus_method :DefaultVolume, "in mount_path:s, out volume:a{sv}" do |mount_path| diff --git a/service/lib/agama/storage/encryption_settings.rb b/service/lib/agama/storage/encryption_settings.rb index 5e308a6398..eb755d5b62 100644 --- a/service/lib/agama/storage/encryption_settings.rb +++ b/service/lib/agama/storage/encryption_settings.rb @@ -28,6 +28,13 @@ module Storage class EncryptionSettings include Y2Storage::SecretAttributes + # @see .encryption_methods + METHODS = [ + Y2Storage::EncryptionMethod::LUKS2, + Y2Storage::EncryptionMethod::TPM_FDE + ].freeze + private_constant :METHODS + # @!attribute encryption_password # Password to use when creating new encryption devices # @return [String, nil] nil if undetermined @@ -41,8 +48,30 @@ class EncryptionSettings # @return [Y2Storage::PbkdFunction, nil] Can be nil if using LUKS1. attr_accessor :pbkd_function + # All known encryption methods + # + # This includes all the potentially accepted methods, no matter whether they are + # possible for the current system and product. + # + # @return [Array] + def self.encryption_methods + METHODS + end + + # All methods that can be used to calculate a proposal for the current system and product + # + # @return [Array] + def self.available_methods + encryption_methods.reject { |m| m.respond_to?(:possible?) && !m.possible? } + end + + # Constructor def initialize - @method = Y2Storage::EncryptionMethod::LUKS1 + # LUKS2 with PBKDF2 is the most sensible option nowadays: + # - LUKS2 offers additional features over LUKS1 + # - PBKDF2 keeps memory usage similar to LUKS1 and is supported by Grub2 + @method = Y2Storage::EncryptionMethod::LUKS2 + @pbkd_function = Y2Storage::PbkdFunction::PBKDF2 end # Whether the proposal must create encrypted devices diff --git a/service/lib/agama/storage/proposal_settings_reader.rb b/service/lib/agama/storage/proposal_settings_reader.rb index 8b04c0beac..fff9245e9e 100644 --- a/service/lib/agama/storage/proposal_settings_reader.rb +++ b/service/lib/agama/storage/proposal_settings_reader.rb @@ -69,24 +69,19 @@ def lvm_reader(settings, value) # @param settings [Agama::Storage::ProposalSettings] # @param encryption [Hash] def encryption_reader(settings, encryption) - method = - if try_tpm_fde?(encryption) - Y2Storage::EncryptionMethod::TPM_FDE - else - Y2Storage::EncryptionMethod.find(encryption.fetch("method", "")) - end + method = Y2Storage::EncryptionMethod.find(encryption.fetch("method", "")) pbkd_function = Y2Storage::PbkdFunction.find(encryption.fetch("pbkd_function", "")) - settings.encryption.method = method if method + settings.encryption.method = method if available_method?(method) settings.encryption.pbkd_function = pbkd_function if pbkd_function end - # @param encryption [Hash] + # @param method [Y2Storage::EncryptionMethod::Base, nil] # @return [Boolean] - def try_tpm_fde?(encryption) - return false unless encryption["tpm_luks_open"] == true + def available_method?(method) + return false unless method - Y2Storage::EncryptionMethod::TPM_FDE.possible? + EncryptionSettings.available_methods.include?(method) end # @param settings [Agama::Storage::ProposalSettings] diff --git a/service/package/rubygem-agama.changes b/service/package/rubygem-agama.changes index 15837bc65c..065179651c 100644 --- a/service/package/rubygem-agama.changes +++ b/service/package/rubygem-agama.changes @@ -1,3 +1,9 @@ +------------------------------------------------------------------- +Thu Jan 18 08:35:01 UTC 2024 - Ancor Gonzalez Sosa + +- New default encryption settings: LUKS2 with PBKDF2. +- Expose encryption methods at D-Bus API (gh#openSUSE/agama#995). + ------------------------------------------------------------------- Tue Jan 16 10:49:14 UTC 2024 - Michal Filka diff --git a/service/test/agama/dbus/storage/proposal_settings_conversion/to_dbus_test.rb b/service/test/agama/dbus/storage/proposal_settings_conversion/to_dbus_test.rb index de6ad33d09..168ab4852e 100644 --- a/service/test/agama/dbus/storage/proposal_settings_conversion/to_dbus_test.rb +++ b/service/test/agama/dbus/storage/proposal_settings_conversion/to_dbus_test.rb @@ -50,8 +50,8 @@ "LVM" => false, "SystemVGDevices" => [], "EncryptionPassword" => "", - "EncryptionMethod" => "luks1", - "EncryptionPBKDFunction" => "", + "EncryptionMethod" => "luks2", + "EncryptionPBKDFunction" => "pbkdf2", "SpacePolicy" => "keep", "SpaceActions" => {}, "Volumes" => [] diff --git a/service/test/agama/storage/proposal_settings_reader_test.rb b/service/test/agama/storage/proposal_settings_reader_test.rb index 88188076f1..b5bed59cf0 100644 --- a/service/test/agama/storage/proposal_settings_reader_test.rb +++ b/service/test/agama/storage/proposal_settings_reader_test.rb @@ -99,8 +99,8 @@ ), encryption: an_object_having_attributes( password: nil, - method: Y2Storage::EncryptionMethod::LUKS1, - pbkd_function: nil + method: Y2Storage::EncryptionMethod::LUKS2, + pbkd_function: Y2Storage::PbkdFunction::PBKDF2 ), space: an_object_having_attributes( policy: :keep, @@ -127,13 +127,13 @@ expect(settings).to have_attributes( encryption: an_object_having_attributes( - method: Y2Storage::EncryptionMethod::LUKS1 + method: Y2Storage::EncryptionMethod::LUKS2 ) ) end end - context "when the config contains an unknown encryption function" do + context "when the config contains an unknown password derivation function" do let(:config_data) do { "storage" => { @@ -144,12 +144,12 @@ } end - it "does not set a PBKD function" do + it "sets the default derivation function" do settings = subject.read expect(settings).to have_attributes( encryption: an_object_having_attributes( - pbkd_function: be_nil + pbkd_function: Y2Storage::PbkdFunction::PBKDF2 ) ) end diff --git a/web/package/cockpit-agama.changes b/web/package/cockpit-agama.changes index 8882dd0190..130068a264 100644 --- a/web/package/cockpit-agama.changes +++ b/web/package/cockpit-agama.changes @@ -1,3 +1,8 @@ +------------------------------------------------------------------- +Thu Jan 18 08:33:52 UTC 2024 - Ancor Gonzalez Sosa + +- Make TPM-based encryption more explicit (gh#openSUSE/agama#995) + ------------------------------------------------------------------- Tue Jan 16 15:27:28 UTC 2024 - José Iván López González diff --git a/web/src/assets/styles/blocks.scss b/web/src/assets/styles/blocks.scss index 70bac9f72d..d59ceebc60 100644 --- a/web/src/assets/styles/blocks.scss +++ b/web/src/assets/styles/blocks.scss @@ -430,3 +430,17 @@ ul[data-of="agama/timezones"] { } } } + +.tpm-hint { + container-type: inline-size; + container-name: tpm-info; + text-align: start; + + .pf-v5-c-alert__title { + margin-block-end: var(--spacer-small); + } + + .pf-v5-c-alert__description { + max-inline-size: 100%; + } +} diff --git a/web/src/assets/styles/layout.scss b/web/src/assets/styles/layout.scss index a642ff4176..6e879528f1 100644 --- a/web/src/assets/styles/layout.scss +++ b/web/src/assets/styles/layout.scss @@ -55,6 +55,8 @@ grid-area: body; overflow: auto; padding-block: var(--spacer-normal); + container-type: inline-size; + container-name: agama-page-content; } footer { diff --git a/web/src/client/storage.js b/web/src/client/storage.js index 7290b74bc3..ba74801644 100644 --- a/web/src/client/storage.js +++ b/web/src/client/storage.js @@ -1,5 +1,5 @@ /* - * Copyright (c) [2022-2023] SUSE LLC + * Copyright (c) [2022-2024] SUSE LLC * * All Rights Reserved. * @@ -46,6 +46,17 @@ const ZFCP_CONTROLLER_IFACE = "org.opensuse.Agama.Storage1.ZFCP.Controller"; const ZFCP_DISKS_NAMESPACE = "/org/opensuse/Agama/Storage1/zfcp_disks"; const ZFCP_DISK_IFACE = "org.opensuse.Agama.Storage1.ZFCP.Disk"; +/** + * Enum for the encryption method values + * + * @readonly + * @enum { string } + */ +const EncryptionMethods = Object.freeze({ + LUKS2: "luks2", + TPM: "tpm_fde" +}); + /** * Removes properties with undefined value * @@ -227,6 +238,7 @@ class ProposalManager { * @typedef {object} ProposalSettings * @property {string} bootDevice * @property {string} encryptionPassword + * @property {string} encryptionMethod * @property {boolean} lvm * @property {string} spacePolicy * @property {string[]} systemVGDevices @@ -283,6 +295,16 @@ class ProposalManager { return proxy.ProductMountPoints; } + /** + * Gets the list of encryption methods accepted by the proposal + * + * @returns {Promise} + */ + async getEncryptionMethods() { + const proxy = await this.proxies.proposalCalculator; + return proxy.EncryptionMethods; + } + /** * Obtains the default volume for the given mount path * @@ -345,6 +367,7 @@ class ProposalManager { spacePolicy: proxy.SpacePolicy, systemVGDevices: proxy.SystemVGDevices, encryptionPassword: proxy.EncryptionPassword, + encryptionMethod: proxy.EncryptionMethod, volumes: proxy.Volumes.map(this.buildVolume), // NOTE: strictly speaking, installation devices does not belong to the settings. It // should be a separate method instead of an attribute in the settings object. @@ -365,7 +388,7 @@ class ProposalManager { * @param {ProposalSettings} settings * @returns {Promise} 0 on success, 1 on failure */ - async calculate({ bootDevice, encryptionPassword, lvm, spacePolicy, systemVGDevices, volumes }) { + async calculate({ bootDevice, encryptionPassword, encryptionMethod, lvm, spacePolicy, systemVGDevices, volumes }) { const dbusVolume = (volume) => { return removeUndefinedCockpitProperties({ MountPath: { t: "s", v: volume.mountPath }, @@ -381,6 +404,7 @@ class ProposalManager { const settings = removeUndefinedCockpitProperties({ BootDevice: { t: "s", v: bootDevice }, EncryptionPassword: { t: "s", v: encryptionPassword }, + EncryptionMethod: { t: "s", v: encryptionMethod }, LVM: { t: "b", v: lvm }, SpacePolicy: { t: "s", v: spacePolicy }, SystemVGDevices: { t: "as", v: systemVGDevices }, @@ -1420,4 +1444,4 @@ class StorageClient extends WithIssues( ), STORAGE_OBJECT ) { } -export { StorageClient }; +export { StorageClient, EncryptionMethods }; diff --git a/web/src/components/core/InstallationFinished.jsx b/web/src/components/core/InstallationFinished.jsx index b6a2566f26..98fb3d8732 100644 --- a/web/src/components/core/InstallationFinished.jsx +++ b/web/src/components/core/InstallationFinished.jsx @@ -1,5 +1,5 @@ /* - * Copyright (c) [2022] SUSE LLC + * Copyright (c) [2022-2024] SUSE LLC * * All Rights Reserved. * @@ -21,65 +21,97 @@ import React, { useState, useEffect } from "react"; import { + Alert, Text, EmptyState, EmptyStateBody, EmptyStateHeader, EmptyStateIcon, + ExpandableSection, } from "@patternfly/react-core"; -import { Page } from "~/components/core"; -import { Center, Icon } from "~/components/layout"; +import { If, Page } from "~/components/core"; +import { Icon } from "~/components/layout"; import { useInstallerClient } from "~/context/installer"; +import { EncryptionMethods } from "~/client/storage"; import { _ } from "~/i18n"; +const TpmHint = () => { + const [isExpanded, setIsExpanded] = useState(false); + const title = _("TPM sealing requires the new system to be booted directly."); + + return ( + {title}}> +
+ {_("If a local media was used to run this installer, remove it before the next boot.")} + setIsExpanded(!isExpanded)} + toggleText={isExpanded ? _("Hide details") : _("See more details")} + > + TPM to automatically open encrypted devices will take place during the first boot of the new system. For that to work, the machine needs to boot directly to the new boot loader.") + }} + /> + +
+
+ ); +}; + const SuccessIcon = () => ; function InstallationFinished() { const client = useInstallerClient(); - const [iguana, setIguana] = useState(false); + const [usingIguana, setUsingIguana] = useState(false); + const [usingTpm, setUsingTpm] = useState(false); const closingAction = () => client.manager.finishInstallation(); - const buttonCaption = iguana - // TRANSLATORS: button label - ? _("Finish") - // TRANSLATORS: button label - : _("Reboot"); useEffect(() => { - async function getIguana() { - const ret = await client.manager.useIguana(); - setIguana(ret); + async function preparePage() { + const iguana = await client.manager.useIguana(); + // FIXME: This logic should likely not be placed here, it's too coupled to storage internals. + // Something to fix when this whole page is refactored in a (hopefully near) future. + const { settings: { encryptionPassword, encryptionMethod } } = await client.storage.proposal.getResult(); + setUsingIguana(iguana); + setUsingTpm(encryptionPassword?.length && encryptionMethod === EncryptionMethods.TPM); } - getIguana(); + // TODO: display the page in a loading mode while needed data is being fetched. + preparePage(); }); return ( // TRANSLATORS: page title -
- - } + + } + /> + + {_("The installation on your machine is complete.")} + + + + } /> - - {_("The installation on your machine is complete.")} - - { - iguana - ? _("At this point you can power off the machine.") - : _("At this point you can reboot the machine to log in to the new system.") - } - - {_("Have a lot of fun! Your openSUSE Development Team.")} - - -
+ + - {buttonCaption} + + {usingIguana ? _("Finish") : _("Reboot")} +
); diff --git a/web/src/components/core/InstallationFinished.test.jsx b/web/src/components/core/InstallationFinished.test.jsx index f3af126630..a70c9d731c 100644 --- a/web/src/components/core/InstallationFinished.test.jsx +++ b/web/src/components/core/InstallationFinished.test.jsx @@ -1,5 +1,5 @@ /* - * Copyright (c) [2022] SUSE LLC + * Copyright (c) [2022-2024] SUSE LLC * * All Rights Reserved. * @@ -24,6 +24,7 @@ import React from "react"; import { screen } from "@testing-library/react"; import { installerRender } from "~/test-utils"; import { createClient } from "~/client"; +import { EncryptionMethods } from "~/client/storage"; import InstallationFinished from "./InstallationFinished"; @@ -31,14 +32,25 @@ jest.mock("~/client"); jest.mock("~/components/core/Sidebar", () => () =>
Agama sidebar
); const finishInstallationFn = jest.fn(); +let encryptionPassword; +let encryptionMethod; describe("InstallationFinished", () => { beforeEach(() => { + encryptionPassword = "n0tS3cr3t"; + encryptionMethod = EncryptionMethods.LUKS2; createClient.mockImplementation(() => { return { manager: { finishInstallation: finishInstallationFn, useIguana: () => Promise.resolve(false) + }, + storage: { + proposal: { + getResult: jest.fn().mockResolvedValue({ + settings: { encryptionMethod, encryptionPassword } + }) + }, } }; }); @@ -60,4 +72,47 @@ describe("InstallationFinished", () => { await user.click(rebootButton); expect(finishInstallationFn).toHaveBeenCalled(); }); + + describe("when TPM is set as encryption method", () => { + beforeEach(() => { + encryptionMethod = EncryptionMethods.TPM; + }); + + describe("and encryption was set", () => { + it("shows the TPM reminder", async () => { + installerRender(); + await screen.findAllByText(/TPM/); + }); + }); + + describe("but encryption was not set", () => { + beforeEach(() => { + encryptionPassword = ""; + }); + + it("does not show the TPM reminder", async () => { + const { user } = installerRender(); + // Forcing the test to slow down a bit with a fake user interaction + // because actually the reminder will be not rendered immediately + // making the queryAllByText to produce a false positive if triggered + // too early here. + const congratsText = screen.getByText("Congratulations!"); + await user.click(congratsText); + expect(screen.queryAllByText(/TPM/)).toHaveLength(0); + }); + }); + }); + + describe("when TPM is not set as encryption method", () => { + it("does not show the TPM reminder", async () => { + const { user } = installerRender(); + // Forcing the test to slow down a bit with a fake user interaction + // because actually the reminder will be not rendered immediately + // making the queryAllByText to produce a false positive if triggered + // too early here. + const congratsText = screen.getByText("Congratulations!"); + await user.click(congratsText); + expect(screen.queryAllByText(/TPM/)).toHaveLength(0); + }); + }); }); diff --git a/web/src/components/overview/StorageSection.test.jsx b/web/src/components/overview/StorageSection.test.jsx index 9c29e02f34..ba08280298 100644 --- a/web/src/components/overview/StorageSection.test.jsx +++ b/web/src/components/overview/StorageSection.test.jsx @@ -35,6 +35,8 @@ const availableDevices = [ { name: "/dev/sdb", size: 697932185600 } ]; +const encryptionMethods = ["luks2", "tpm_fde"]; + const proposalResult = { settings: { bootDevice: "/dev/sda", @@ -48,6 +50,7 @@ const storageMock = { probe: jest.fn().mockResolvedValue(0), proposal: { getAvailableDevices: jest.fn().mockResolvedValue(availableDevices), + getEncryptionMethods: jest.fn().mockResolvedValue(encryptionMethods), getResult: jest.fn().mockResolvedValue(proposalResult), calculate: jest.fn().mockResolvedValue(0) }, diff --git a/web/src/components/storage/ProposalPage.jsx b/web/src/components/storage/ProposalPage.jsx index 335c939167..cbeb1d84ec 100644 --- a/web/src/components/storage/ProposalPage.jsx +++ b/web/src/components/storage/ProposalPage.jsx @@ -34,6 +34,7 @@ const initialState = { loading: true, availableDevices: [], volumeTemplates: [], + encryptionMethods: [], settings: {}, actions: [], errors: [] @@ -54,6 +55,11 @@ const reducer = (state, action) => { return { ...state, availableDevices }; } + case "UPDATE_ENCRYPTION_METHODS": { + const { encryptionMethods } = action.payload; + return { ...state, encryptionMethods }; + } + case "UPDATE_VOLUME_TEMPLATES": { const { volumeTemplates } = action.payload; return { ...state, volumeTemplates }; @@ -89,6 +95,10 @@ export default function ProposalPage() { return await cancellablePromise(client.proposal.getAvailableDevices()); }, [client, cancellablePromise]); + const loadEncryptionMethods = useCallback(async () => { + return await cancellablePromise(client.proposal.getEncryptionMethods()); + }, [client, cancellablePromise]); + const loadVolumeTemplates = useCallback(async () => { const mountPoints = await cancellablePromise(client.proposal.getProductMountPoints()); const volumeTemplates = []; @@ -127,6 +137,9 @@ export default function ProposalPage() { const availableDevices = await loadAvailableDevices(); dispatch({ type: "UPDATE_AVAILABLE_DEVICES", payload: { availableDevices } }); + const encryptionMethods = await loadEncryptionMethods(); + dispatch({ type: "UPDATE_ENCRYPTION_METHODS", payload: { encryptionMethods } }); + const volumeTemplates = await loadVolumeTemplates(); dispatch({ type: "UPDATE_VOLUME_TEMPLATES", payload: { volumeTemplates } }); @@ -137,7 +150,7 @@ export default function ProposalPage() { dispatch({ type: "UPDATE_ERRORS", payload: { errors } }); if (result !== undefined) dispatch({ type: "STOP_LOADING" }); - }, [calculateProposal, cancellablePromise, client, loadAvailableDevices, loadErrors, loadProposalResult, loadVolumeTemplates]); + }, [calculateProposal, cancellablePromise, client, loadAvailableDevices, loadEncryptionMethods, loadErrors, loadProposalResult, loadVolumeTemplates]); const calculate = useCallback(async (settings) => { dispatch({ type: "START_LOADING" }); @@ -200,6 +213,7 @@ export default function ProposalPage() { Promise.resolve({ mountPath })), diff --git a/web/src/components/storage/ProposalSettingsSection.jsx b/web/src/components/storage/ProposalSettingsSection.jsx index dc0b82ab40..d5b7bccc16 100644 --- a/web/src/components/storage/ProposalSettingsSection.jsx +++ b/web/src/components/storage/ProposalSettingsSection.jsx @@ -22,7 +22,7 @@ import React, { useEffect, useState } from "react"; import { Button, - Form, Skeleton, Switch, + Form, Skeleton, Switch, Checkbox, ToggleGroup, ToggleGroupItem, Tooltip } from "@patternfly/react-core"; @@ -385,13 +385,18 @@ created in a logical volume of the system volume group."); * @callback onValidateFn * @param {boolean} valid */ -const EncryptionPasswordForm = ({ +const EncryptionSettingsForm = ({ id, password: passwordProp, + method: methodProp, + methods, onSubmit = noop, onValidate = noop }) => { const [password, setPassword] = useState(passwordProp || ""); + const [method, setMethod] = useState(methodProp); + const tpmId = "tpm_fde"; + const luks2Id = "luks2"; useEffect(() => { if (password.length === 0) onValidate(false); @@ -399,11 +404,26 @@ const EncryptionPasswordForm = ({ const changePassword = (_, v) => setPassword(v); + const changeMethod = (_, value) => { + value ? setMethod(tpmId) : setMethod(luks2Id); + }; + const submitForm = (e) => { e.preventDefault(); - onSubmit(password); + onSubmit(password, method); }; + const Description = () => ( + TPM can verify the integrity of the system. TPM sealing requires the new system to be booted directly on its first run.") + }} + /> + ); + return (
+ } + isChecked={method === tpmId} + onChange={changeMethod} + /> + } + /> ); }; /** - * Allows to selected encryption + * Allows to define encryption * @component * * @param {object} props * @param {string} [props.password=""] - Password for encryption + * @param {string} [props.method=""] - Encryption method * @param {boolean} [props.isChecked=false] - Whether encryption is selected * @param {boolean} [props.isLoading=false] - Whether to show the selector as loading * @param {onChangeFn} [props.onChange=noop] - On change callback @@ -429,14 +462,17 @@ const EncryptionPasswordForm = ({ * @callback onChangeFn * @param {object} settings */ -const EncryptionPasswordField = ({ +const EncryptionField = ({ password: passwordProp = "", + method: methodProp = "", + methods, isChecked: isCheckedProp = false, isLoading = false, onChange = noop }) => { const [isChecked, setIsChecked] = useState(isCheckedProp); const [password, setPassword] = useState(passwordProp); + const [method, setMethod] = useState(methodProp); const [isFormOpen, setIsFormOpen] = useState(false); const [isFormValid, setIsFormValid] = useState(true); @@ -444,10 +480,11 @@ const EncryptionPasswordField = ({ const closeForm = () => setIsFormOpen(false); - const acceptForm = (newPassword) => { + const acceptForm = (newPassword, newMethod) => { closeForm(); setPassword(newPassword); - onChange({ isChecked, password: newPassword }); + setMethod(newMethod); + onChange({ isChecked, password: newPassword, method: newMethod }); }; const cancelForm = () => { @@ -468,10 +505,10 @@ const EncryptionPasswordField = ({ } }; - const ChangePasswordButton = () => { + const ChangeSettingsButton = () => { return ( - { isChecked && } + { isChecked && } - - {_("Accept")} + {_("Accept")} @@ -618,6 +657,7 @@ const SpacePolicyField = ({ * @param {ProposalSettings} props.settings * @param {StorageDevice[]} [props.availableDevices=[]] * @param {Volume[]} [props.volumeTemplates=[]] + * @param {String[]} [props.encryptionMethods=[]] * @param {boolean} [isLoading=false] * @param {onChangeFn} [props.onChange=noop] * @@ -628,6 +668,7 @@ export default function ProposalSettingsSection({ settings, availableDevices = [], volumeTemplates = [], + encryptionMethods = [], isLoading = false, onChange = noop }) { @@ -643,8 +684,8 @@ export default function ProposalSettingsSection({ onChange(settings); }; - const changeEncryption = ({ password }) => { - onChange({ encryptionPassword: password }); + const changeEncryption = ({ password, method }) => { + onChange({ encryptionPassword: password, encryptionMethod: method }); }; const changeSpacePolicy = (policy) => { @@ -673,8 +714,10 @@ export default function ProposalSettingsSection({ isLoading={settings.lvm === undefined} onChange={changeLVM} /> -