linux容器安全机制—seccomp/capabilities

465 阅读2分钟

linux seccomp

linux seccomp参考链接

https://blog.csdn.net/qq_44846324/article/details/121731640

http://kuring.me/post/linux-seccomp/

seccomp是secure computing mode的缩写,是Linux内核中的一个安全计算工具,机制用于限制应用程序可以使用的系统调用,增加系统的安全性。

/proc/${pid}/status文件中的Seccomp字段可以看到进程的Seccomp

  • 返回值0表示没有使用seccomp
  • 返回值2表示使用了seccomp并处于SECCOMP_MODE_FILTER模式

prctl

seccomp有三种工作模式:seccomp-disabled,seccomp-strict,seccomp-filter

seccomp-filter模式允许进程为传入的系统调用指定一个过滤器。

Linux内核提供了两个系统调用,prctl()和seccomp()来设置seccomp过滤模式。

但是它们只能用于更改调用线程/进程的seccomp过滤器模式,不能设置其他进程的seccomp过滤器模式。

seccomp支持两种模式:SECCOMP_MODE_STRICTSECCOMP_MODE_FILTER

在SECCOMP_MODE_STRICT模式下,进程不能使用read(2)、write(2)、_exit(2)和sigreturn(2)以外的其他系统调用。 在SECCOMP_MODE_FILTER模式下,可以利用Berkeley Packet Filter(BPF)配置哪些系统调用及它们的参数可以被进程使用。

使用prctl来设置程序的seccomp为strict模式,仅允许readwrite_exitsigreturn四个系统调用

当调用seccomp白名单之外的系统调用时,程序会被kill

#include <stdio.h>         
#include <sys/prctl.h>     
#include <linux/seccomp.h> 
#include <unistd.h>       int main() {
  printf("step 1: unrestricted\n");
​
  // Enable filtering
  prctl(PR_SET_SECCOMP, SECCOMP_MODE_STRICT);
  printf("step 2: only 'read', 'write', '_exit' and 'sigreturn' syscalls\n");
​
  // Redirect stderr to stdout
  dup2(1, 2);
  printf("step 3: !! YOU SHOULD NOT SEE ME !!\n");
​
  // Success (well, not so in this case...)
  return 0;
}

输出结果

step 1: unrestricted
step 2: only 'read', 'write', '_exit' and 'sigreturn' syscalls
已杀死

基于BPF的seccomp

基于prctl系统调用的seccomp机制不够灵活,在linux 3.5之后引入了基于BPF的可定制的系统调用过滤功能

需要先安装依赖包:apt install libseccomp-dev

#include <stdio.h>   /* printf */
#include <unistd.h>  /* dup2: just for test */
#include <seccomp.h> /* libseccomp */
​
int main() {
  printf("step 1: unrestricted\n");
​
  // Init the filter
  scmp_filter_ctx ctx;
  ctx = seccomp_init(SCMP_ACT_KILL); // default action: kill
​
  // setup basic whitelist
  seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(rt_sigreturn), 0);
  seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit), 0);
  seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(read), 0);
  seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 0);
​
  // setup our rule
  seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(dup2), 2,
                        SCMP_A0(SCMP_CMP_EQ, 1),
                        SCMP_A1(SCMP_CMP_EQ, 2));
​
  // build and load the filter
  seccomp_load(ctx);
  printf("step 2: only 'write' and dup2(1, 2) syscalls\n");
​
  // Redirect stderr to stdout
  dup2(1, 2);
  printf("step 3: stderr redirected to stdout\n");
​
  // Duplicate stderr to arbitrary fd
  dup2(2, 42);
  printf("step 4: !! YOU SHOULD NOT SEE ME !!\n");
​
  // Success (well, not so in this case...)
  return 0;
}
gcc b.c -o b -l seccomp

输出结果

step 1: unrestricted
step 2: only 'write' and dup2(1, 2) syscalls
step 3: stderr redirected to stdout
错误的系统调用 (核心已转储)

直接手写 BPF 代码过滤 system call稍有不慎就会引入 bug,导致一些 system call 没有被blacklist 掉。开源项目 Kafel 提供了解决方案。

Kafel 规定了一种更便于人理解的 policy file,并且提供了编译器,能把 policy file 编译成 BPF 代码

