为什么我的程序使用 d-bus 访问 systemd 来启动和停止服务在多用户嵌入式系统上会出现权限错误

为什么我的程序使用 d-bus 访问 systemd 来启动和停止服务在多用户嵌入式系统上会出现权限错误

我使用的嵌入式系统有多个用户,例如“root”和“user1”。我正在运行一个以“user1”身份登录的 C++ 二进制文件,它无法启动/停止服务并出现权限错误。在 root 中运行时相同的二进制文件工作正常。这是代码:

#include <iostream>
#include <systemd/sd-bus.h>


static void SDCallMethodSS(
  sd_bus* bus,
  const std::string& name,
  const std::string& method)
{
  sd_bus_error err = SD_BUS_ERROR_NULL;
  sd_bus_message* msg = nullptr;
  int r;

  r = sd_bus_call_method(bus,
      "org.freedesktop.systemd1",
      "/org/freedesktop/systemd1",
      "org.freedesktop.systemd1.Manager",
      method.c_str(),
      &err,
      &msg,
      "ss",
      name.c_str(),  "replace" );

  if (r < 0)
  {
    std::string err_str("Could not send " + method +
                        " command to systemd for service: " + name +
                        ". Error: " + err.message );

    sd_bus_error_free(&err);
    sd_bus_message_unref(msg);
    throw std::runtime_error(err_str);
  }

  char* response;
  r = sd_bus_message_read(msg, "o", &response);
  if (r < 0)
  {
          std::cerr<< "Failed to parse response message: " << strerror(-r) << std::endl;;
  }

  sd_bus_error_free(&err);
  sd_bus_message_unref(msg);
}

int main() {
  int r;
  sd_bus *bus = NULL;

  r = sd_bus_open_system(&bus);
  if (r < 0) {
          std::cerr<< "Failed to connect to system bus: " << strerror(-r) << std::endl;
    return -1;
  }

  try{
    SDCallMethodSS(bus, std::string("foo-daemon.service"), std::string("StopUnit"));
  } catch (std::exception& e) {
    std::cout << "Exception in SDCallMethodSS(): " << e.what() << std::endl;
    return -2;
  }
}

Foo-daemon 是一个虚拟程序:

#include <unistd.h>

int main()
{
  while(1){
    sleep(1);
  }

}

服务文件很简单:

[Unit]
Description=Foo

[Service]
ExecStart=/usr/local/bin/foo-daemon

[Install]
WantedBy=multi-user.target

服务文件加载到 /etc/systemd/system 'user1' 的输出为:

Exception in SDCallMethodSS(): Could not send StopUnit command to systemd for service: foo-daemon.service. Error: Permission denied

如何解决“user1”的权限问题

答案1

你的问题从这里开始:

r = sd_bus_open_system(&bus);

这将打开系统的总线。这会导致与你运行时相同的行为

user1@machine:~$ systemctl ...

无论您使用 sd-bus API 还是 systemctl,systemd都将以相同的方式对您进行身份验证。 user1无权启动/停止设备。


替代方案 1:--用户总线

一种替代方法是使用:

r = sd_bus_open_user(&bus);

这与 using 类似systemctl --user ...,但您的进程将具有与 using 相同的权限,user1并且仅在user1的总线上运行。


替代方案 2:polkit 规则(用户权限)

我们需要配置systemd以允许user1启动/停止系统总线上的单元。这是通过polkit

如果您使用的是基于 Debian 的系统 (polkit < 106),请通过创建文件来创建规则*.pkla

/etc/polkit-1/localauthority/50-local.d/service-auth.pkla
---
[Allow user1 to start/stop/restart services]
Identity=unix-user:user1
Action=org.freedesktop.systemd1.manage-units
ResultActive=yes

替代方案 3:polkit 规则(特定于服务的权限)

如果您使用的是基于 Redhat/Arch 的系统 (polkit >=106),那么您将拥有 javascript 类型的语法,它可以让您更加具体。在这种情况下,您可以允许任何用户管理foo-daemon.service文件*.rules

/etc/polkit-1/rules.d/foo-daemon.rules
---
polkit.addRule(function(action, subject) {
    if (action.id == "org.freedesktop.systemd1.manage-units") {
        if (action.lookup("unit") == "foo-daemon.service") {
            var verb = action.lookup("verb");
            if (verb == "start" || verb == "stop" || verb == "restart") {
                return polkit.Result.YES;
            }
        }
    }
});

替代方案 4:polkit 规则(组权限)

我喜欢使用的解决方案是授予组成员管理单元的权限。那么只要您的用户是该组的成员,他们就能够systemctl {start,stop,restart} ...sd_bus_open_system(...)

这里有一个关于如何执行此操作的答案:

systemd 以组中的非特权用户身份启动

相关内容