Skip to content

Commit

Permalink
shell, login: Overhaul SSH host key handling
Browse files Browse the repository at this point in the history
New hostkeys are now added to ~/.ssh/known_hosts instead of
/etc/ssh/ssh_known_hosts, so that also non-admin users can do it.
Also, the /etc/ssh/ssh_known_hosts file is meant to be populated by
the sysadmin, so it is best for Cockpit to leave it alone.

The login page now automatically sets the #server-field input when
there is a "/=HOST" redirection in the URL. Otherwise trying to log in
will fail with a surprising "Unknown host" error message.

The login page now remembers accepted SSH hostkeys in localStorage. It
also has a dedicated UI for this, which avoids having to put the
fingerprint into a text input.

Fixes cockpit-project#14482
  • Loading branch information
mvollmer committed Dec 11, 2020
1 parent c62adf2 commit 820430e
Show file tree
Hide file tree
Showing 12 changed files with 356 additions and 235 deletions.
106 changes: 76 additions & 30 deletions doc/guide/authentication.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,30 @@
<chapter id="authentication">
<title>Cockpit Authentication</title>

<section id="initial-auth">
<title>Primary server authentication</title>

<para>While cockpit allows you to monitor and administer several servers at the
same time, there is always a primary server your browser connects to
that runs the Cockpit web service (cockpit-ws) through which connections to
additional servers are established.
See <ulink url="https://raw.githubusercontent.com/cockpit-project/cockpit/master/doc/cockpit-transport.png">this diagram</ulink> for how it works.</para>

<para>Normally, a session is established on the primary server,
and you use the Shell UI of that session to connect to secondary
servers.</para>

<para>However, it is also possible to instruct the
<filename>cockpit-ws</filename> process on the primary server to
directly connect to a secondary server, without opening a
session on the primary server at all. This is done on the main
login page of Cockpit, by filling out the "Connect to"
field.</para>

<section id="initial-auth">
<title>Directly logging into the primary server</title>

<para>The most common way to use Cockpit is to just log directly
into the server that you want to access. This can be done if you
have direct network access to port 9090 on that server.</para>

<para>By default the cockpit web service is installed on the base system and
<link linkend="listen">socket activated by systemd</link>. In this setup
access is controlled by a cockpit specific pam stack, generally located
Expand Down Expand Up @@ -47,18 +62,27 @@
</section>

<section id="secondary-auth">
<title>Secondary server authentication</title>
<title>Logging into a secondary server from the primary session</title>

<para>Once you have a session on the primary server you will be
able to connect to additional servers by using the host switching
UI of the Cockpit Shell. This is useful if you have direct network
access to the primary server, but not to the secondary server.</para>

<para>On the command line, you would log into the primary server
and then use SSH to log into the secondary one. Cockpit does just
the same, and uses SSH to log into the secondary server. Instead
of running a interactive shell there, however, it starts a
<filename>cockpit-bridge</filename> process.</para>

<para>Once you are able to login to the primary server you will be able to connect to
additional servers. These servers will need to be running an SSH server on port 22
and be configured to support one of the following authentication methods.</para>
<para>Thus, these servers will need to be running an SSH server on
port 22 and be configured to support one of the following
authentication methods.</para>

<section id="password">
<title>Password</title>
<para>The target server will need to have password based authentication
enabled in <filename>sshd</filename>. When this is setup for the first time,
Cockpit will ensure that the user connected to primary server has the
same password on the secondary server.</para>
enabled in <filename>sshd</filename>.</para>
</section>

<section id="kerberos">
Expand All @@ -72,31 +96,53 @@
<section id="public-key">
<title>Public key</title>

