Skip to content

Commit

Permalink
containers/ws: Support all OpenSSH key types
Browse files Browse the repository at this point in the history
Load a provided SSH key into ssh-agent, and let cockpit-ssh use it from
there. This is the same approach that we use with "regular" cockpit and
connecting to remote hosts in the shell.

This drops the restriction that keys must be in the old RSA PEM format. SSH has
moved to the proprietary "OpenSSH" format a few years ago, but openssl (or
anything else other than SSH itself) cannot decrypt these any more.

Unfortunately ssh-add does not allow reading the password from stdin, so
construct and use an askpass agent (similar to Client).

Restrict the lifetime of keys in the agent to 30 seconds. That gives the login
process enough time to connect, but avoids keeping the unencrypted key in
memory indefinitely.
  • Loading branch information
martinpitt committed Jul 11, 2022
1 parent f723065 commit 39dcbf9
Show file tree
Hide file tree
Showing 4 changed files with 41 additions and 9 deletions.
2 changes: 1 addition & 1 deletion containers/ws/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ You can also mount an encrypted private key inside the container and set the env

-e COCKPIT_SSH_KEY_PATH=/id_rsa -v ~/.ssh/id_rsa:/id_rsa:ro,Z

Then cockpit will use the provided password to decrypt the key and establish an SSH connection to the given host using that private key. **Warning:** This currently only supports RSA keys in the old PEM file format (`ssh-keygen -t rsa -m PEM`), not the current OpenSSH specific format.
Then cockpit will use the provided password to decrypt the key and establish an SSH connection to the given host using that private key.

## More Info

Expand Down
34 changes: 27 additions & 7 deletions containers/ws/cockpit-auth-ssh-key
Original file line number Diff line number Diff line change
Expand Up @@ -134,16 +134,36 @@ def decode_basic_header(response):
return user, password


def send_decrypted_key(fname, password):
p = subprocess.run(["openssl", "rsa", "-in", fname, "-passin", "stdin"],
check=False, capture_output=True, encoding="UTF-8",
input=password)
def load_key(fname, password):
# ssh-add has the annoying behavior that it re-asks without any limit if the password is wrong
# to mitigate, self-destruct to only allow one iteration
with open("/run/askpass", "w") as fd:
fd.write("""#!/bin/sh
rm -f $0
cat /run/password""")

os.fchmod(fd.fileno(), 0o755)

env = os.environ.copy()
env["SSH_ASKPASS_REQUIRE"] = "force"
env["SSH_ASKPASS"] = "/run/askpass"

pass_fd = os.open("/run/password", os.O_CREAT | os.O_EXCL | os.O_WRONLY | os.O_CLOEXEC, mode=0o600)
try:
os.write(pass_fd, password.encode("UTF-8"))
os.close(pass_fd)

p = subprocess.run(["ssh-add", "-t", "30", fname],
check=False, env=env,
capture_output=True, encoding="UTF-8")
finally:
os.unlink("/run/password")

if p.returncode == 0:
send_auth_command(None, f"private-key {p.stdout}")
send_auth_command(None, "ssh-agent")
return True
else:
print("Couldn't open private key:", p.stderr, file=sys.stderr)
print("Couldn't load private key:", p.stderr, file=sys.stderr)
return False


Expand All @@ -162,7 +182,7 @@ def main(args):
send_problem_init("internal-error", str(e), {})
raise

if send_decrypted_key(key_name, password):
if load_key(key_name, password):
host = f"{user}@{host}"
else:
send_problem_init("authentication-failed", "Couldn't open private key",
Expand Down
2 changes: 2 additions & 0 deletions containers/ws/label-run
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,7 @@ else
mountpoint --quiet /etc/cockpit/cockpit.conf || ln -s /container/default-bastion.conf /etc/cockpit/cockpit.conf

/usr/libexec/cockpit-certificate-ensure

eval $(ssh-agent)
exec /usr/libexec/cockpit-ws --local-ssh "$@"
fi
12 changes: 11 additions & 1 deletion test/verify/check-ws-bastion
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ class TestWsBastionContainer(MachineCase):
b = self.browser

KEY_PASSWORD = "sshfoobar"
# FIXME: cockpit-auth-ssh-key only understands PEM format RSA keys
# old RSA/PEM format
m.execute(f"ssh-keygen -q -f /root/id_bastion -t rsa -m PEM -N {KEY_PASSWORD}")
m.execute(f"ssh-keyscan localhost | sed 's/^localhost/{HOST}/' > /root/known_hosts")
self.addCleanup(m.execute, "rm /root/known_hosts /root/id_bastion /root/id_bastion.pub")
Expand Down Expand Up @@ -215,6 +215,16 @@ class TestWsBastionContainer(MachineCase):
b.set_val("#login-password-input", KEY_PASSWORD)
b.click("#login-button")
b.wait_visible('#content')
b.logout()

# now test with current OpenSSH format
m.execute(f"yes | ssh-keygen -q -f /root/id_bastion -t rsa -N {KEY_PASSWORD}")
m.execute(f"cp /root/id_bastion.pub {p} && chown admin:admin {p} && chmod 600 {p}")
b.set_val("#login-user-input", "admin")
b.set_val("#server-field", HOST)
b.set_val("#login-password-input", KEY_PASSWORD)
b.click("#login-button")
b.wait_visible('#content')


@unittest.skipUnless(DEFAULT_IMAGE == "fedora-coreos", "no cockpit/ws container on this image")
Expand Down

0 comments on commit 39dcbf9

Please sign in to comment.