Skip to content

Commit

Permalink
beiboot: support for temporary UserKnownHostsFile
Browse files Browse the repository at this point in the history
In the bastion host case, create a temporary UserKnownHostsFile for ssh
and populate it with data from the authorization (if provided).  On
successful login, send the updated file back to the user via the
long-disused `x-login-data` mechanism.  That'll get it added to the JSON
blob that is sent as the reply to the `GET /login` request.
  • Loading branch information
allisonkarlitskaya committed Aug 20, 2024
1 parent 4e32845 commit 3a6bab8
Showing 1 changed file with 15 additions and 11 deletions.
26 changes: 15 additions & 11 deletions src/cockpit/beiboot.py
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,7 @@ def flatpak_spawn(cmd: Sequence[str], env: Sequence[str]) -> tuple[Sequence[str]


class SshPeer(Peer):
user_known_hosts_file: 'Path | None' = None
always: bool

def __init__(self, router: Router, destination: str, args: argparse.Namespace):
Expand Down Expand Up @@ -266,15 +267,17 @@ async def connect_from_flatpak(self) -> None:

async def connect_from_bastion_host(self) -> None:
basic_password = None
known_hosts = ''
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 response.startswith('Basic '):
user, basic_password = base64.b64decode(response.split(' ', 1)[1]).decode().split(':', 1)
decoded = base64.b64decode(response[6:]).decode()
user_password, _, known_hosts = decoded.partition('\0')
user, _, basic_password = user_password.partition(':')
if user: # this can be empty, i.e. auth is just ":"
logger.debug("got username %s and password from Basic auth", user)
username_opts = ['-l', user]
Expand All @@ -290,20 +293,14 @@ async def connect_from_bastion_host(self) -> None:
logger.error("Could not find cockpit-askpass helper at %r", askpass)

with tempfile.TemporaryDirectory() as tmpdir:
user_known_hosts_file = Path(tmpdir) / 'user-known-hosts'
user_known_hosts_file.write_text(user_known_hosts)
self.user_known_hosts_file = Path(tmpdir) / 'user-known-hosts'
self.user_known_hosts_file.write_text(known_hosts)

cmd, env = via_ssh(cmd, self.destination, ssh_askpass, *username_opts,
'-o', f'UserKnownHostsfile={user_known_hosts_file!s}')
'-o', f'UserKnownHostsfile={self.user_known_hosts_file!s}')

await self.boot(cmd, env, basic_password)

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

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])
Expand Down Expand Up @@ -375,6 +372,13 @@ async def run(args) -> None:
try:
message = dict(await bridge.ssh_peer.start())

if bridge.ssh_peer.user_known_hosts_file:
bridge.write_control(
command='authorize', challenge='x-login-data', cookie='-', login_data={
'known-hosts': bridge.ssh_peer.user_known_hosts_file.read_text()
}
)

# See comment in do_init() above: we tell cockpit-ws that we support
# this and then handle it ourselves when we get the init message.
capabilities = message.setdefault('capabilities', {})
Expand Down

0 comments on commit 3a6bab8

Please sign in to comment.