Skip to content

Commit

Permalink
beiboot: Send initial authorize request
Browse files Browse the repository at this point in the history
In order to use cockpit.beiboot as cockpit-ssh replacement from the
bastion host (not Client mode) login page, it needs to consider the given
username and password. cockpit-ssh sends an initial `authorize` message
for that and checks for "Basic" auth. If that fails, it aborts
immediately with `authentication-failed`. Implement the same in
cockpit.beiboot.

Note: The UI does not currently get along with multiple password
attempts. Once we drop cockpit-ssh, we should fix the UI and
cockpit.beiboot to behave like the flatpak, keep the initial SSH
running, and just answer the "try again" prompts.

Cover this in a new `TestLogin.testLoginSshBeiboot`. Once we generally
replace cockpit-ssh with cockpit.beiboot, this will get absorbed by
TestLogin and TestMultiMachine* and can be dropped again.
  • Loading branch information
martinpitt committed Sep 1, 2024
1 parent c039299 commit c0b00c2
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 2 deletions.
19 changes: 17 additions & 2 deletions src/cockpit/beiboot.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,10 +130,25 @@ class AuthorizeResponder(ferny.AskpassHandler):
commands = ('ferny.askpass', 'cockpit.report-exists')
router: Router

def __init__(self, router: Router):
def __init__(self, router: Router, basic_password: Optional[str]):
self.router = router
self.basic_password = basic_password
self.have_basic_password = bool(basic_password)

async def do_askpass(self, messages: str, prompt: str, hint: str) -> Optional[str]:
logger.debug("AuthorizeResponder: prompt %r, messages %r, hint %r", prompt, messages, hint)

if self.have_basic_password and 'password:' in prompt.lower():
# the UI currently expects us to fail on a wrong password, so only send it once
if self.basic_password is not None:
logger.debug("AuthorizeResponder: sending Basic auth password for prompt %r", prompt)
reply = self.basic_password
self.basic_password = None
return reply
else:
logger.debug("AuthorizeResponder: prompt %r, used up our password, failing", prompt)
raise CockpitProblem(problem='authentication-failed', message=messages.strip().splitlines()[-1])

if hint == 'none':
# We have three problems here:
#
Expand Down Expand Up @@ -272,7 +287,7 @@ async def connect_from_bastion_host(self) -> None:

async def boot(self, cmd: Sequence[str], env: Sequence[str], basic_password: 'str | None' = None) -> None:
beiboot_helper = BridgeBeibootHelper(self)
agent = ferny.InteractionAgent([AuthorizeResponder(self.router), beiboot_helper])
agent = ferny.InteractionAgent([AuthorizeResponder(self.router, basic_password), beiboot_helper])

logger.debug("Launching command: cmd=%s env=%s", cmd, env)
transport = await self.spawn(cmd, env, stderr=agent, start_new_session=True)
Expand Down
1 change: 1 addition & 0 deletions test/browser/run-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ if [ "$PLAN" = "main" ]; then
TestLogin.testFailingWebsocketSafari
TestLogin.testFailingWebsocketSafariNoCA
TestLogin.testLogging
TestLogin.testLoginSshBeiboot
TestLogin.testRaw
TestLogin.testServer
TestLogin.testUnsupportedBrowser
Expand Down
59 changes: 59 additions & 0 deletions test/verify/check-static-login
Original file line number Diff line number Diff line change
Expand Up @@ -954,6 +954,65 @@ matchrule = <SUBJECT>^DC=LAN,DC=COCKPIT,CN=alice$
# by IPv6 address and port
check_server("[::1]:22", expect_fp_ack=True)

# check cockpit-beiboot as replacement of cockpit-ssh on the login page
# once that becomes the default, TestMultiMachine* and the other TestLogin* cover this
@testlib.skipImage("needs pybridge", "rhel-8*", "centos-8*")
# enable this once our cockpit/ws container can beiboot
@testlib.skipOstree("client setup does not work with ws container")
def testLoginSshBeiboot(self):
m = self.machine
b = self.browser

# this matches our bots test VMs
my_ip = "172.27.0.15"

m.write("/etc/cockpit/cockpit.conf", """
[Ssh-Login]
Command = /usr/bin/env COCKPIT_DEBUG=all python3 -m cockpit.beiboot
""", append=True)
m.start_cockpit()

def try_login(user, password, server=None):
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")

def check_no_processes():
m.execute(f"while pgrep -af '[s]sh .* {my_ip}' >&2; do sleep 1; done")
m.execute("while pgrep -af '[c]ockpit.beiboot' >&2; do sleep 1; done")

def check_session():
b.wait_visible('#content')
b.enter_page('/system')
b.wait_visible('.system-information')
m.execute(f"pgrep -af '[s]sh -l admin .*{my_ip}'")
m.execute(f"pgrep -af '[c]ockpit.beiboot.*{my_ip}'")
b.logout()
check_no_processes()

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

# wrong password
try_login("admin", "wrong")
b.wait_in_text("#login-error-message", "Authentication failed")
check_no_processes()
# goes back to normal login form
b.wait_visible('#login-user-input')

# colliding usernames; user names in "Connect to:" are *not* supported,
# but pin down the behaviour
try_login("admin", "foobar", server=f"other@{my_ip}")
check_session()


if __name__ == '__main__':
testlib.test_main()

0 comments on commit c0b00c2

Please sign in to comment.