namespace的6项隔离技术

时间:Aug. 23, 2020 分类:

目录:

linux的隔离系统调用

namespace 系统调用参数 隔离内容 内核版本
Mount CLONE_NEWNS 挂载点 2.4.19
UTS CLONE_NEWUTS 主机名域名 2.6.19
IPC CLONE_NEWIPC 信号量,消息队列和共享内存 2.6.19
PID CLONE_NEWPID 进程编号 2.6.24
Network CLONE_NEWNET 网络设备,网络栈和端口号等 2.6.29
User CLONE_NEWUSER 用户和用户组 3.8

namespace的操作通过clone(),setns(),unshare()和/proc下的文件实现

clone

clone()是Linux系统调用fork()的通用实现方式

int clone(int(*child_func)(void *), void *child_stack, int flags, void *arg);
  • child_func 传入子进程运行的程序主函数
  • child_stack 传入子进程使用的栈空间
  • flags 使用的CLONE_*标志位
  • args 用于用户传入的参数

setns

进程从原来的namespace加入一个已经存在的namespace,docker的exec就是这样操作的

unsetns

进程从原有的namespace隔离,docker并没有使用这个系统调用

proc文件

/proc/[pid]/ns目录

$ ll /proc/$$/ns 
total 0
lrwxrwxrwx 1 mrwhy mrwhy 0 Aug 22 23:33 ipc -> ipc:[4026531839]
lrwxrwxrwx 1 mrwhy mrwhy 0 Aug 22 23:33 mnt -> mnt:[4026531840]
lrwxrwxrwx 1 mrwhy mrwhy 0 Aug 22 23:33 net -> net:[4026531956]
lrwxrwxrwx 1 mrwhy mrwhy 0 Aug 22 23:33 pid -> pid:[4026531836]
lrwxrwxrwx 1 mrwhy mrwhy 0 Aug 22 23:33 user -> user:[4026531837]
lrwxrwxrwx 1 mrwhy mrwhy 0 Aug 22 23:33 uts -> uts:[4026531838]

如果两个进程指向的同一个namespace,证明两个进程在一个namespace,作为软链,如果link文件被打开文件描述符,这个namespace会一直存在,其他的进程也可以挂载到namespace

$ mount --bind /proc/1234/ns/uts ~/uts

fork

fork调用的时候,会创建新的进程,并分配资源,例如存储数据和代码空间,然后原来进程的值复制到新的进程

fork调用调用一次却返回两次,父进程和子进程分别返回一次,通过返回值历来区分,子进程返回的是0,父进程返回的子进程的pid

#include <unistd.h>
#include <stdio.h>
int main(){
    pid_t fpid; 
    fpid=fork();
    if (fpid < 0)printf("error in fork!");
    else if (fpid == 0){
        printf("Child process, pid is %d\n", getpid());
    }
    else{
        printf("Parent process, pid is %d\n", getpid());
    }
    return 0;
}

编译执行一下

$ gcc -Wall fork_example.c
$ ./a.out 
Parent process, pid is 15415
Child process, pid is 15416

UTS

#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <sched.h>

#define STACK_SIZE (1024 * 1024)

static char child_stack[STACK_SIZE];
char* const child_args[] = {
    "/bin/bash",
    NULL
};

int child_main(void* args){
    printf("在子进程中!\n");
    sethostname("newnamespace", 12);
    execv(child_args[0], child_args);
    return 1;
}

int main(){
    printf("程序运行: \n");
    int child_pid = clone(child_main, child_stack + STACK_SIZE, CLONE_NEWUTS | SIGCHLD, NULL);
    waitpid(child_pid, NULL, 0);
    printf("已退出\n");
    return 0;
}

编译执行一下

[root@localhost ~] gcc -Wall uts.c -o uts.o
[root@localhost ~] ./uts.o 
程序运行: 
在子进程中!
[root@newnamespace ~]# exit
已退出
[root@localhost ~]

ipc

通过shell创建message queue

$ ipcmk -Q
Message queue id: 0
$ ipcs -q

------ Message Queues --------
key        msqid      owner      perms      used-bytes   messages    
0x5e9ee8bc 0          root       644        0            0           

进入隔离ipc的namespace

$ ./ipc.o 
程序运行: 
在子进程中!
[root@newnamespace ~]# ipcs -q