但是kafel生成的bpf代码是BPF_STMTBPF_JUMP这种类型的

docker中的应用

通过如下方式可以查看docker是否启用seccomp

$ sudo docker info --format "{{ .SecurityOptions }}"
[name=apparmor name=seccomp,profile=builtin]

关于docker的seccomp配置文件信息可以看官方文档https://docs.docker.com/engine/security/seccomp/

docker run --rm \
             -it \
             --security-opt seccomp=/path/to/seccomp/profile.json \
             hello-world

k8s中的应用seccomp

https://zhuanlan.zhihu.com/p/578250080

eBPF-seccomp的缺点https://zhuanlan.zhihu.com/p/580777000

linux capability

参考链接

https://www.xiexianbin.cn/linux/basic/linux-capabilities/index.html

http://kuring.me/post/capability/?highlight=capabilities

传统的unix权限模型将进程分为root用户进程(有效用户id为0)和普通用户进程。普通用户需要root权限的某些功能,通常通过setuid系统调用实现。但普通用户并不需要root的所有权限,可能仅仅需要修改系统时间的权限而已。这种粗放的权限管理方式势必会带来一定的安全隐患。

capability用于分割root用户的权限,将root的权限分割为不同的能力,每一种能力代表一定的特权操作。

例如CAP_SYS_MODULE用于表示用户加载内核模块的特权操作。根据进程具有的能力来进行特权操作的访问控制。

Capabilites作为线程的属性存在,虽然linux下又对进程和线程不怎么区分

这样一来,权限检查的过程就变成了:在执行特权操作时,如果线程的有效身份不是 root,就去检查其是否具有该特权操作所对应的 capabilities,并以此为依据,决定是否可以执行特权操作。

capabilities类别

Capability 名称说明
CAP_AUDIT_CONTROL启用和禁用内核审计;改变审计过滤规则;检索审计状态和过滤规则
CAP_AUDIT_READ允许通过 multicast netlink 套接字读取审计日志
CAP_AUDIT_WRITE将记录写入内核审计日志
CAP_BLOCK_SUSPEND使用可以阻止系统挂起的特性
CAP_CHOWN修改文件所有者的权限
CAP_DAC_OVERRIDE忽略文件的 DAC 访问限制
CAP_DAC_READ_SEARCH忽略文件读及目录搜索的 DAC 访问限制
CAP_FOWNER忽略文件属主 ID 必须和进程用户 ID 相匹配的限制
CAP_FSETID允许设置文件的 setuid 位
CAP_IPC_LOCK允许锁定共享内存片段
CAP_IPC_OWNER忽略 IPC 所有权检查
CAP_KILL允许对不属于自己的进程发送信号
CAP_LEASE允许修改文件锁的 FL_LEASE 标志
CAP_LINUX_IMMUTABLE允许修改文件的 IMMUTABLE 和 APPEND 属性标志
CAP_MAC_ADMIN允许 MAC 配置或状态更改
CAP_MAC_OVERRIDE覆盖 MAC(Mandatory Access Control)
CAP_MKNOD允许使用 mknod() 系统调用
CAP_NET_ADMIN允许执行网络管理任务
CAP_NET_BIND_SERVICE允许绑定到小于 1024 的端口
CAP_NET_BROADCAST允许网络广播和多播访问
CAP_NET_RAW允许使用原始套接字
CAP_SETGID允许改变进程的 GID
CAP_SETFCAP允许为文件设置任意的 capabilities
CAP_SETPCAP参考capabilities man page
CAP_SETUID允许改变进程的 UID
CAP_SYS_ADMIN允许执行系统管理任务,如加载或卸载文件系统、设置磁盘配额等
CAP_SYS_BOOT允许重新启动系统
CAP_SYS_CHROOT允许使用 chroot() 系统调用
CAP_SYS_MODULE允许插入和删除内核模块
CAP_SYS_NICE允许提升优先级及设置其他进程的优先级
CAP_SYS_PACCT允许执行进程的 BSD 式审计
CAP_SYS_PTRACE允许跟踪任何进程
CAP_SYS_RAWIO允许直接访问 /devport、/dev/mem、/dev/kmem 及原始块设备
CAP_SYS_RESOURCE忽略资源限制
CAP_SYS_TIME允许改变系统时钟
CAP_SYS_TTY_CONFIG允许配置 TTY 设备
CAP_SYSLOG允许使用 syslog() 系统调用
CAP_WAKE_ALARM允许触发一些能唤醒系统的东西(比如 CLOCK_BOOTTIME_ALARM 计时器)

