Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

storage: Readonly crypto improvements #20148

Merged
merged 4 commits into from
Mar 26, 2024
Merged
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
36 changes: 36 additions & 0 deletions pkg/storaged/block/actions.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* This file is part of Cockpit.
*
* Copyright (C) 2024 Red Hat, Inc.
*
* Cockpit is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* Cockpit is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Cockpit; If not, see <http://www.gnu.org/licenses/>.
*/

import cockpit from "cockpit";
import client from "../client";

import { format_dialog } from "./format-dialog.jsx";

const _ = cockpit.gettext;

export function std_format_action(backing_block, content_block) {
const excuse = backing_block.ReadOnly ? _("Device is read-only") : null;

return {
title: _("Format"),
action: () => format_dialog(client, backing_block.path),
excuse,
danger: true
};
}
38 changes: 29 additions & 9 deletions pkg/storaged/block/format-dialog.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ function format_dialog_internal(client, path, start, size, enable_dos_extended,
const content_block = block.IdUsage == "crypto" ? client.blocks_cleartext[path] : block;

const offer_keep_keys = block.IdUsage == "crypto";
const unlock_before_format = offer_keep_keys && !content_block;
const unlock_before_format = offer_keep_keys && (!content_block || content_block.ReadOnly);

const create_partition = (start !== undefined);

Expand Down Expand Up @@ -230,6 +230,8 @@ function format_dialog_internal(client, path, start, size, enable_dos_extended,
extract_option(crypto_split_options, "noauto");
extract_option(crypto_split_options, "nofail");
extract_option(crypto_split_options, "_netdev");
extract_option(crypto_split_options, "readonly");
extract_option(crypto_split_options, "read-only");
const crypto_extra_options = unparse_options(crypto_split_options);

let [, old_dir, old_opts] = get_fstab_config(block, true,
Expand Down Expand Up @@ -438,6 +440,8 @@ function format_dialog_internal(client, path, start, size, enable_dos_extended,
if (is_encrypted(vals)) {
let opts = [];
if (is_filesystem(vals)) {
if (vals.mount_options?.ro)
opts.push("readonly");
if (!mount_now || vals.at_boot == "never")
opts.push("noauto");
if (vals.at_boot == "nofail")
Expand Down Expand Up @@ -516,17 +520,24 @@ function format_dialog_internal(client, path, start, size, enable_dos_extended,
if (config_items.length > 0)
options["config-items"] = { t: 'a(sa{sv})', v: config_items };

function maybe_unlock() {
async function maybe_unlock() {
const content_block = client.blocks_cleartext[path];
if (content_block)
if (content_block) {
if (content_block.ReadOnly) {
const block_crypto = client.blocks_crypto[path];
await block_crypto.Lock({});
await unlock_with_type(client, block, vals.old_passphrase, existing_passphrase_type, false);
}
return content_block;
}

return (unlock_with_type(client, block, vals.old_passphrase, existing_passphrase_type)
.catch(error => {
dlg.set_values({ needs_explicit_passphrase: true });
return Promise.reject(error);
})
.then(() => client.blocks_cleartext[path]));
try {
await unlock_with_type(client, block, vals.old_passphrase, existing_passphrase_type, false);
return client.blocks_cleartext[path];
} catch (error) {
dlg.set_values({ needs_explicit_passphrase: true });
throw error;
Comment on lines +537 to +539
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These 3 added lines are not executed by any test.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe throwing here will break, as it is unhandled by the caller.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is also caught by the dialog machinery.

}
}

function format() {
Expand Down Expand Up @@ -588,6 +599,15 @@ function format_dialog_internal(client, path, start, size, enable_dos_extended,
if (is_encrypted(vals))
remember_passphrase(new_block, vals.passphrase);

if (is_encrypted(vals) && is_filesystem(vals) && vals.mount_options?.ro) {
const block_crypto = await client.wait_for(() => block_crypto_for_block(path));
await block_crypto.Lock({});
if (vals.passphrase)
await block_crypto.Unlock(vals.passphrase, { "read-only": { t: "b", v: true } });
else
await unlock_with_type(client, block, vals.old_passphrase, existing_passphrase_type, true);
}

if (is_filesystem(vals) && mount_now) {
const block_fsys = await client.wait_for(() => block_fsys_for_block(path));
await client.mount_at(client.blocks[block_fsys.path], mount_point);
Expand Down
5 changes: 2 additions & 3 deletions pkg/storaged/block/unformatted-data.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,9 @@
*/

import cockpit from "cockpit";
import client from "../client";

import { StorageCard, new_card } from "../pages.jsx";
import { format_dialog } from "./format-dialog.jsx";
import { std_format_action } from "./actions.jsx";
import { std_lock_action } from "../crypto/actions.jsx";

const _ = cockpit.gettext;
Expand All @@ -33,7 +32,7 @@ export function make_unformatted_data_card(next, backing_block, content_block) {
component: StorageCard,
actions: [
std_lock_action(backing_block, content_block),
{ title: _("Format"), action: () => format_dialog(client, backing_block.path), danger: true },
std_format_action(backing_block, content_block),
]
});
}
5 changes: 2 additions & 3 deletions pkg/storaged/block/unrecognized-data.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,12 @@

import cockpit from "cockpit";
import React from "react";
import client from "../client";

import { CardBody } from "@patternfly/react-core/dist/esm/components/Card/index.js";
import { DescriptionList } from "@patternfly/react-core/dist/esm/components/DescriptionList/index.js";

import { StorageCard, StorageDescription, new_card } from "../pages.jsx";
import { format_dialog } from "./format-dialog.jsx";
import { std_format_action } from "./actions.jsx";
import { std_lock_action } from "../crypto/actions.jsx";

const _ = cockpit.gettext;
Expand All @@ -38,7 +37,7 @@ export function make_unrecognized_data_card(next, backing_block, content_block)
props: { backing_block, content_block },
actions: [
std_lock_action(backing_block, content_block),
{ title: _("Format"), action: () => format_dialog(client, backing_block.path), danger: true },
std_format_action(backing_block, content_block),
]
});
}
Expand Down
4 changes: 2 additions & 2 deletions pkg/storaged/btrfs/device.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import { DescriptionList } from "@patternfly/react-core/dist/esm/components/Desc
import { StorageCard, StorageDescription, new_card, register_crossref } from "../pages.jsx";
import { StorageUsageBar } from "../storage-controls.jsx";
import { std_lock_action } from "../crypto/actions.jsx";
import { format_dialog } from "../block/format-dialog.jsx";
import { std_format_action } from "../block/actions.jsx";
import { btrfs_device_usage } from "./utils.jsx";

const _ = cockpit.gettext;
Expand Down Expand Up @@ -90,7 +90,7 @@ export function btrfs_device_actions(backing_block, content_block) {
if (backing_block && content_block)
return [
std_lock_action(backing_block, content_block),
{ title: _("Format"), action: () => format_dialog(client, backing_block.path), danger: true },
std_format_action(backing_block, content_block),
];
else
return [];
Expand Down
4 changes: 1 addition & 3 deletions pkg/storaged/crypto/actions.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import client from "../client";
import { get_existing_passphrase, unlock_with_type } from "./keyslots.jsx";
import { set_crypto_auto_option } from "../utils.js";
import { dialog_open, PassInput } from "../dialog.jsx";
import { remember_passphrase } from "../anaconda.jsx";

const _ = cockpit.gettext;

Expand All @@ -45,8 +44,7 @@ export function unlock(block) {
Action: {
Title: _("Unlock"),
action: async function (vals) {
await crypto.Unlock(vals.passphrase, {});
remember_passphrase(block, vals.passphrase);
await unlock_with_type(client, block, vals.passphrase, null);
await set_crypto_auto_option(block, true);
}
}
Expand Down
54 changes: 44 additions & 10 deletions pkg/storaged/crypto/keyslots.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

import cockpit from "cockpit";
import React from "react";
import client from "../client.js";

import { CardBody, CardHeader, CardTitle } from '@patternfly/react-core/dist/esm/components/Card/index.js';
import { Checkbox } from "@patternfly/react-core/dist/esm/components/Checkbox/index.js";
Expand All @@ -38,7 +39,10 @@ import {
dialog_open,
SelectOneRadio, TextInput, PassInput, Skip
} from "../dialog.jsx";
import { decode_filename, encode_filename, get_block_mntopts, block_name, for_each_async, get_children, parse_options, unparse_options, edit_crypto_config } from "../utils.js";
import {
decode_filename, encode_filename, get_block_mntopts, block_name, for_each_async, get_children,
parse_options, extract_option, unparse_options, edit_crypto_config
} from "../utils.js";
import { StorageButton } from "../storage-controls.jsx";

import clevis_luks_passphrase_sh from "./clevis-luks-passphrase.sh";
Expand Down Expand Up @@ -72,22 +76,51 @@ export function clevis_recover_passphrase(block, just_type) {
.then(output => output.trim());
}

function clevis_unlock(block) {
async function clevis_unlock(client, block, luksname, readonly) {
const dev = decode_filename(block.Device);
const clear_dev = "luks-" + block.IdUUID;
return cockpit.spawn(["clevis", "luks", "unlock", "-d", dev, "-n", clear_dev],
{ superuser: true });
const clear_dev = luksname || "luks-" + block.IdUUID;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This added line is not executed by any test.


if (readonly) {
// HACK - clevis-luks-unlock can not unlock things readonly.
// But see https://github.com/latchset/clevis/pull/317 (merged
// Feb 2023, unreleased as of Feb 2024).
const passphrase = await clevis_recover_passphrase(block, false);
const crypto = client.blocks_crypto[block.path];
const unlock_options = { "read-only": { t: "b", v: readonly } };
await crypto.Unlock(passphrase, unlock_options);
return;
}

await cockpit.spawn(["clevis", "luks", "unlock", "-d", dev, "-n", clear_dev],
{ superuser: true });
}

export async function unlock_with_type(client, block, passphrase, passphrase_type) {
export async function unlock_with_type(client, block, passphrase, passphrase_type, override_readonly) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bit of a nit, but why not override_readonly = false?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

override_readonly has three meanings:

  • null / undefined: don't override, take readonly-nesss from /etc/crypttab
  • true: make it readonly, regradless of what cryttab says
  • false: make it readwrite, regardless of what crypttab says

The default if not specified is "don't override".

const crypto = client.blocks_crypto[block.path];
let readonly = false;
let luksname = null;

for (const c of block.Configuration) {
if (c[0] == "crypttab") {
const options = parse_options(decode_filename(c[1].options.v));
readonly = extract_option(options, "readonly") || extract_option(options, "read-only");
luksname = decode_filename(c[1].name.v);
break;
}
}

if (override_readonly !== null && override_readonly !== undefined)
readonly = override_readonly;

const unlock_options = { "read-only": { t: "b", v: readonly } };

if (passphrase) {
await crypto.Unlock(passphrase, {});
await crypto.Unlock(passphrase, unlock_options);
remember_passphrase(block, passphrase);
} else if (passphrase_type == "stored") {
await crypto.Unlock("", {});
await crypto.Unlock("", unlock_options);
} else if (passphrase_type == "clevis") {
await clevis_unlock(block);
await clevis_unlock(client, block, luksname, readonly);
} else {
// This should always be caught and should never show up in the UI
throw new Error("No passphrase");
Expand Down Expand Up @@ -187,7 +220,8 @@ export function init_existing_passphrase(block, just_type, callback) {
return {
title: _("Unlocking disk"),
func: dlg => {
return get_existing_passphrase(block, just_type).then(passphrase => {
const backing = client.blocks[block.CryptoBackingDevice];
return get_existing_passphrase(backing || block, just_type).then(passphrase => {
if (!passphrase)
dlg.set_values({ needs_explicit_passphrase: true });
if (callback)
Expand Down
5 changes: 2 additions & 3 deletions pkg/storaged/crypto/locked-encrypted-data.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,9 @@
*/

import cockpit from "cockpit";
import client from "../client";

import { StorageCard, new_card } from "../pages.jsx";
import { format_dialog } from "../block/format-dialog.jsx";
import { std_format_action } from "../block/actions.jsx";
import { unlock } from "./actions.jsx";

const _ = cockpit.gettext;
Expand All @@ -35,7 +34,7 @@ export function make_locked_encrypted_data_card(next, block) {
props: { block },
actions: [
{ title: _("Unlock"), action: () => unlock(block) },
{ title: _("Format"), action: () => format_dialog(client, block.path), danger: true },
std_format_action(block, null),
]
});
}
4 changes: 3 additions & 1 deletion pkg/storaged/dialog.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -863,11 +863,12 @@ export const SelectSpace = (tag, title, options) => {
};
};

const CheckBoxComponent = ({ tag, val, title, tooltip, update_function }) => {
const CheckBoxComponent = ({ tag, val, title, tooltip, disabled, update_function }) => {
return (
<Checkbox data-field={tag} data-field-type="checkbox"
id={tag}
isChecked={val}
isDisabled={disabled}
label={
<>
{title}
Expand Down Expand Up @@ -905,6 +906,7 @@ export const CheckBoxes = (tag, title, options) => {
tag={ftag}
val={fval}
title={field.title}
disabled={field.disabled}
tooltip={field.tooltip}
options={options}
update_function={fchange} />;
Expand Down
4 changes: 2 additions & 2 deletions pkg/storaged/filesystem/filesystem.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import {
import { StorageLink, StorageUsageBar, StorageSize } from "../storage-controls.jsx";
import { StorageCard, StorageDescription, new_card, useIsNarrow } from "../pages.jsx";

import { format_dialog } from "../block/format-dialog.jsx";
import { std_format_action } from "../block/actions.jsx";
import { is_mounted, MountPoint, mount_point_text, edit_mount_point } from "./utils.jsx";
import { mounting_dialog } from "./mounting-dialog.jsx";
import { check_mismounted_fsys, MismountAlert } from "./mismounting.jsx";
Expand Down Expand Up @@ -101,7 +101,7 @@ export function make_filesystem_card(next, backing_block, content_block, fstab_c
content_block && mounted
? { title: _("Unmount"), action: () => mounting_dialog(client, content_block, "unmount") }
: { title: _("Mount"), action: () => mounting_dialog(client, content_block || backing_block, "mount") },
{ title: _("Format"), action: () => format_dialog(client, backing_block.path), danger: true },
std_format_action(backing_block, content_block),
]
});
}
Expand Down
Loading