Skip to content

Commit

Permalink
beiboot: split flatpak and bastion code paths
Browse files Browse the repository at this point in the history
These have diverged enough that splitting them out from each other would
substantially improve readability.
  • Loading branch information
allisonkarlitskaya committed Jul 10, 2024
1 parent 8b07ac8 commit 4e32845
Showing 1 changed file with 85 additions and 44 deletions.
129 changes: 85 additions & 44 deletions src/cockpit/beiboot.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import os
import re
import shlex
import tempfile
from pathlib import Path
from typing import Dict, Iterable, Optional, Sequence

Expand All @@ -33,7 +34,7 @@
from cockpit.bridge import setup_logging
from cockpit.channel import ChannelRoutingRule
from cockpit.channels import PackagesChannel
from cockpit.jsonutil import JsonObject
from cockpit.jsonutil import JsonObject, get_str
from cockpit.packages import Packages, PackagesLoader, patch_libexecdir
from cockpit.peer import Peer
from cockpit.protocol import CockpitProblem
Expand Down Expand Up @@ -203,6 +204,38 @@ async def do_custom_command(self, command: str, args: tuple, fds: list[int], std
self.router.routing_rules.insert(0, ChannelRoutingRule(self.router, [PackagesChannel]))


def python_interpreter(comment: str) -> tuple[Sequence[str], Sequence[str]]:
return ('python3', '-ic', f'# {comment}'), ()


def via_ssh(cmd: Sequence[str], dest: str, ssh_askpass: Path, *ssh_opts: str) -> tuple[Sequence[str], Sequence[str]]:
host, _, port = dest.rpartition(':')
# catch cases like `host:123` but not cases like `[2001:abcd::1]
if port.isdigit():
destination = ['-p', port, host]
else:
destination = [dest]

return (
'ssh', *ssh_opts, *destination, shlex.join(cmd)
), (
f'SSH_ASKPASS={ssh_askpass!s}',
# DISPLAY=x helps trigger a heuristic in old ssh versions to force them
# to use askpass. Newer ones look at SSH_ASKPASS_REQUIRE.
'DISPLAY=x',
'SSH_ASKPASS_REQUIRE=force',
)


def flatpak_spawn(cmd: Sequence[str], env: Sequence[str]) -> tuple[Sequence[str], Sequence[str]]:
return (
'flatpak-spawn', '--host',
*(f'--env={kv}' for kv in env),
*cmd
), (
)


class SshPeer(Peer):
always: bool

Expand All @@ -212,60 +245,68 @@ def __init__(self, router: Router, destination: str, args: argparse.Namespace):
super().__init__(router)

async def do_connect_transport(self) -> None:
# do we have user/password (Basic auth) from the login page?
auth = await self.router.request_authorization("*")
ssh_opts = []
# Choose your own adventure...
if os.path.exists('/.flatpak-info'):
await self.connect_from_flatpak()
else:
await self.connect_from_bastion_host()

async def connect_from_flatpak(self) -> None:
# We want to run a python interpreter somewhere...
cmd, env = python_interpreter('cockpit-bridge')

# Remote host? Wrap command with SSH
if self.destination != 'localhost':
# we run ssh and thus the helper on the host, always use the xdg-cache helper
cmd, env = via_ssh(cmd, self.destination, ensure_ferny_askpass())

cmd, env = flatpak_spawn(cmd, env)

await self.boot(cmd, env)

async def connect_from_bastion_host(self) -> None:
basic_password = None
username_opts = []

# do we have user/password (Basic auth) from the login page?
auth = await self.router.request_authorization_object("*")
response = get_str(auth, 'response')
user_known_hosts = get_str(auth, 'user-known-hosts', '')

if auth is not None and auth.startswith('Basic '):
user, basic_password = base64.b64decode(auth.split(' ', 1)[1]).decode().split(':', 1)
if response.startswith('Basic '):
user, basic_password = base64.b64decode(response.split(' ', 1)[1]).decode().split(':', 1)
if user: # this can be empty, i.e. auth is just ":"
logger.debug("got username %s and password from Basic auth", user)
ssh_opts = ['-l', user]
username_opts = ['-l', user]

beiboot_helper = BridgeBeibootHelper(self)
# We want to run a python interpreter somewhere...
cmd, env = python_interpreter('cockpit-bridge')

agent = ferny.InteractionAgent([AuthorizeResponder(self.router, basic_password), beiboot_helper])
# outside of the flatpak we expect cockpit-ws and thus an installed helper
askpass = patch_libexecdir('${libexecdir}/cockpit-askpass')
assert isinstance(askpass, str)
ssh_askpass = Path(askpass)
if not ssh_askpass.exists():
logger.error("Could not find cockpit-askpass helper at %r", askpass)

# We want to run a python interpreter somewhere...
cmd: Sequence[str] = ('python3', '-ic', '# cockpit-bridge')
env: Sequence[str] = ()
with tempfile.TemporaryDirectory() as tmpdir:
user_known_hosts_file = Path(tmpdir) / 'user-known-hosts'
user_known_hosts_file.write_text(user_known_hosts)

in_flatpak = os.path.exists('/.flatpak-info')
cmd, env = via_ssh(cmd, self.destination, ssh_askpass, *username_opts,
'-o', f'UserKnownHostsfile={user_known_hosts_file!s}')

# Remote host? Wrap command with SSH
if self.destination != 'localhost':
if in_flatpak:
# we run ssh and thus the helper on the host, always use the xdg-cache helper
ssh_askpass = ensure_ferny_askpass()
else:
# outside of the flatpak we expect cockpit-ws and thus an installed helper
askpass = patch_libexecdir('${libexecdir}/cockpit-askpass')
assert isinstance(askpass, str)
ssh_askpass = Path(askpass)
if not ssh_askpass.exists():
logger.error("Could not find cockpit-askpass helper at %r", askpass)

env = (
f'SSH_ASKPASS={ssh_askpass!s}',
'DISPLAY=x',
'SSH_ASKPASS_REQUIRE=force',
)
host, _, port = self.destination.rpartition(':')
# catch cases like `host:123` but not cases like `[2001:abcd::1]
if port.isdigit():
ssh_opts += ['-p', port, host]
else:
ssh_opts += [self.destination]
await self.boot(cmd, env, basic_password)

cmd = ('ssh', *ssh_opts, shlex.join(cmd))
self.router.write_control(
command='authorize', challenge='x-login-data', cookie='-', login_data={
'user-known-hosts': user_known_hosts_file.read_text()
}
)

# Running in flatpak? Wrap command with flatpak-spawn --host
if in_flatpak:
cmd = ('flatpak-spawn', '--host',
*(f'--env={kv}' for kv in env),
*cmd)
env = ()
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, 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

0 comments on commit 4e32845

Please sign in to comment.