DBus Policy that Allows Group to Access System Service

I wrote a dbus service and have it listening on the system bus, under the bus name “org.jfhbrook.plusdeck” and the path “/”. That seems to be working fine. I have a corresponding dbus client that I’d like to use to interact with that system bus service, either if I’m the root user (called with sudo) or if I’m in a particular group (in this case, the “plusdeck” group).

I currently have this policy file, based on the dbus-daemon docs and cribbing from whatever examples I could find:

<?xml version="1.0" encoding="UTF-8"?> <!-- -*- XML -*- -->

<!DOCTYPE busconfig PUBLIC
          "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
          "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">

<busconfig>
  <!-- Root user can own the plusdeck service -->
  <policy user="root">
    <allow own="org.jfhbrook.plusdeck"/>
    <allow send_destination="org.jfhbrook.plusdeck"/>
  </policy>

  <!-- Allow access for the "plusdeck" group -->
  <policy group="plusdeck">
    <allow send_destination="org.jfhbrook.plusdeck"/>
  </policy>
</busconfig>

This works when I use sudo. However, when I use the same client with my user, which is a member of the plusdeck group, I get an error:

ERROR:plusdeck.dbus.client:org.freedesktop.DBus.Error.AccessDenied: Access to org.jfhbrook.plusdeck.Eject() not permitted.

Note that this is a different error than I’d get if I didn’t have access to the bus - that would be ERROR:plusdeck.dbus.client:org.freedesktop.DBus.Error.AccessDenied: Sender is not authorized to send message. It seems I’m authorized to send messages, but not to call that method.

I’ve tried combinations of everything I can think of, including setting send_member="*" in the allow tag, and I’m at my wit’s end.

In many ways, this is a general Linux question. However, I post here because I’m using Fedora 41, and I know that dbus configuration can have some distribution-specific issues - for instance, for all I know this is an SELinux issue.

Make sure to apply the changes, i.e. restart the related services, relogin/reboot.
If the issue persists, verify your group membership, check for SELinux denials, try switching SELinux to permissive mode:

groups
journalctl --no-pager -b _AUDIT_TYPE_NAME=AVC
sudo setenforce 0
1 Like

I’ve done some more investigation, and I believe the issue is with systemd and polkit.

First, if I introspect my interface, I can see the methods are annotated with “org.freedesktop.systemd1.Privileged”:

  <method name="Eject">
   <annotation name="org.freedesktop.systemd1.Privileged" value="true"/>
  </method>

I have not found clear documentation on this front - systemd’s docs in particular here are… messy. But it appears that the intent is for systemd to delegate to polkit. Evidence for this is a similarly frustrated user on GitHub and some documentation for the C api that seemed intended to help.

To that end, I have a polkit policy that looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE policyconfig PUBLIC "-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN" "http://www.freedesktop.org/standards/PolicyKit/1.0/policyconfig.dtd">
<policyconfig>
  <vendor>plusdeck</vendor>
  <vendor_url>https://github.com/jfhbrook/plusdeck</vendor_url>
  <action id="org.jfhbrook.plusdeck.Eject">
    <message>Polkit no allow eject tho</message>
    <defaults>
      <allow_any>yes</allow_any>
      <allow_inactive>yes</allow_inactive>
      <allow_active>yes</allow_active>
    </defaults>
  </action>
</policyconfig>

I have also attempted writing a JavaScript rule:

polkit.addRule(function(action, subject) {
  if ((action.id == "org.jfhbrook.plusdeck.Eject" ||
     action.id == "org.jfhbrook.plusdeck.FastForwardA" ||
     action.id == "org.jfhbrook.plusdeck.Pause" ||
     action.id == "org.jfhbrook.plusdeck.PlayA" ||
     action.id == "org.jfhbrook.plusdeck.PlayB" ||
     action.id == "org.jfhbrook.plusdeck.Stop" ||
     action.id == "org.jfhbrook.plusdeck.WaitFor") &&
    subject.isInGroup("plusdeck")) {
        return polkit.Result.YES;
  }

  return polkit.Result.NOT_HANDLED;
});

However, neither of these files seems to have any effect whatsoever, even accounting for reloads and reboots. I also do not see any logs involving polkit.

I guess you’re using the sd-bus API? I’ve never used it, but it appears that you need to use the SD_BUS_VTABLE_UNPRIVILEGED flag with e.g. SD_BUS_METHOD().

1 Like

Yes, I’m using python bindings for sdbus. This explains a lot! I think you’ve hit pay dirt, but I’ll have to try this out tomorrow to be sure.

Props to @chrisawi for hitting at what seems to be the solution!! I haven’t completely tested it - unrelated issues with my release pipeline - but the API I’m using bears this out.

As the C API docs indicate, the sdbus API treats methods on the system bus as “privileged” by default, and requires that unprivileged methods are explicitly marked as such in the service definition code.

My service is written in Python with python-sdbus, and indeed, it does support this API.

So in my code, I had something like:

    @dbus_method_async("")
    async def eject(self: Self) -> None:
        """
        Eject the tape.
        """

        self.client.eject()

and I needed to change it to:

    @dbus_method_async("", flags=sdbus.DbusUnprivilegedFlag)
    async def eject(self: Self) -> None:
        """
        Eject the tape.
        """

        self.client.eject()

Like I said, I haven’t been able to fully test this end-to-end. But I’m really confident that this is the answer, and so am posting this as the solution.

Thanks!