Skip to content

Commit

Permalink
storage: Tang keyservers for Stratis
Browse files Browse the repository at this point in the history
  • Loading branch information
mvollmer committed May 26, 2023
1 parent 4c9a322 commit c1b5523
Show file tree
Hide file tree
Showing 4 changed files with 312 additions and 54 deletions.
28 changes: 17 additions & 11 deletions pkg/storaged/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -1056,7 +1056,7 @@ function stratis2_start() {
};

client.features.stratis = true;
client.stratis_pools = client.stratis_manager.client.proxies("org.storage.stratis2.pool.r1",
client.stratis_pools = client.stratis_manager.client.proxies("org.storage.stratis2.pool.r3",
"/org/storage/stratis2",
{ watch: false });
client.stratis_blockdevs = client.stratis_manager.client.proxies("org.storage.stratis2.blockdev.r2",
Expand Down Expand Up @@ -1144,6 +1144,7 @@ function stratis2_fetch_manager_properties(proxy) {
proxy.StoppedPools[uuid] = {
devs: l.devs,
key_description: { t: "(bv)", v: [true, l.key_description] },
clevis_info: { t: "(bv)", v: [true, l.clevis_info] },
};
}
client.update();
Expand All @@ -1156,15 +1157,20 @@ function stratis2_fetch_pool_properties(proxy) {
proxy.TotalPhysicalUsed = [false, ""];
if (!proxy.KeyDescription)
proxy.KeyDescription = [false, ""];
stratis2_fetch_properties(proxy, ["TotalPhysicalSize", "TotalPhysicalUsed", "KeyDescription"]).then(values => {
if (values.TotalPhysicalSize)
proxy.TotalPhysicalSize = values.TotalPhysicalSize;
if (values.TotalPhysicalUsed)
proxy.TotalPhysicalUsed = [true, values.TotalPhysicalUsed];
if (values.KeyDescription)
proxy.KeyDescription = [true, values.KeyDescription];
client.update();
});
if (!proxy.ClevisInfo)
proxy.ClevisInfo = [false, ["", ""]];
stratis2_fetch_properties(proxy, ["TotalPhysicalSize", "TotalPhysicalUsed", "KeyDescription", "ClevisInfo"])
.then(values => {
if (values.TotalPhysicalSize)
proxy.TotalPhysicalSize = values.TotalPhysicalSize;
if (values.TotalPhysicalUsed)
proxy.TotalPhysicalUsed = [true, values.TotalPhysicalUsed];
if (values.KeyDescription)
proxy.KeyDescription = [true, values.KeyDescription];
if (values.ClevisInfo)
proxy.ClevisInfo = [true, values.ClevisInfo];
client.update();
});
}

function stratis2_fetch_pool_properties_by_path(path) {
Expand Down Expand Up @@ -1225,7 +1231,7 @@ function stratis2_fixup_pool_notifications(data) {
const props = data[path][iface];
if (props && props.Name) {
// The pool at 'path' got renamed.
fixup_data[path] = { "org.storage.stratis2.pool.r1": { Name: props.Name } };
fixup_data[path] = { "org.storage.stratis2.pool.r3": { Name: props.Name } };
for (const fsys of client.stratis_pool_filesystems[path]) {
fixup_data[fsys.path] = {
"org.storage.stratis2.filesystem": {
Expand Down
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
171 changes: 153 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";

import { std_reply, with_keydesc, with_stored_passphrase } from "./stratis-utils.js";

Expand Down Expand Up @@ -207,6 +209,82 @@ export const StratisPoolDetails = ({ client, pool }) => {
});
}

function add_tang() {
return with_keydesc(client, pool, (keydesc, keydesc_set) => {
dialog_open({
Title: _("Add Tang keyserver"),
Fields: [
TextInput("tang_url", _("Keyserver address"),
{
validate: validate_url
}),
Skip("medskip",
{
visible: () => !keydesc_set
}),
PassInput("passphrase", _("Disk passphrase"),
{
visible: () => !keydesc_set,
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, keydesc, vals.passphrase));
}
}
});
});
}

function confirm_tang_trust(url, adv, keydesc, passphrase) {
dialog_open({
Title: _("Verify key"),
Body: <TangKeyVerification url={url} adv={adv} />,
Action: {
Title: _("Trust key"),
action: function (vals, progress) {
function bind() {
if (pool.BindClevis) {
return pool.BindClevis("tang", JSON.stringify({ url, adv })).then(std_reply);
} else {
return pool.Bind("tang", JSON.stringify({ url, adv })).then(std_reply);
}
}

if (passphrase)
return with_stored_passphrase(client, keydesc, passphrase, bind);
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) {
if (pool.UnbindClevis) {
return pool.UnbindClevis().then(std_reply);
} else {
return pool.Unbind().then(std_reply);
}
}
}
});
}

function rename() {
dialog_open({
Title: _("Rename Stratis pool"),
Expand Down Expand Up @@ -370,6 +448,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 @@ -398,6 +481,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 @@ -650,20 +753,7 @@ export function start_pool(client, uuid, show_devs) {
const stopped_props = manager.StoppedPools[uuid];
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];
}
let has_clevis = false;

function start(unlock_method) {
return client.stratis_start_pool(uuid, unlock_method).then(std_reply);
Expand Down Expand Up @@ -691,9 +781,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 @@ -703,6 +791,31 @@ 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] &&
stopped_props.key_description.v[1].t == "(bs)" &&
stopped_props.key_description.v[1].v[0]) {
key_desc = stopped_props.key_description.v[1].v[1];
}

if (stopped_props.clevis_info &&
stopped_props.clevis_info.t == "(bv)" &&
stopped_props.clevis_info.v[0] &&
stopped_props.clevis_info.v[1].t == "(b(ss))" &&
stopped_props.clevis_info.v[1].v[0]) {
has_clevis = true;
}

if (!key_desc && !has_clevis) {
// Not an encrypted pool, just start it
return start();
} else if (has_clevis) {
return start("clevis").catch(unlock_with_keyring);
} else {
return unlock_with_keyring();
}
}

const StratisStoppedPoolSidebar = ({ client, uuid }) => {
Expand All @@ -726,13 +839,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 @@ -741,6 +866,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 c1b5523

Please sign in to comment.