------ Message Queues --------
key        msqid      owner      perms      used-bytes   messages    

$ exit
已退出

可以看到是没有之前声明的queue

PID

每个PID的namespace会有一个单独的PID计数器

内核中维护了PID的namespace树状结构,父namespace可以看到子namespace中的进程,并且可以通过信号对子namespace进程产生影响,而子namespace看不到父namespace进程

  • PIDnamespace中第一个进程pid为1,和linux系统的init一样,当子进程成为孤儿进程,PID中的namespace的init进程维护
  • 一个PIDnamespace进程,不会影响其他namespace的节点的进程
  • PIDnamespace中重新挂载/proc,只会显示同属一个PIDnamespace中的其他进程
#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <sched.h>

#define STACK_SIZE (1024 * 1024)

static char child_stack[STACK_SIZE];
char* const child_args[] = {
    "/bin/bash",
    NULL
};

int child_main(void* args){
    printf("在子进程中!\n");
    sethostname("newnamespace", 12);
    execv(child_args[0], child_args);
    return 1;
}

int main(){
    printf("程序运行: \n");
    int child_pid = clone(child_main, child_stack + STACK_SIZE, CLONE_NEWPID | CLONE_NEWIPC | CLONE_NEWUTS | SIGCHLD, NULL);
    waitpid(child_pid, NULL, 0);
    printf("已退出\n");
    return 0;
}

进入隔离pid的namespace

$ gcc -Wall pid.c -o pid.o
$ ./pid.o 
程序运行: 
在子进程中!
$ echo $$
1
$ mount -t proc proc /proc
$ ls /proc/
1
33
...
$ exit
已退出

init进程有信号屏蔽的特权,如果init进程没有处理该信号的逻辑,namespace内其他进程发送的信号会被屏蔽,父PIDnamespace发送的,只会接受SIGKILL或SIGSTOP

NS

mount namespace是linux的第一个namespace,所以是CLONE_NEWNS,通过隔离文件系统挂载点对隔离文件系统提供支持

在创建mount namespace时,会将文件系统复制到新的namespace,新的挂载操作都会在新的namespace

挂载有挂载传播的功能,包括共享关系和从属关系

  • 共享关系:如果两个挂载对象为共享关系,一个挂载对象中的事件会同步到另一个挂载对象
  • 从属关系:如果两个挂载对象为主从关系,主挂载对象的事件会同步到从挂载对象,而反之不行

挂载关系

  • 共享挂载share
  • 主从挂载slave
  • 共享主从挂载shared and slave
  • 私有挂载private
  • 不可绑定挂载unbindable

mount默认就是私有挂载,如果使用其他挂载方式可以指定--make-shared--make-slave

使用CLONE_NEWNS标志位

NET

隔离有网络设备,协议栈,路由表,防火墙,/proc/net,/sys/class/net和套接字等

使用CLONE_NEWNET标志位,创建的net隔离的namespace是独立出来网络,docker是通过vethpair进行通信,在通信之前是管道建立的连接

USER

usernamespace主要隔离了标识符,属性(用户,用户组,root目录,key密钥和特殊权限)

#define _GNU_SOURCE
#include <sys/capability.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <sched.h>

#define STACK_SIZE (1024 * 1024)

static char child_stack[STACK_SIZE];
char* const child_args[] = {
    "/bin/bash",
    NULL
};

int child_main(void* args){
    printf("在子进程中!\n");
    cap_t caps;
    printf("eUID = %ld; eGID = %ld; ", (long)geteuid(), (long)getegid());
    caps = cap_get_proc();
    printf("capabilities: %s\n", cap_to_text(caps, NULL));
    execv(child_args[0], child_args);
    return 1;
}

int main(){
    printf("程序运行: \n");
    int child_pid = clone(child_main, child_stack + STACK_SIZE, CLONE_NEWUSER | CLONE_NEWPID | CLONE_NEWIPC | CLONE_NEWUTS | SIGCHLD, NULL);
    waitpid(child_pid, NULL, 0);
    printf("已退出\n");
    return 0;
}

构建

$ yum install -y libcap-devel
$ gcc -Wall -lcap user.c -o user.o
$ ./user.o 
程序运行: 
在子进程中!
eUID = 0; eGID = 0; capabilities: = 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,35,36+ep
$ exit
已退出