Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
mvollmer committed May 22, 2023
1 parent 282c0b3 commit 7b838e5
Show file tree
Hide file tree
Showing 3 changed files with 305 additions and 43 deletions.
51 changes: 26 additions & 25 deletions pkg/storaged/crypto-keyslots.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ 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 => {
Expand Down Expand Up @@ -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 Expand Up @@ -606,35 +606,36 @@ function add_or_update_tang(dlg, vals, block, url, adv, old_key, passphrase) {
.catch(request_passphrase_on_error_handler(dlg, vals, passphrase, block));
}

function edit_tang_adv(client, block, key, url, adv, passphrase) {
export const TangKeyVerification = ({ url, adv }) => {
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));

return <>
<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>
</>;
};

function edit_tang_adv(client, block, key, url, adv, passphrase) {
const dlg = 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>
</>
),
Body: <TangKeyVerification url={url} adv={adv} />,
Fields: existing_passphrase_fields(_("Saving a new passphrase requires unlocking the disk. Please provide a current disk passphrase.")),
Action: {
Title: _("Trust key"),
Expand Down
191 changes: 173 additions & 18 deletions pkg/storaged/stratis-details.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import {
TextInput, PassInput, SelectOne, SelectSpaces,
CheckBoxes,
BlockingMessage, TeardownMessage,
Skip,
init_active_usage_processes
} from "./dialog.jsx";

Expand All @@ -48,6 +49,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, TangKeyVerification } from "./crypto-keyslots.jsx";

const _ = cockpit.gettext;

Expand Down Expand Up @@ -263,6 +265,102 @@ 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) {
dialog_open({
Title: _("Verify key"),
Body: <TangKeyVerification url={url} adv={adv} />,
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 +530,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 +563,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 +841,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 +878,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 +888,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 +937,25 @@ 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 +964,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
Loading

0 comments on commit 7b838e5

Please sign in to comment.