Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
mvollmer committed May 19, 2023
1 parent 9e7c48e commit a3432b6
Show file tree
Hide file tree
Showing 2 changed files with 202 additions and 23 deletions.
10 changes: 5 additions & 5 deletions pkg/storaged/crypto-keyslots.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,15 @@ const _ = cockpit.gettext;
/* Tang advertisement utilities
*/

function get_tang_adv(url) {
export function get_tang_adv(url) {
return cockpit.spawn(["curl", "-sSf", url + "/adv"], { err: "message" })
.then(JSON.parse)
.catch(error => {
return Promise.reject(error.toString().replace(/^curl: \([0-9]+\) /, ""));
});
}

function tang_adv_payload(adv) {
export function tang_adv_payload(adv) {
return JSON.parse(cockpit.utf8_decoder().decode(cockpit.base64_decode(adv.payload)));
}

Expand Down Expand Up @@ -92,7 +92,7 @@ function compute_thp(jwk) {
};
}

function compute_sigkey_thps(adv) {
export function compute_sigkey_thps(adv) {
function is_signing_key(jwk) {
if (!jwk.use && !jwk.key_ops)
return true;
Expand Down Expand Up @@ -474,7 +474,7 @@ function ensure_nbde_support_dialog(steps, client, block, url, adv, old_key, exi
});
}

function parse_url(url) {
export function parse_url(url) {
// clevis-encrypt-tang defaults to "http://" (via curl), so we do the same here.
if (!/^[a-zA-Z]+:\/\//.test(url))
url = "http://" + url;
Expand All @@ -487,7 +487,7 @@ function parse_url(url) {
}
}

function validate_url(url) {
export function validate_url(url) {
if (url.length === 0)
return _("Address cannot be empty");
if (!parse_url(url))
Expand Down
215 changes: 197 additions & 18 deletions pkg/storaged/stratis-details.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import cockpit from "cockpit";
import React from "react";

import { Card, CardBody, CardHeader, CardTitle } from '@patternfly/react-core/dist/esm/components/Card/index.js';
import { ClipboardCopy } from "@patternfly/react-core/dist/esm/components/ClipboardCopy/index.js";
import { DescriptionList, DescriptionListDescription, DescriptionListGroup, DescriptionListTerm } from "@patternfly/react-core/dist/esm/components/DescriptionList/index.js";
import { List, ListItem } from "@patternfly/react-core/dist/esm/components/List/index.js";
import { PlusIcon, ExclamationTriangleIcon } from "@patternfly/react-icons";
Expand All @@ -36,6 +37,7 @@ import {
TextInput, PassInput, SelectOne, SelectSpaces,
CheckBoxes,
BlockingMessage, TeardownMessage,
Skip,
init_active_usage_processes
} from "./dialog.jsx";

Expand All @@ -48,6 +50,7 @@ import {
} from "./utils.js";
import { fmt_to_fragments } from "utils.jsx";
import { mount_explanation } from "./format-dialog.jsx";
import { validate_url, get_tang_adv, parse_url, tang_adv_payload, compute_sigkey_thps } from "./crypto-keyslots.jsx";

const _ = cockpit.gettext;

Expand Down Expand Up @@ -263,6 +266,127 @@ export const StratisPoolDetails = ({ client, pool }) => {
});
}

function add_tang() {
const key_desc = pool.KeyDescription[1][1];
return client.stratis_list_keys()
.then(keys => {
if (keys.indexOf(key_desc) >= 0)
add_tang_with_keydesc(false);
else
add_tang_with_keydesc(key_desc);
})
.catch(ex => {
console.warn("Failed fetch properties", ex.toString());
});
}

function add_tang_with_keydesc(key_desc) {
dialog_open({
Title: _("Add Tang keyserver"),
Fields: [
TextInput("tang_url", _("Keyserver address"),
{
validate: validate_url
}),
Skip("medskip",
{
visible: () => !!key_desc
}),
PassInput("passphrase", _("Disk passphrase"),
{
visible: () => !!key_desc,
validate: val => !val.length && _("Passphrase cannot be empty"),
explanation: _("Adding a keyserver requires unlocking the pool. Please provide the existing pool passphrase.")
})
],
Action: {
Title: _("Add"),
action: function (vals, progress) {
return get_tang_adv(vals.tang_url)
.then(adv => confirm_tang_trust(vals.tang_url, adv, key_desc, vals.passphrase));
}
}
});
}

function confirm_tang_trust(url, adv, key_desc, passphrase) {
const parsed = parse_url(url);
const cmd = cockpit.format("ssh $0 tang-show-keys $1", parsed.hostname, parsed.port);

const sigkey_thps = compute_sigkey_thps(tang_adv_payload(adv));

dialog_open({
Title: _("Verify key"),
Body: (
<>
<p>{_("Make sure the key hash from the Tang server matches one of the following:")}</p>

<h2 className="sigkey-heading">{_("SHA256")}</h2>
{ sigkey_thps.map(s => <p key={s} className="sigkey-hash">{s.sha256}</p>) }

<h2 className="sigkey-heading">{_("SHA1")}</h2>
{ sigkey_thps.map(s => <p key={s} className="sigkey-hash">{s.sha1}</p>) }

<p>
{_("Manually check with SSH: ")}
<ClipboardCopy hoverTip={_("Copy to clipboard")}
clickTip={_("Successfully copied to clipboard!")}
variant="inline-compact"
isCode>
{cmd}
</ClipboardCopy>
</p>
</>
),
Action: {
Title: _("Trust key"),
action: function (vals, progress) {
function bind() {
return pool.BindClevis("tang", JSON.stringify({ url, adv }))
.then((result, code, message) => {
if (code)
return Promise.reject(message);
});
}

if (key_desc) {
return client.stratis_store_passphrase(key_desc, passphrase)
.then(bind)
.catch(ex => {
return remove_passphrase(client, key_desc)
.then(() => Promise.reject(ex));
})
.then(() => {
return remove_passphrase(client, key_desc);
});
} else
return bind();
}
}
});
}

function remove_tang() {
dialog_open({
Title: _("Remove Tang keyserver?"),
Body: <div>
<p>{ fmt_to_fragments(_("Remove $0?"), <b>{tang_url}</b>) }</p>
<p className="slot-warning">{ fmt_to_fragments(_("Keyserver removal may prevent unlocking $0."), <b>{pool.Name}</b>) }</p>
</div>,
Action: {
DangerButton: true,
Title: _("Remove"),
action: function (vals) {
return pool.UnbindClevis()
.then((result, code, message) => {
if (code)
return Promise.reject(message);
});
}
}
});
}

function rename() {
dialog_open({
Title: _("Rename Stratis pool"),
Expand Down Expand Up @@ -432,6 +556,11 @@ export const StratisPoolDetails = ({ client, pool }) => {

const use = pool.TotalPhysicalUsed[0] && [Number(pool.TotalPhysicalUsed[1]), Number(pool.TotalPhysicalSize)];

const can_tang = (pool.Encrypted &&
pool.ClevisInfo[0] && // pool has consistent clevis config
(!pool.ClevisInfo[1][0] || pool.ClevisInfo[1][1][0] == "tang")); // not bound or bound to "tang"
const tang_url = can_tang && pool.ClevisInfo[1][0] ? JSON.parse(pool.ClevisInfo[1][1][1]).url : null;

const header = (
<Card>
<CardHeader actions={{
Expand Down Expand Up @@ -460,6 +589,26 @@ export const StratisPoolDetails = ({ client, pool }) => {
</DescriptionListDescription>
</DescriptionListGroup>
}
{ can_tang &&
<DescriptionListGroup>
<DescriptionListTerm className="control-DescriptionListTerm">
{_("storage", "Keyserver")}
</DescriptionListTerm>
<DescriptionListDescription>
{ tang_url == null ? "-" : tang_url }
<DescriptionListDescription className="tab-row-actions">
{ tang_url == null
? <StorageButton onClick={add_tang}>{_("Add")}</StorageButton>
: null
}
{ tang_url != null
? <StorageButton onClick={remove_tang}>{_("Remove")}</StorageButton>
: null
}
</DescriptionListDescription>
</DescriptionListDescription>
</DescriptionListGroup>
}
</DescriptionList>
</CardBody>
</Card>
Expand Down Expand Up @@ -718,20 +867,6 @@ export function start_pool(client, uuid, show_devs) {
const devs = stopped_props.devs.v.map(d => d.devnode).sort();
let key_desc = null;

if (stopped_props.key_description &&
stopped_props.key_description.t == "(bv)" &&
stopped_props.key_description.v[0]) {
if (stopped_props.key_description.v[1].t != "(bs)" ||
!stopped_props.key_description.v[1].v[0]) {
dialog_open({
Title: _("Error"),
Body: _("This pool can not be unlocked here because its key description is not in the expected format.")
});
return;
}
key_desc = stopped_props.key_description.v[1].v[1];
}

function start(unlock_method) {
return client.stratis_start_pool(uuid, unlock_method)
.then((result, code, message) => {
Expand Down Expand Up @@ -769,9 +904,7 @@ export function start_pool(client, uuid, show_devs) {
});
}

if (!key_desc) {
return start();
} else {
function unlock_with_keyring() {
return (client.stratis_list_keys()
.catch(() => [{ }])
.then(keys => {
Expand All @@ -781,6 +914,32 @@ export function start_pool(client, uuid, show_devs) {
unlock_with_keydesc(key_desc);
}));
}

if (stopped_props.key_description &&
stopped_props.key_description.t == "(bv)" &&
stopped_props.key_description.v[0]) {
if (stopped_props.key_description.v[1].t != "(bs)" ||
!stopped_props.key_description.v[1].v[0]) {
dialog_open({
Title: _("Error"),
Body: _("This pool can not be unlocked here because its key description is not in the expected format.")
});
return;
}
key_desc = stopped_props.key_description.v[1].v[1];
}

if (!key_desc) {
// Not an encrypted pool, just start it
return start();
} else {
if (stopped_props.clevis_info
&& stopped_props.clevis_info.t == "(bv)"
&& stopped_props.clevis_info.v[0]) {
return start("clevis").catch(unlock_with_keyring);
} else
return unlock_with_keyring();
}
}

const StratisStoppedPoolSidebar = ({ client, uuid }) => {
Expand All @@ -804,13 +963,23 @@ const StratisStoppedPoolSidebar = ({ client, uuid }) => {
};

export const StratisStoppedPoolDetails = ({ client, uuid }) => {
const stopped_props = client.stratis_manager.StoppedPools[uuid];
const clevis_info = stopped_props.clevis_info;

const can_tang = clevis_info && clevis_info.v[0] && (!clevis_info.v[1].v[0] || clevis_info.v[1].v[1][0] == "tang");
const tang_url = can_tang && clevis_info.v[1].v[0] ? JSON.parse(clevis_info.v[1].v[1][1]).url : null;

function start() {
return start_pool(client, uuid);
}

const header = (
<Card>
<CardHeader actions={{ actions: <><StorageButton kind="primary" onClick={start}>{_("Start")}</StorageButton></> }}>
<CardHeader actions={{ actions: <StorageButton kind="primary"
spinner
onClick={start}>
{_("Start")}
</StorageButton> }}>
<CardTitle component="h2">{_("Stopped Stratis pool")}</CardTitle>
</CardHeader>
<CardBody>
Expand All @@ -819,6 +988,16 @@ export const StratisStoppedPoolDetails = ({ client, uuid }) => {
<DescriptionListTerm>{_("storage", "UUID")}</DescriptionListTerm>
<DescriptionListDescription>{ uuid }</DescriptionListDescription>
</DescriptionListGroup>
{ can_tang &&
<DescriptionListGroup>
<DescriptionListTerm className="control-DescriptionListTerm">
{_("storage", "Keyserver")}
</DescriptionListTerm>
<DescriptionListDescription>
{ tang_url || "-" }
</DescriptionListDescription>
</DescriptionListGroup>
}
</DescriptionList>
</CardBody>
</Card>
Expand Down

0 comments on commit a3432b6

Please sign in to comment.