Skip to content

Commit

Permalink
shell: Require confirmation before connecting to remote machines
Browse files Browse the repository at this point in the history
  • Loading branch information
mvollmer committed Sep 11, 2024
1 parent c062fd7 commit 57aeb51
Show file tree
Hide file tree
Showing 13 changed files with 411 additions and 144 deletions.
12 changes: 12 additions & 0 deletions doc/man/cockpit.conf.xml
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,18 @@ IdleTimeout=15
<para>When not specified, there is no idle timeout by default.</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>WarnBeforeConnecting</option></term>
<listitem>
<para>Whether to warn before connecting to remote hosts from the Shell. Defaults to true</para>
<informalexample>
<programlisting language="ini">
[Session]
WarnBeforeConnecting=false
</programlisting>
</informalexample>
</listitem>
</varlistentry>
</variablelist>
</refsect1>

Expand Down
10 changes: 10 additions & 0 deletions pkg/shell/base_index.js
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,16 @@ function Frames(index, setupIdleResetTimers) {

/* Need to create a new frame */
if (!frame) {
/* Never create new frames for machines that are not
connected yet. That would open a channel to them (for
loading the URL), which woould trigger the bridge to
attempt a log in. We want all logins to happen in a
single place (in hosts.jsx) so that we can get the
options right, and show a warning dialog.
*/
if (host != "localhost" && machine.state !== "connected")
return null;

new_frame = true;
frame = document.createElement("iframe");
frame.setAttribute("class", "container-frame");
Expand Down
117 changes: 95 additions & 22 deletions pkg/shell/hosts.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { Tooltip } from "@patternfly/react-core/dist/esm/components/Tooltip";

import 'polyfills';
import { CockpitNav, CockpitNavItem } from "./nav.jsx";
import { HostModal } from "./hosts_dialog.jsx";
import { HostModal, try2Connect, codes } from "./hosts_dialog.jsx";
import { useLoggedInUser } from "hooks";

const _ = cockpit.gettext;
Expand Down Expand Up @@ -73,8 +73,8 @@ export class CockpitHosts extends React.Component {
editing: false,
current_user: "",
current_key: props.machine.key,
show_modal: false,
edit_machine: null,
modal_properties: null,
modal_callback: null,
};

this.toggleMenu = this.toggleMenu.bind(this);
Expand All @@ -89,6 +89,16 @@ export class CockpitHosts extends React.Component {
cockpit.user().then(user => {
this.setState({ current_user: user.name || "" });
}).catch(exc => console.log(exc));

window.trigger_connection_flow = machine => {
if (!this.state.modal_properties)
this.connectHost(machine);
};
this.props.index.navigate(null, true);
}

componentWillUnmount() {
window.trigger_connection_flow = null;
}

static getDerivedStateFromProps(nextProps, prevState) {
Expand Down Expand Up @@ -116,12 +126,80 @@ export class CockpitHosts extends React.Component {
});
}

onAddNewHost() {
this.setState({ show_modal: true });
showModal(properties) {
return new Promise((resolve, reject) => {
this.setState({ modal_properties: properties,
modal_callback: result => { resolve(result); return Promise.resolve() },
});
});
}

async onAddNewHost() {
await this.showModal({ });
}

onHostEdit(event, machine) {
this.setState({ show_modal: true, edit_machine: machine });
async onHostEdit(event, machine) {
const connection_string = await this.showModal({ address: machine.address });
if (connection_string) {
const parts = this.props.machines.split_connection_string(connection_string);
const addr = this.props.hostAddr({ host: parts.address }, true);
if (machine == this.props.machine && parts.address != machine.address) {
this.props.loader.connect(parts.address);
this.props.jump(addr);
}
}
}

async connectHost(machine) {
if (machine.address == "localhost" || machine.state == "connected" || machine.state == "connecting")
return machine.connection_string;

let connection_string = null;

if (machine.problem && codes[machine.problem]) {
// trouble shooting
connection_string = await this.showModal({
address: machine.address,
template: codes[machine.problem],
});
} else if (!window.sessionStorage.getItem("connection-warning-shown")) {
// connect by launching into the "Connection warning" dialog.
connection_string = await this.showModal({
address: machine.address,
template: "connect"
});
} else {
// Try to connect without any dialog
try {
await try2Connect(this.props.machines, machine.connection_string);
connection_string = machine.connection_string;
} catch (err) {
// continue with troubleshooting in the dialog
connection_string = await this.showModal({
address: machine.address,
template: codes[err.problem] || "change-port",
error_options: err,
});
}
}

if (connection_string) {
// make the rest of the shell aware that the machine is now connected
const parts = this.props.machines.split_connection_string(connection_string);
this.props.loader.connect(parts.address);
this.props.index.navigate();
}

return connection_string;
}

async onHostSwitch(machine) {
const connection_string = await this.connectHost(machine);
if (connection_string) {
const parts = this.props.machines.split_connection_string(connection_string);
const addr = this.props.hostAddr({ host: parts.address }, true);
this.props.jump(addr);
}
}

onEditHosts() {
Expand Down Expand Up @@ -180,7 +258,7 @@ export class CockpitHosts extends React.Component {
header={(m.user ? m.user : this.state.current_user) + " @"}
status={m.state === "failed" ? { type: "error", title: _("Connection error") } : null}
className={m.state}
jump={this.props.jump}
jump={() => this.onHostSwitch(m)}
actions={<>
<Tooltip content={_("Edit")} position="right">
<Button isDisabled={m.address === "localhost"} className="nav-action" hidden={!editing} onClick={e => this.onHostEdit(e, m)} key={m.label + "edit"} variant="secondary"><EditIcon /></Button>
Expand Down Expand Up @@ -240,20 +318,13 @@ export class CockpitHosts extends React.Component {
</HostsSelector>
}
</div>
{this.state.show_modal &&
<HostModal machines_ins={this.props.machines}
onClose={() => this.setState({ show_modal: false, edit_machine: null })}
address={this.state.edit_machine ? this.state.edit_machine.address : null}
caller_callback={this.state.edit_machine
? (new_connection_string) => {
const parts = this.props.machines.split_connection_string(new_connection_string);
if (this.state.edit_machine == this.props.machine && parts.address != this.state.edit_machine.address) {
const addr = this.props.hostAddr({ host: parts.address }, true);
this.props.jump(addr);
}
return Promise.resolve();
}
: null } />
{this.state.modal_properties &&
<HostModal machines_ins={this.props.machines}
onClose={() => this.setState({ modal_properties: null })}
{...this.state.modal_properties}
caller_callback={this.state.modal_callback}
caller_cancelled={() => this.state.modal_callback(null)}
/>
}
</>
);
Expand All @@ -263,6 +334,8 @@ export class CockpitHosts extends React.Component {
CockpitHosts.propTypes = {
machine: PropTypes.object.isRequired,
machines: PropTypes.object.isRequired,
index: PropTypes.object.isRequired,
loader: PropTypes.object.isRequired,
selector: PropTypes.string.isRequired,
hostAddr: PropTypes.func.isRequired,
jump: PropTypes.func.isRequired,
Expand Down
Loading

0 comments on commit 57aeb51

Please sign in to comment.