flatpak / xdg-dbus-proxy

GNU Lesser General Public License v2.1
57 stars 21 forks source link

Fallback to other dbus path if filters don't match #47

Closed paralin closed 1 year ago

paralin commented 1 year ago

Related question: https://unix.stackexchange.com/questions/736453

This is the situation:

The goal is to have requests for NetworkManager be routed to the parent dbus, and requests for anything else sent to the container dbus.

I've had a look at xdg-dbus-proxy to implement this but am confused: is it possible to use the proxy to implement this? I thought of listening on a different dbus path with the proxy, and forwarding conditionally (the parent dbus is mounted at /run/parent-dbus):

xdg-dbus-proxy \
  unix:path=/run/parent-dbus/system_bus_socket \
  /run/merged-dbus/system_bus_socket \
  --filter --log \
  --talk=org.freedesktop.NetworkManager.* \
  unix:path=/run/dbus/system_bus_socket \
  /run/merged-dbus/system_bus_socket

Then setting in the environment for nmcli:

export DBUS_SYSTEM_BUS_ADDRESS=unix:path=/run/merged-dbus/system_bus_socket

This almost works: if I run the above command as-is, it doesn't work (nmcli says NetworkManager is not running, I assume the requests are being sent to /run/dbus/...). If I change the last argument to xdg-dbus-proxy to something else like /run/other-dbus/system_bus_socket, it works: the proxy forwards the NetworkManager requests to the /run/parent-dbus/..., however, requests that I intended to send to the /run/dbus/... do not get sent there.

This must be because xdg-dbus-proxy overwrites /run/merged-dbus/system_bus_proxy twice instead of "merging" the two together into a single proxy, where anything missing the filters on the first address/path pair is sent to the second one.

It seems like to do what I want, I might have to do some modifications to xdg-dbus-proxy.

Any help / guidance is greatly appreciated. Thanks much.

cc @smcv

smcv commented 1 year ago

Sorry, xdg-dbus-proxy is not designed to do what you are asking for.

The intention is that you use it to filter one or more "real" buses - normally the well-known session bus, the well-known system bus, and the accessibility bus - and for each of those buses, it provides a filtered socket that sandboxed apps can use as though it was the "real" bus. For example, realistically you might tell it to listen on /run/my-session-bus, /run/my-system-bus and /run/my-a11y-bus.

What you are asking for is combining two "real" buses into one proxied bus, so that clients can connect to the proxied bus, send requests, and have those requests go to one or the other of the "real" buses, depending on the request; for example you might have NetworkManager on the host's "real" system bus, but some other service (let's say systemd) on a different "real" system bus inside your Docker container. Is that right?

I don't think that can actually work in general, because D-Bus isn't designed for that. For example, if you send a request like AddMatch or GetNameOwner to org.freedesktop.DBus, where do we send that - the bus with NetworkManager on it, or the bus with systemd on it? The right answer is "it depends".

This is likely to be worst for AddMatch, because some match rules don't specify a bus name at all, which would mean xdg-dbus-proxy would have to... what? Add the same match rule on both buses? If it does that, then whose reply would it send back to the caller? D-Bus isn't designed for one method call to get two replies.

Name ownership is also not going to work in that scenario, because it would be possible that NetworkManager on the host's system bus, and systemd on the container's system bus, could both have unique name :1.23, making it impossible for the client in the container to know which is which.

I think the way to achieve your higher-level goal would be to have a content-aware proxy that provides the service side of the NetworkManager interface inside the container (so to other software inside the container, it pretends to be NetworkManager), and then implements that by turning round and connecting as a client to a socket that comes from outside the container (so to software outside the container, it behaves like a NetworkManager client like nmcli).

If your aim is to have a security boundary, so that the NetworkManager client can talk to the host NetworkManager but nothing else, then you might want to use xdg-dbus-proxy as well, to provide a filtered version of the real host system bus inside the container. Like this:

arrows are: client --> server

HOST                            |  CONTAINER
                                |
dbus-daemon                     | dbus-daemon
/run/dbus/system_bus_socket     | /run/dbus/system_bus_socket <--- clients
  ^        ^                    |          ^
  |        |                    |          |
  |    xdg-dbus-proxy           |       content-aware NM API proxy
  |        ^                    |          |
  |        |                    |          v
  |        \------------------------ /run/xdp-filtered-host-bus
  |                             |
NetworkManager                  |

However, if you want a security boundary, you will also need to audit the NetworkManager API to make sure that clients inside the container can't get arbitrary code execution on the host by reconfiguring NetworkManager (perhaps by telling it to run a script of their choice as a VPN implementation).

smcv commented 1 year ago

I want nmcli in the container to access the NetworkManager on the host system.

If that's all you want, you could rename nmcli to nmcli.real, and replace nmcli with a shell script that does something like this:

#!/bin/sh
set -eu
export DBUS_SYSTEM_BUS_ADDRESS=unix:path=/run/parent-dbus/system_bus_socket
exec /usr/bin/nmcli.real "$@"

You only need all this stuff with application-layer proxying if you want multiple NM clients, potentially in processes that also need to talk to the "other" system bus, to be able to access NM.

paralin commented 1 year ago

@smcv thanks very much for your detailed reply, this is quite helpful. It makes sense now why I haven't found anyone anywhere on the internet who has done something like this (bridging two bus)

I'll read your replies closely and try to find a solution

For context: in SkiffOS I run Holoiso with SteamOS inside a docker container. It expects to be able to talk to the dbus services relating to kde plasma / logind / other "in the container" dbus services. But it also wants to talk to NetworkManager to select a network.

Similarly I also want to make the network manager control widgets work in kde plasma.

Since the only service I care about bridging in almost all cases is networkmanager, I think the best approach (from a quick read through of the above) is what you mentioned: creating a service that pretends to be specifically NetworkManager and relays the RPCs to the parent bus.

Thanks again for your very helpful and detailed reply, I will use it for reference going forward.

igo95862 commented 5 months ago

why I haven't found anyone anywhere on the internet who has done something like this (bridging two bus)

I am also kind of interested in this (D-Bus bridge) for my sandbox project.

I might look in to creating such proxy one day.

paralin commented 5 months ago

@igo95862 if you do please post on the SkiffOS issue so that we can collaborate. I also encountered this again recently and am thinking about a dbus bridge for containers again.