程序和文件的capabilities

  • 每个进程拥有以下几组Capabilities set

    • Effective:cap_effective: 进程当前可用的能力集
    • Inheritable:cap_inheritable: 进程可以传递给子进程的能力集
    • Permitted:cap_permitted: 进程可拥有的最大能力集
    • Ambient:cap_ambient: Linux 4.3后引入的能力集
    • Bounding:cap_bounding: 用于进一步限制能力的获取
  • 对于可执行程序文件的Capabilities set

    • Permitted
    • Inheritable
    • Effective:Effective 只是一个 bit。如果设置为开启,那么在执行 execve 函数后,Permitted 集合中新增的 capabilities 会自动出现在进程的 Effective 集合中。

capabilities命令使用示例

apt install libcap2-bin -y

相关命令:

  • getcap:获取指定文件的 Capabilities
  • setcap:给文件设置 Capabilities
  • getpcaps用于获取进程所具有的能力。
aha@ubuntu20:~/桌面$ sudo getcap /usr/bin/chown
​
# 将chown命令授权给普通用户也具备更改文件owner的能力
# 其中eip分别代表cap_effective(e) cap_inheritable(i) cap_permitted(p)
aha@ubuntu20:~/桌面$ sudo setcap cap_chown=eip /usr/bin/chown
aha@ubuntu20:~/桌面$ sudo getcap /usr/bin/chown
/usr/bin/chown = cap_chown+eip
​
# 使用root创建测试文件
aha@ubuntu20:~/桌面$ sudo touch /tmp/a.txt
aha@ubuntu20:~/桌面$ echo "hello" > /tmp/a.txt
bash: /tmp/a.txt: 权限不够
​
# 普通用户也可以修改root用户创建文件的owner了(正常情况下chown需要sudo一下)
aha@ubuntu20:~/桌面$ chown aha:aha /tmp/a.txt
aha@ubuntu20:~/桌面$ echo "hello" > /tmp/a.txt
aha@ubuntu20:~/桌面$ cat /tmp/a.txt 
hello
​
# 清除chown的能力
aha@ubuntu20:~/桌面$ sudo setcap -r /usr/bin/chown
aha@ubuntu20:~/桌面$ sudo getcap /usr/bin/chown

可以通过/proc/${pid}/status文件中的CapInh CapPrm CapEff CapBnd CapAmb来表示,每个字段为8个字节即64bit,每个比特表示一种能力,这几个字段存放在进程的内核数据结构task_struct中,由此可见capability的最小单位为线程,而不是进程。

# 查看进程的Cap
$ cat /proc/$$/status | grep Cap
CapInh: 0000000000000000
CapPrm: 0000000000000000
CapEff: 0000000000000000
CapBnd: 000001ffffffffff
CapAmb: 0000000000000000

使用 capsh 解码

$ capsh --decode=000001ffffffffff
WARNING: libcap needs an update (cap=40 should have a name).
0x000001ffffffffff=cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_linux_immutable,cap_net_bind_service,cap_net_broadcast,cap_net_admin,cap_net_raw,cap_ipc_lock,cap_ipc_owner,cap_sys_module,cap_sys_rawio,cap_sys_chroot,cap_sys_ptrace,cap_sys_pacct,cap_sys_admin,cap_sys_boot,cap_sys_nice,cap_sys_resource,cap_sys_time,cap_sys_tty_config,cap_mknod,cap_lease,cap_audit_write,cap_audit_control,cap_setfcap,cap_mac_override,cap_mac_admin,cap_syslog,cap_wake_alarm,cap_block_suspend,cap_audit_read,38,39,40