<para>When you successfully log into the primary server, an <filename>ssh-agent
</filename> is started. The following keys are then preloaded into the
<filename>ssh-agent</filename> provided they are supported by your SSH
version, present, with the correct permissions, and either unencrypted
or encrypted with the same password that was used to login.</para>
<para>When you successfully log into the primary server, a
<filename>ssh-agent</filename> is started and keys are loaded into
it by running <filename>ssh-add</filename> without any arguments.
Any passphrase prompt is answered with the password used to log
into the primary server.</para>

<programlisting>
~/.ssh/id_rsa
~/.ssh/id_dsa
~/.ssh/id_ed25519
~/.ssh/id_ecdsa
</programlisting>

<para>Cockpit provides an interface for loading other keys into the agent
<para>Cockpit provides a user interface for loading other keys into the agent
that could not be automatically loaded.</para>

<para>The target server will need to have public key authentication enabled in
<filename>sshd</filename>, and the public key you wish to use must be present in
<filename>~/.ssh/authorized_keys</filename>.</para>
<para>The target server will need to have public key
authentication enabled in <filename>sshd</filename>, and the
public key you wish to use must be present in
<filename>~/.ssh/authorized_keys</filename>. Cockpit has a user
interface for creating SSH keys and for authorizing them.</para>

<para>Note that when a user is authenticated in this way the authentication
happens without a password, as such the standard cockpit reauthorization
mechanisms do not work. The user will only be able to obtain additional
privileges if they do not require a password.</para>
</section>

<section id="host-keys">
<title>SSH host keys</title>
<para>Cockpit will prompt the user to verify unknown SSH host
keys, and will write accepted host keys into
<filename>~/.ssh/known_hosts</filename>.</para>
</section>
</section>

<section id="direct-secondary-auth">
<title>Directly logging into a secondary server without a primary session</title>

<para>It is also possible to log into a secondary server without
opening a session on the primary server. This is useful if you
are not actually interested in the primary server and would only
use it because you do not have direct network access to the
secondary server.</para>

<para>In this case, <filename>cockpit-ws</filename> still runs on
the primary server, but the credentials from the login screen are
directly used with SSH to log into the secondary server given in
the "Connect To" field of the login screen.</para>

<para>Thus, the PAM configuration and accounts on the primaray
server don't matter at all. Often, the only purpose of the primary
server is to sit on the boundary of your network and forward
connections to internal machines.</para>

<para>In this case, the login page will prompt you to verify
unknown SSH keys. Accepted keys will be remembered in the local
storage of your browser.</para>
</section>

</chapter>
15 changes: 3 additions & 12 deletions pkg/lib/machines.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import $ from "jquery";
import cockpit from "cockpit";

import ssh_add_key_sh from "raw-loader!ssh-add-key.sh";

var mod = { };

