Skip to content

Commit

Permalink
pybridge: Handle ssh host key prompts in beiboot
Browse files Browse the repository at this point in the history
Stop treating host key prompts as generic conversation messages. We want
the UI to handle them properly, with some verbiage/buttons and the
recipe for validating host keys, instead of letting the user type "yes".
The login page recognizes these through the presence of the `host-key`
authorize field (and irritatingly, an extra `default` field with the
actual value).

We can't use ferny's builtin `do_hostkey()` responder for this, as that
requires `ferny.Session(handle_host_key=True)`, and that API is not
flexible enough to handle our ssh command modifications and the extra
beiboot_helper handler. This needs some bigger redesign.

So just recognize and parse SSH's host key prompts, and rely on our
integration tests to spot breakage in future distro releases.

This enables the login page's host key localstorage mechanism, so adjust
TestLogin.testLoginSshBeiboot to only expect the host key on the first
login attempt.
  • Loading branch information
martinpitt committed Oct 6, 2023
1 parent 0809eef commit 4457911
Show file tree
Hide file tree
Showing 4 changed files with 23 additions and 12 deletions.
6 changes: 3 additions & 3 deletions containers/flatpak/test/test-browser-login-ssh.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ async function test() {
document.getElementById("server-field").value = "%HOST%";
ph_mouse("#login-button", "click");

// unknown host key
await assert_conversation("authenticity of host");
document.getElementById("conversation-input").value = "yes";
// accept unknown host key
await ph_wait_present("#hostkey-message-1");
await ph_wait_in_text("#hostkey-message-1", "%HOST%");
ph_mouse("#login-button", "click");

await ph_wait_present("#conversation-prompt");
Expand Down
12 changes: 11 additions & 1 deletion src/cockpit/beiboot.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import importlib.resources
import logging
import os
import re
import shlex
from pathlib import Path
from typing import Dict, Iterable, Optional, Sequence
Expand Down Expand Up @@ -165,12 +166,21 @@ async def do_askpass(self, messages: str, prompt: str, hint: str) -> Optional[st
# Let's avoid all of that by just showing nothing.
return None

# is this a host key prompt?
fp_match = re.search(r'\n(\w+) key fingerprint is ([^.]+)\.', prompt)
args = {}
if fp_match:
args['host-key'] = f'{fp_match.group(2)} {fp_match.group(1)}'
args['default'] = fp_match.group(2)

challenge = 'X-Conversation - ' + base64.b64encode(prompt.encode()).decode()
response = await self.router.request_authorization(challenge,
timeout=None,
messages=messages,
prompt=prompt,
hint=hint,
echo=False)
echo=False,
**args)

b64 = response.removeprefix('X-Conversation -').strip()
response = base64.b64decode(b64.encode()).decode()
Expand Down
5 changes: 3 additions & 2 deletions test/verify/check-client
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,9 @@ Command = /usr/bin/env python3 -m cockpit.beiboot
b.wait_not_visible("#recent-hosts-list")
b.set_val("#server-field", "10.111.113.2")
b.click("#login-button")
b.wait_in_text("#conversation-group", "authenticity of host '10.111.113.2")
b.set_val("#conversation-input", "yes")
# accept unknown host key
b.wait_in_text("#hostkey-message-1", "10.111.113.2")
b.wait_in_text("#hostkey-fingerprint", "SHA256:")
b.click("#login-button")
b.wait_text("#conversation-prompt", "[email protected]'s password: ")
b.set_val("#conversation-input", "foobar")
Expand Down
12 changes: 6 additions & 6 deletions test/verify/check-static-login
Original file line number Diff line number Diff line change
Expand Up @@ -973,17 +973,17 @@ Command = /usr/bin/env python3 -m cockpit.beiboot
""", append=True)
m.start_cockpit()

def try_login(user, password, server=None):
def try_login(user, password, server=None, expect_hostkey=False):
b.open("/")
b.set_val('#login-user-input', user)
b.set_val('#login-password-input', password)
b.click("#show-other-login-options")
b.set_val("#server-field", server or my_ip)
b.click("#login-button")
# ack unknown host key; FIXME: this should be a proper authorize message, not a prompt
b.wait_in_text("#conversation-prompt", "authenticity of host")
b.set_val("#conversation-input", "yes")
b.click("#login-button")
if expect_hostkey:
b.wait_in_text("#hostkey-message-1", my_ip)
b.wait_in_text("#hostkey-fingerprint", "SHA256:")
b.click("#login-button")

def check_no_processes():
m.execute(f"while pgrep -af '[s]sh .* {my_ip}' >&2; do sleep 1; done")
Expand All @@ -999,7 +999,7 @@ Command = /usr/bin/env python3 -m cockpit.beiboot
check_no_processes()

# successful login through SSH
try_login("admin", "foobar")
try_login("admin", "foobar", expect_hostkey=True)
check_session()

# wrong password
Expand Down

0 comments on commit 4457911

Please sign in to comment.