From d220099eaa464d10e5ec81f160f9ac8fc1819ea7 Mon Sep 17 00:00:00 2001 From: Allison Karlitskaya Date: Fri, 13 Oct 2023 18:36:01 +0200 Subject: [PATCH] vendor: update to the latest version of ferny This has some breaking API changes, so make adjustments as necessary. One slightly non-obvious change comes from the removal of InteractionAgent.add_handlers(), which had the potential to be racy in case a message arrived before its handler was registered. We now have to specify all handlers up-front, which means that the block for doing that needs to move up to before the transport is connected. Of course, we can only write the stage1 bootloader after the transport is online, so we need to keep that in its current location. --- src/cockpit/beiboot.py | 5 ++--- src/cockpit/polkit.py | 6 +++--- src/cockpit/remote.py | 8 ++++---- src/cockpit/superuser.py | 29 ++++++++++++++++++----------- test/pytest/test_beiboot.py | 2 +- vendor/ferny | 2 +- 6 files changed, 29 insertions(+), 23 deletions(-) diff --git a/src/cockpit/beiboot.py b/src/cockpit/beiboot.py index 3e2583e1903..79f04a6ee16 100644 --- a/src/cockpit/beiboot.py +++ b/src/cockpit/beiboot.py @@ -126,7 +126,7 @@ def shutdown(self) -> None: self.peer.close() -class AuthorizeResponder(ferny.InteractionResponder): +class AuthorizeResponder(ferny.AskpassHandler): commands = ('ferny.askpass', 'cockpit.report-exists') router: Router @@ -184,8 +184,7 @@ def __init__(self, router: Router, destination: str, args: argparse.Namespace): async def do_connect_transport(self) -> None: beiboot_helper = BridgeBeibootHelper(self) - agent = ferny.InteractionAgent(AuthorizeResponder(self.router)) - agent.add_handler(beiboot_helper) + agent = ferny.InteractionAgent([AuthorizeResponder(self.router), beiboot_helper]) # We want to run a python interpreter somewhere... cmd: Sequence[str] = ('python3', '-ic', '# cockpit-bridge') diff --git a/src/cockpit/polkit.py b/src/cockpit/polkit.py index 1e3f85fbb45..b59a6a2c7e4 100644 --- a/src/cockpit/polkit.py +++ b/src/cockpit/polkit.py @@ -22,7 +22,7 @@ import pwd from typing import Dict, List, Sequence, Tuple -from cockpit._vendor.ferny import InteractionResponder +from cockpit._vendor.ferny import AskpassHandler from cockpit._vendor.systemd_ctypes import Variant, bus # that path is valid on at least Debian, Fedora/RHEL, and Arch @@ -43,7 +43,7 @@ # mapping, but that method is not available for Python 3.6 yet. class org_freedesktop_PolicyKit1_AuthenticationAgent(bus.Object): - def __init__(self, responder: InteractionResponder): + def __init__(self, responder: AskpassHandler): super().__init__() self.responder = responder @@ -125,7 +125,7 @@ class PolkitAgent: Use this as a context manager to ensure that the agent gets unregistered again. """ - def __init__(self, responder: InteractionResponder): + def __init__(self, responder: AskpassHandler): self.responder = responder self.agent_slot = None diff --git a/src/cockpit/remote.py b/src/cockpit/remote.py index b1cbaf48d80..c4facc36493 100644 --- a/src/cockpit/remote.py +++ b/src/cockpit/remote.py @@ -30,7 +30,7 @@ logger = logging.getLogger(__name__) -class PasswordResponder(ferny.InteractionResponder): +class PasswordResponder(ferny.AskpassHandler): PASSPHRASE_RE = re.compile(r"Enter passphrase for key '(.*)': ") password: Optional[str] @@ -104,7 +104,7 @@ async def do_connect_transport(self) -> None: logger.debug('connecting to host %s failed: %s', host, exc) raise PeerError('no-host', error='no-host', message=str(exc)) from exc - except ferny.HostKeyError as exc: + except ferny.SshHostKeyError as exc: if responder.hostkeys_seen: # If we saw a hostkey then we can issue a detailed error message # containing the key that would need to be accepted. That will @@ -114,7 +114,7 @@ async def do_connect_transport(self) -> None: else: error_args = {} - if isinstance(exc, ferny.ChangedHostKeyError): + if isinstance(exc, ferny.SshChangedHostKeyError): error = 'invalid-hostkey' elif self.private: error = 'unknown-hostkey' @@ -126,7 +126,7 @@ async def do_connect_transport(self) -> None: type(exc), exc, self.private, responder.hostkeys_seen, error, error_args) raise PeerError(error, error=error, auth_method_results={}, **error_args) from exc - except ferny.AuthenticationError as exc: + except ferny.SshAuthenticationError as exc: logger.debug('authentication to host %s failed: %s', host, exc) results = {method: 'not-provided' for method in exc.methods} diff --git a/src/cockpit/superuser.py b/src/cockpit/superuser.py index a938c128f17..aa48706ed79 100644 --- a/src/cockpit/superuser.py +++ b/src/cockpit/superuser.py @@ -40,9 +40,9 @@ class SuperuserPeer(ConfiguredPeer): - responder: ferny.InteractionResponder + responder: ferny.AskpassHandler - def __init__(self, router: Router, config: BridgeConfig, responder: ferny.InteractionResponder): + def __init__(self, router: Router, config: BridgeConfig, responder: ferny.AskpassHandler): super().__init__(router, config) self.responder = responder @@ -54,7 +54,17 @@ async def do_connect_transport(self) -> None: else: logger.debug('connecting non-polkit superuser peer transport %r', self.args) - agent = ferny.InteractionAgent(self.responder) + responders: 'list[ferny.InteractionHandler]' = [self.responder] + + if '# cockpit-bridge' in self.args: + logger.debug('going to beiboot superuser bridge %r', self.args) + helper = BridgeBeibootHelper(self, ['--privileged']) + responders.append(helper) + stage1 = make_bootloader(helper.steps, gadgets=ferny.BEIBOOT_GADGETS).encode() + else: + stage1 = None + + agent = ferny.InteractionAgent(responders) if 'SUDO_ASKPASS=ferny-askpass' in self.env: tmpdir = context.enter_context(TemporaryDirectory()) @@ -65,11 +75,8 @@ async def do_connect_transport(self) -> None: transport = await self.spawn(self.args, env, stderr=agent, start_new_session=True) - if '# cockpit-bridge' in self.args: - logger.debug('going to beiboot superuser bridge %r', self.args) - helper = BridgeBeibootHelper(self, ['--privileged']) - agent.add_handler(helper) - transport.write(make_bootloader(helper.steps, gadgets=ferny.BEIBOOT_GADGETS).encode()) + if stage1 is not None: + transport.write(stage1) try: await agent.communicate() @@ -77,7 +84,7 @@ async def do_connect_transport(self) -> None: raise PeerError('authentication-failed', message=str(exc)) from exc -class CockpitResponder(ferny.InteractionResponder): +class CockpitResponder(ferny.AskpassHandler): commands = ('ferny.askpass', 'cockpit.send-stderr') async def do_custom_command(self, command: str, args: Tuple, fds: List[int], stderr: str) -> None: @@ -125,7 +132,7 @@ def apply_rule(self, options: JsonObject) -> Optional[Peer]: # superuser requested, but not active? That's an error. raise RoutingError('access-denied') - # ferny.InteractionResponder + # ferny.AskpassHandler async def do_askpass(self, messages: str, prompt: str, hint: str) -> Optional[str]: assert self.pending_prompt is None echo = hint == "confirm" @@ -153,7 +160,7 @@ def peer_done(self): self.current = 'none' self.peer = None - async def go(self, name: str, responder: ferny.InteractionResponder) -> None: + async def go(self, name: str, responder: ferny.AskpassHandler) -> None: if self.current != 'none': raise bus.BusError('cockpit.Superuser.Error', 'Superuser bridge already running') diff --git a/test/pytest/test_beiboot.py b/test/pytest/test_beiboot.py index cec36806c9c..8571c02501a 100644 --- a/test/pytest/test_beiboot.py +++ b/test/pytest/test_beiboot.py @@ -12,7 +12,7 @@ class BeibootPeer(Peer): async def do_connect_transport(self) -> None: helper = BridgeBeibootHelper(self) - agent = ferny.InteractionAgent(helper) + agent = ferny.InteractionAgent([helper]) transport = await self.spawn([sys.executable, '-iq'], env=[], stderr=agent) transport.write(bootloader.make_bootloader(helper.steps, gadgets=ferny.BEIBOOT_GADGETS).encode()) await agent.communicate() diff --git a/vendor/ferny b/vendor/ferny index 7d706d56745..c26d44452e9 160000 --- a/vendor/ferny +++ b/vendor/ferny @@ -1 +1 @@ -Subproject commit 7d706d56745274ce9c46a2540916dced14bcbd5e +Subproject commit c26d44452e9cb82dcafa71f88032bffcd7b9fa41