python: update ferny, implement cockpit.send-stderr
ferny stopped implementing our send-stderr command, which was only used for us, so we have to implement it ourselves now, from our InteractionResponder in superuser.py. On the plus side, we can now use interaction_client.py to send custom commands. According to the ferny documentation, we should prefer this over sending messages for ourselves. Note: in comparison to the old send-stderr handling inside of ferny, our new `cockpit.send-stderr` command no longer implies that the ferny interaction is complete — we need to send `ferny.end` separately. This isn't actually required for the interaction to exit (since we'll send our "init" message very soon anyway) but is required in some situations to prevent ferny from interpreting our dropping the stderr socket as an unexpected exit.
This commit is contained in:
parent
3ca979d542
commit
61973c8c2b
|
@ -1 +1 @@
|
|||
Subproject commit 39fe96ce1f5928d51e80759f35d70930bbb57ee1
|
||||
Subproject commit 4d4319fa2e5c15010612544e95bc61636935dea6
|
|
@ -28,6 +28,7 @@ import socket
|
|||
import subprocess
|
||||
from typing import Dict, Iterable, List, Tuple, Type
|
||||
|
||||
from cockpit._vendor.ferny import interaction_client
|
||||
from cockpit._vendor.systemd_ctypes import bus, run_async
|
||||
|
||||
from . import polyfills
|
||||
|
@ -150,24 +151,25 @@ async def run(args) -> None:
|
|||
|
||||
|
||||
def try_to_receive_stderr():
|
||||
fds = []
|
||||
try:
|
||||
stderr_socket = socket.fromfd(2, socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
ours, theirs = socket.socketpair()
|
||||
socket.send_fds(stderr_socket, [b'\0ferny\0(["send-stderr"], {})'], [theirs.fileno(), 1])
|
||||
theirs.close()
|
||||
_msg, fds, _flags, _addr = socket.recv_fds(ours, 1, 1)
|
||||
with ours:
|
||||
with theirs:
|
||||
interaction_client.command(2, 'cockpit.send-stderr', fds=[theirs.fileno()])
|
||||
_msg, fds, _flags, _addr = socket.recv_fds(ours, 1, 1)
|
||||
except OSError:
|
||||
return
|
||||
|
||||
if fds:
|
||||
# This is our new stderr. We have to be careful not to leak it.
|
||||
try:
|
||||
stderr_fd, = fds
|
||||
|
||||
try:
|
||||
os.dup2(stderr_fd, 2)
|
||||
finally:
|
||||
os.close(stderr_fd)
|
||||
# We're about to abruptly drop our end of the stderr socketpair that we
|
||||
# share with the ferny agent. ferny would normally treat that as an
|
||||
# unexpected error. Instruct it to do a clean exit, instead.
|
||||
interaction_client.command(2, 'ferny.end')
|
||||
os.dup2(stderr_fd, 2)
|
||||
finally:
|
||||
for fd in fds:
|
||||
os.close(fd)
|
||||
|
||||
|
||||
def setup_logging(*, debug: bool):
|
||||
|
|
|
@ -20,7 +20,8 @@ import contextlib
|
|||
import getpass
|
||||
import logging
|
||||
import os
|
||||
from typing import Dict, List, Optional, Sequence, Union
|
||||
import socket
|
||||
from typing import Dict, List, Optional, Sequence, Tuple, Union
|
||||
|
||||
from cockpit._vendor import ferny
|
||||
from cockpit._vendor.systemd_ctypes import bus
|
||||
|
@ -57,7 +58,15 @@ class SuperuserPeer(ConfiguredPeer):
|
|||
return transport
|
||||
|
||||
|
||||
class AuthorizeResponder(ferny.InteractionResponder):
|
||||
class CockpitResponder(ferny.InteractionResponder):
|
||||
async def do_custom_command(self, command: str, args: Tuple, fds: List[int], stderr: str) -> None:
|
||||
if command == 'cockpit.send-stderr':
|
||||
with socket.socket(fileno=fds[0]) as sock:
|
||||
fds.pop(0)
|
||||
socket.send_fds(sock, [b'\0'], [2])
|
||||
|
||||
|
||||
class AuthorizeResponder(CockpitResponder):
|
||||
def __init__(self, router: Router):
|
||||
self.router = router
|
||||
|
||||
|
@ -66,7 +75,7 @@ class AuthorizeResponder(ferny.InteractionResponder):
|
|||
return await self.router.request_authorization(f'plain1:{hexuser}')
|
||||
|
||||
|
||||
class SuperuserRoutingRule(RoutingRule, ferny.InteractionResponder, bus.Object, interface='cockpit.Superuser'):
|
||||
class SuperuserRoutingRule(RoutingRule, CockpitResponder, bus.Object, interface='cockpit.Superuser'):
|
||||
superuser_configs: Dict[str, Dict[str, object]]
|
||||
pending_prompt: Optional[asyncio.Future]
|
||||
peer: Optional[Peer]
|
||||
|
|
|
@ -7,7 +7,7 @@ pw = os.environ.get('PSEUDO_PASSWORD')
|
|||
if pw:
|
||||
reader, writer = os.pipe()
|
||||
# '-' is the (ignored) argv[0], and 'can haz pw' is the message in argv[1]
|
||||
interaction_client.interact(2, writer, ['-', 'can haz pw?'], {})
|
||||
interaction_client.askpass(2, writer, ['-', 'can haz pw?'], {})
|
||||
os.close(writer)
|
||||
|
||||
response = os.read(reader, 1024).decode('utf-8').strip()
|
||||
|
|
Loading…
Reference in New Issue