var known_hosts_path = "/etc/ssh/ssh_known_hosts";
/*
* We share the Machines state between multiple frames. Only
* one frame has the job of loading the state, usually index.js
Expand Down Expand Up @@ -218,17 +219,7 @@ function Machines() {
}

self.add_key = function(host_key) {
var known_hosts = cockpit.file(known_hosts_path, { superuser: "try" });
return known_hosts
.modify(function(data) {
if (!data)
data = "";

return data + "\n" + host_key;
})
.always(function() {
known_hosts.close();
});
return cockpit.script(ssh_add_key_sh, [host_key.trim(), "known_hosts"], { err: "message" });
};

self.add = function add(connection_string, color) {
Expand Down
3 changes: 2 additions & 1 deletion pkg/lib/ssh-add-key.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
set -euf

d=$HOME/.ssh
f=$d/authorized_keys
p=${2:-authorized_keys}
f=$d/$p

if ! test -f "$f"; then
mkdir -m 700 -p "$d"
Expand Down
101 changes: 85 additions & 16 deletions src/ws/login.css
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,25 @@ body {
line-height: 1.5;
}

h1, h2, h3, h4 {
font-size: 1.25rem;
margin: 0;
}

h1 {
font-size: 1.75rem;
line-height: 1.3;
padding: 0 0 1rem;
}

h2 {
font-size: 1.5rem;
}

pre {
white-space: pre-wrap;
}

.pf-c-button,
label {
font-weight: 600;
Expand Down Expand Up @@ -106,6 +125,8 @@ textarea {
button {
-webkit-appearance: button;
overflow: visible;
border-radius: 0.1875rem;
border: none;
}

button::-moz-focus-inner,
Expand All @@ -132,10 +153,16 @@ p {
margin-bottom: 2rem;
}

.form-group + .form-group {
.form-group:not([hidden]) + .form-group {
margin-top: 1rem;
}

.login-actions {
display: flex;
align-items: baseline;
grid-gap: 1rem;
}

label {
display: inline-block;
}
Expand Down Expand Up @@ -239,7 +266,7 @@ label.checkbox {
display: inline-block;
}

#login-button[disabled] .spinner {
#login-button[spinning] .spinner {
display: inline-flex;
}

Expand All @@ -253,8 +280,9 @@ label.checkbox {
background-image: none;
border: 1px solid transparent;
white-space: nowrap;
padding: 2px 6px;
padding: 0.25rem 0.5rem;
border-radius: 1px;
font-size: inherit;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
Expand All @@ -277,12 +305,6 @@ label.checkbox {
width: device-width;
}

.pf-c-button {
padding: 0.25rem 0.5rem;
border-radius: 1px;
font-size: inherit;
}

.pf-m-primary {
background-color: #06c;
border-color: #06c;
Expand All @@ -295,10 +317,20 @@ label.checkbox {
background-color: #004080;
border-color: #004080;
color: #fff;
outline: none;
}

.pf-m-primary:active {
background-image: none;
.pf-m-danger {
background-color: #c9190b;
border-color: #c9190b;
color: #fff;
}

.pf-m-danger:hover,
.pf-m-danger:focus {
background-color: #a30000;
border-color: #a30000;
outline: none;
}

.login-pf {
Expand Down Expand Up @@ -431,6 +463,19 @@ label.checkbox {
color: #a30000;
}

.pf-c-alert.pf-m-inline.pf-m-warning {
background: #fdf7e7;
border-color: #f0ab00;
}

.pf-c-alert.pf-m-warning > svg {
color: #f0ab00;
}

.pf-c-alert.pf-m-warning .pf-c-alert__title {
color: #795600;
}

.pf-c-alert.pf-m-inline.pf-m-info {
background: #e7f1fa;
border-color: #73bcf7;
Expand Down Expand Up @@ -489,8 +534,24 @@ label.checkbox {
border-color: rgba(255, 255, 255, .75) rgba(255, 255, 255, .25) rgba(255, 255, 255, .25);
}

#hostkey-fingerprint {
font-size: large;
font-weight: bold;
margin-bottom: 0px
}

#hostkey-type {
font-size: small;
}

#hostkey-verify-help-cmds {
border-radius: 0.25rem;
padding: 0.5rem 1rem;
background: #ddd;
}

#login-button .spinner,
#login-button[disabled] #login-button-text,
#login-button[spinning] #login-button-text,
.hide-before:before {
display: none;
}
Expand Down Expand Up @@ -552,7 +613,12 @@ label.checkbox {
align-items: center;
justify-content: center;
height: 2.5rem;
padding: 0.5rem;
padding: 0.5rem 1rem;
flex-basis: 100%;
}

#hostkey-group:not([hidden]) ~ .login-actions > #login-button {
flex-basis: max-content;
}

#login-button[disabled] {
Expand Down Expand Up @@ -673,10 +739,13 @@ body.login-pf {
}

.login-pf #brand {
font-size: 1.75rem;
line-height: 1.3;
padding: 0 0 1rem;
text-transform: initial;
font-weight: 400;
}

#brand strong,
#brand b {
font-weight: 600;
}

.unsupported-browser a,
Expand Down
Loading

0 comments on commit 820430e

Please sign in to comment.