/* * Copyright (C) 2023 Red Hat, Inc. * SPDX-License-Identifier: LGPL-2.1-or-later */ import cockpit from "cockpit"; import React from "react"; import client from "../client"; import { DescriptionList } from "@patternfly/react-core/dist/esm/components/DescriptionList/index.js"; import { CardBody } from "@patternfly/react-core/dist/esm/components/Card/index.js"; import { useObject, useEvent } from "hooks"; import * as python from "python.js"; import * as timeformat from "timeformat"; import { dialog_open, TextInput, PassInput } from "../dialog.jsx"; import { block_name, encode_filename, decode_filename, parse_options, unparse_options, extract_option, edit_crypto_config } from "../utils.js"; import { StorageCard, StorageDescription, new_card } from "../pages.jsx"; import luksmeta_monitor_hack_py from "./luksmeta-monitor-hack.py"; import { is_mounted } from "../filesystem/utils.jsx"; import { StorageLink } from "../storage-controls.jsx"; import { CryptoKeyslots } from "./keyslots.jsx"; const _ = cockpit.gettext; export function make_encryption_card(next, block) { return new_card({ title: _("Encryption"), next, type_extra: _("encrypted"), component: EncryptionCard, props: { block }, }); } function monitor_luks(block) { const self = { stop, luks_version: null, slots: null, slot_error: null, max_slots: null, }; cockpit.event_target(self); const dev = decode_filename(block.Device); const channel = python.spawn(luksmeta_monitor_hack_py, [dev], { superuser: "require" }); let buf = ""; channel.stream(output => { buf += output; const lines = buf.split("\n"); buf = lines[lines.length - 1]; if (lines.length >= 2) { const data = JSON.parse(lines[lines.length - 2]); self.slots = data.slots; self.luks_version = data.version; self.max_slots = data.max_slots; self.dispatchEvent("changed"); } }); channel.catch(err => { self.slots = []; self.slot_error = err; self.dispatchEvent("changed"); }); function stop() { channel.close(); } return self; } function parse_tag_mtime(tag) { if (tag && tag.indexOf("1:") === 0) { try { const parts = tag.split("-")[1].split("."); // s:ns → ms const mtime = parseInt(parts[0]) * 1000 + parseInt(parts[1]) * 1e-6; return cockpit.format(_("Last modified: $0"), timeformat.dateTime(mtime)); } catch { return null; } } else return null; } function monitor_mtime(path) { const self = { stop, mtime: 0 }; cockpit.event_target(self); let file = null; if (path) { file = cockpit.file(path, { superuser: "require" }); file.watch((_, tag) => { self.mtime = parse_tag_mtime(tag); self.dispatchEvent("changed") }, { read: false }); } function stop() { if (file) file.close(); } return self; } const EncryptionCard = ({ card, block }) => { const luks_info = useObject(() => monitor_luks(block), m => m.stop(), [block]); useEvent(luks_info, "changed"); let old_options; let passphrase_path; const old_config = block.Configuration.find(c => c[0] === "crypttab"); if (old_config) { old_options = (decode_filename(old_config[1].options.v) .split(",") .filter(function (s) { return s.indexOf("x-parent") !== 0 }) .join(",")); passphrase_path = decode_filename(old_config[1]["passphrase-path"].v); } const stored_passphrase_info = useObject(() => monitor_mtime(passphrase_path), m => m.stop(), [passphrase_path]); useEvent(stored_passphrase_info, "changed"); const split_options = parse_options(old_options); let opt_noauto = extract_option(split_options, "noauto"); const extra_options = unparse_options(split_options); function edit_stored_passphrase() { edit_crypto_config(block, function (config, commit) { dialog_open({ Title: _("Stored passphrase"), Fields: [ PassInput("passphrase", _("Stored passphrase"), { value: (config && config['passphrase-contents'] ? decode_filename(config['passphrase-contents'].v) : "") }) ], Action: { Title: _("Save"), action: function (vals) { config["passphrase-contents"] = { t: 'ay', v: encode_filename(vals.passphrase) }; delete config["passphrase-path"]; return commit(); } } }); }); } function edit_options() { const fsys_config = client.blocks_crypto[block.path]?.ChildConfiguration.find(c => c[0] === "fstab"); const content_block = client.blocks_cleartext[block.path]; const is_fsys = fsys_config || (content_block && content_block.IdUsage === "filesystem"); edit_crypto_config(block, function (config, commit) { dialog_open({ Title: _("Encryption options"), Fields: [ TextInput("options", "", { value: extra_options }), ], isFormHorizontal: false, Action: { Title: _("Save"), action: function (vals) { let opts = []; if (is_fsys && content_block) opt_noauto = !is_mounted(client, content_block); if (opt_noauto) opts.push("noauto"); opts = opts.concat(parse_options(vals.options)); config.options = { t: 'ay', v: encode_filename(unparse_options(opts)) }; return commit(); } } }); }); } const cleartext = client.blocks_cleartext[block.path]; const option_parts = []; if (extra_options) option_parts.push(extra_options); const options = option_parts.join(", "); return ( { luks_info.luks_version ? "LUKS" + luks_info.luks_version : "-" } {cleartext ? block_name(cleartext) : "-"} {_("edit")}} /> {_("edit")}} /> ); };