代码示例:设置进程capabilities

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
​
#undef _POSIX_SOURCE
#include <sys/capability.h>
​
extern int errno;
​
void whoami(void){
    printf("uid=%i  euid=%i  gid=%i\n", getuid(), geteuid(), getgid());
}
​
void listCaps(){
    cap_t caps = cap_get_proc();
    ssize_t y = 0;
    printf("The process %d was give capabilities %s\n",(int) getpid(), cap_to_text(caps, &y));
    fflush(0);
    cap_free(caps);
}
​
int main(int argc, char **argv){
    whoami();
    //int stat = setuid(geteuid());
    pid_t parentPid = getpid();
    printf("parentPid: %d\n", parentPid);
    if(!parentPid){
        return 1;
    }
​
    cap_t caps = cap_init();
    // 给进程增加5种能力
    cap_value_t capList[5] ={ CAP_NET_RAW, CAP_NET_BIND_SERVICE , CAP_SETUID, CAP_SETGID,CAP_SETPCAP } ;
    unsigned num_caps = 5;
    cap_set_flag(caps, CAP_EFFECTIVE, num_caps, capList, CAP_SET);
    cap_set_flag(caps, CAP_INHERITABLE, num_caps, capList, CAP_SET);
    cap_set_flag(caps, CAP_PERMITTED, num_caps, capList, CAP_SET);
​
    if (cap_set_proc(caps)) {
        perror("capset()");
        return EXIT_FAILURE;
    }
    listCaps();
​
    // 将进程的能力清除
    printf("dropping caps\n");
    cap_clear(caps);  // resetting caps storage
    if (cap_set_proc(caps)) {
        perror("capset()");
        return EXIT_FAILURE;
    }
    listCaps();
​
    cap_free(caps);
    return 0;
}
$ gcc a.c -o a -lcap
​
$ sudo ./a  # 普通用户不能给进程设置能力
uid=0  euid=0  gid=0
parentPid: 30322
The process 30322 was give capabilities = cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw+eip
dropping caps
The process 30322 was give capabilities =

代码示例:查看进程capabilities

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>#undef _POSIX_SOURCE
#include <sys/capability.h>int main(){
    struct __user_cap_header_struct cap_header_data;
    cap_user_header_t cap_header = &cap_header_data;
​
    struct __user_cap_data_struct cap_data_data;
    cap_user_data_t cap_data = &cap_data_data;
​
    cap_header->pid = getpid();
    cap_header->version = _LINUX_CAPABILITY_VERSION_1;
​
    if (capget(cap_header, cap_data) < 0) {
        perror("Failed capget");
        exit(1);
    }
    printf("Cap data 0x%x, 0x%x, 0x%x\n", cap_data->effective,cap_data->permitted, cap_data->inheritable);
    return 0;
}
$ ./d   # 普通用户默认情况下没有任何能力
Cap data 0x0, 0x0, 0x0
​
$ sudo ./d  # root用户默认拥有所有的能力
Cap data 0xffffffff, 0xffffffff, 0x0

docker中使用capabilities

https://docs.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities

$ docker run  --help
​
Usage:  docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
​
Run a command in a new container
​
Options:
...
      --cap-add list                   Add Linux capabilities
      --cap-drop list                  Drop Linux capabilities
...
      --privileged                     Give extended privileges to this container
...

列出的Capabilities分两类

  • 一是 Docker 默认给容器添加的,我们可以通过 --cap-drop 去除其中一个或者多个
  • 二是 Docker 默认删除的,我们可以通过 --cap-add 添加其中一个或者多个

可以使用--privileged赋予容器所有的capabilities

特权提升cap_sys_nice示例

容器缺省不支持cap_sys_nice,所以无法改变nice值

# docker exec -it test1 /bin/sh
/ # renice -n -9 1
renice: setpriority: Permission denied

通过--cap-add给容器增加cap_sys_nice特权集

# docker run  --name test2 -td --cap-add=cap_sys_nice  busybox  /bin/httpd -f
# docker exec -it  test2  /bin/sh
/ # renice -n -9 -p 1
/ #
在宿主机上查看nice值,发现已经修改为-9,test1的nice值还是0
#  ps -eo "%p %c %n"  | grep httpd
 16371 httpd             0
 21056 httpd            -9

kubernetes使用capabilities

Kubernetes 配置 Capabilities 通过 spec.containers.sercurityContext.capabilities 配置 adddrop 配置

apiVersion: v1
kind: Pod
metadata:
  name: p-1
spec:
  containers:
  - name: p-1
    image: busybox
    args:
    - sleep
    - "3600"
    securityContext:
      capabilities:
        add:
        - NET_ADMIN
        drop:
        - KILL