You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何在无需sudo/su/root权限的情况下为用户提供systemctl的部分访问权限?

如何在无需sudo/su/root权限的情况下为用户提供systemctl的部分访问权限?

嗨,看了你的问题,我来给你梳理几个靠谱的方案,既解决权限需求,又能规避setuid程序带来的安全风险:

一、优先用sudoers配置替代setuid程序(最安全推荐)

自己写setuid程序很容易踩安全坑,而sudo的权限控制系统经过多年打磨,不仅更安全,还能实现精细的权限管控,完全能满足你的需求:

  1. 编辑sudoers文件:一定要用visudo命令编辑(它会自动做语法校验,避免写错导致sudo失效),执行:
    visudo
    
  2. 添加权限规则:根据你的需求,允许特定用户/用户组仅操作指定的3个服务。比如:
    • 允许单个用户gabriel无需输入密码,执行指定服务的start/stop/restart/status操作:
      gabriel ALL=(ALL) NOPASSWD: /bin/systemctl start service1.service, /bin/systemctl stop service1.service, /bin/systemctl restart service1.service, /bin/systemctl status service1.service, /bin/systemctl start service2.service, /bin/systemctl stop service2.service, /bin/systemctl restart service2.service, /bin/systemctl status service2.service, /bin/systemctl start service3.service, /bin/systemctl stop service3.service, /bin/systemctl restart service3.service, /bin/systemctl status service3.service
      
    • 如果是多个用户,可以先创建用户组(比如service-managers),再给组授权:
      %service-managers ALL=(ALL) NOPASSWD: /bin/systemctl * service1.service, /bin/systemctl * service2.service, /bin/systemctl * service3.service
      
      注意:上面的*会匹配所有systemctl子命令,如果不想允许enable/disable这类改变开机自启的操作,建议像第一个例子那样明确列出允许的子命令。
  3. 验证效果:用户只需在执行命令前加sudo即可,比如:
    sudo systemctl restart service1.service
    
    因为配置了NOPASSWD,所以不需要输入密码。

二、如果你一定要重构原setuid程序(不推荐,但满足需求)

反编译后的代码通常可读性差,不如直接重新写一个极简的C程序,实现和原程序一样的逻辑,同时注意安全编程规范:

示例安全代码

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pwd.h>
#include <string.h>

// 允许操作的用户名列表
const char *allowed_users[] = {"gabriel", "other_user", NULL};
// 允许操作的服务列表
const char *allowed_services[] = {"service1.service", "service2.service", "service3.service", NULL};

// 检查用户是否在允许列表内
int is_allowed_user(const char *username) {
    for (int i = 0; allowed_users[i] != NULL; i++) {
        if (strcmp(username, allowed_users[i]) == 0) {
            return 1;
        }
    }
    return 0;
}

// 检查服务是否在允许列表内
int is_allowed_service(const char *service) {
    for (int i = 0; allowed_services[i] != NULL; i++) {
        if (strcmp(service, allowed_services[i]) == 0) {
            return 1;
        }
    }
    return 0;
}

int main(int argc, char *argv[]) {
    // 参数校验:必须传入子命令和服务名,比如 ./my-systemctl restart service1.service
    if (argc < 3) {
        fprintf(stderr, "用法: %s <command> <service>\n", argv[0]);
        return 1;
    }

    // 获取当前登录用户的信息
    uid_t current_uid = getuid();
    struct passwd *user_info = getpwuid(current_uid);
    if (user_info == NULL) {
        perror("获取用户信息失败");
        return 1;
    }

    // 校验用户权限
    if (!is_allowed_user(user_info->pw_name)) {
        fprintf(stderr, "错误:你没有权限执行此操作\n");
        return 1;
    }

    // 校验服务权限
    if (!is_allowed_service(argv[2])) {
        fprintf(stderr, "错误:该服务不允许操作\n");
        return 1;
    }

    // 切换到root权限(因为程序设置了setuid位)
    if (seteuid(0) != 0) {
        perror("切换权限失败");
        return 1;
    }

    // 用execve执行systemctl,避免shell注入风险(绝对不要用system()函数)
    char *systemctl_cmd[] = {"/bin/systemctl", argv[1], argv[2], NULL};
    execve("/bin/systemctl", systemctl_cmd, NULL);

    // 如果execve返回,说明执行失败
    perror("执行systemctl命令失败");
    return 1;
}

编译与权限设置

# 编译程序
gcc -o my-systemctl my-systemctl.c
# 设置所有者为root
chown root:root my-systemctl
# 设置setuid位
chmod u+s my-systemctl

⚠️ 重要提醒:这个程序一定要保持极简,不要添加多余功能,所有输入参数都要严格校验,否则很容易被攻击者利用漏洞获取root权限。

三、关于setuid程序的安全风险

你担心的点非常正确,setuid程序确实有很高的安全隐患:

  • 程序会以root权限运行,一旦存在缓冲区溢出、参数校验不严、使用不安全函数(比如strcpy()system())等漏洞,攻击者就能直接获取root权限
  • 自己编写的代码很难覆盖所有安全场景,相比之下,sudo是经过全球安全社区长期测试和维护的,安全性高很多
  • sudo还自带审计日志,方便追踪用户的操作,而自定义setuid程序需要自己实现日志功能,否则出问题很难排查

备注:内容来源于stack exchange,提问作者Gabriel Grinspan

火山引擎 最新活动