命名空间(namespace)
是 Linux 内核用来隔离内核资源的方式。也相当于对一些资源进行了权限管理。在一个namespace中的进程共享一些资源,而不再一个namespace中则无法访问这些资源
linux提供的namespace服务为容器的出现和发展提供了基础条件
官方文档:https://man7.org/linux/man-pages/man7/namespaces.7.html
linux 隔离方式
名称 宏定义 隔离内容
Cgroup CLONE_NEWCGROUP Cgroup root directory (since Linux 4.6)
IPC CLONE_NEWIPC System V IPC, POSIX message queues (since Linux 2.6.19)
Network CLONE_NEWNET Network devices, stacks, ports, etc. (since Linux 2.6.24)
Mount CLONE_NEWNS Mount points (since Linux 2.4.19)
PID CLONE_NEWPID Process IDs (since Linux 2.6.24)
User CLONE_NEWUSER User and group IDs (started in Linux 2.6.23 and completed in Linux 3.8)
UTS CLONE_NEWUTS Hostname and NIS domain name (since Linux 2.6.19)
- Cgroup:物理资源namespace,使进程有一个独立的cgroup控制组
- IPC:进程间通讯,使进程有一个独立的ipc,包括消息队列,共享内存和信号量
- Network:网络空间,使进程有一个独立的网络栈
- Mount:文件空间
- PID:进程空间
- User:用户空间
- UTS:主机名空间
查看进程namespace
❯ ls -al /proc/$$/ns
总用量 0
dr-x--x--x 2 root root 0 Apr 1 15:50 .
dr-xr-xr-x 9 root root 0 Apr 1 15:43 ..
lrwxrwxrwx 1 root root 0 Apr 1 15:50 cgroup -> cgroup:[4026531835]
lrwxrwxrwx 1 root root 0 Apr 1 15:50 ipc -> ipc:[4026531839]
lrwxrwxrwx 1 root root 0 Apr 1 15:50 mnt -> mnt:[4026531840]
lrwxrwxrwx 1 root root 0 Apr 1 15:50 net -> net:[4026532293]
lrwxrwxrwx 1 root root 0 Apr 1 15:50 pid -> pid:[4026531836]
lrwxrwxrwx 1 root root 0 Apr 1 15:50 pid_for_children -> pid:[4026531836]
lrwxrwxrwx 1 root root 0 Apr 1 15:50 user -> user:[4026531837]
lrwxrwxrwx 1 root root 0 Apr 1 15:50 uts -> uts:[4026531838]
如果两个进程的某个
namespace
文件指向同一个链接文件,说明其相关资源在同一个 namespace
中相关函数
- clone:clone 在创建新进程的同时创建 namespace
- setns:setns 加入一个已经存在的 namespace
- unshare:unshare 在原先进程上进行 namespace 隔离
资源隔离测试用例
一、测试UTS隔离
#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <sched.h>
#include <signal.h>
#include <unistd.h>
/* 定义一个给 clone 用的栈,栈大小1M */
#define STACK_SIZE (1024 * 1024)
static char container_stack[STACK_SIZE];
char* const container_args[] = {
"/bin/bash",
NULL
};
int container_main(void* arg)
{
printf("Container - inside the container!\n");
sethostname("container",10); /* 设置hostname */
execv(container_args[0], container_args);
printf("Something's wrong!\n");
return 1;
}
int main()
{
printf("Parent - start a container!\n");
int container_pid = clone(container_main, container_stack+STACK_SIZE,
CLONE_NEWUTS | SIGCHLD, NULL); /*启用CLONE_NEWUTS Namespace隔离 */
waitpid(container_pid, NULL, 0);
printf("Parent - container stopped!\n");
return 0;
}
image-20220322142542912
二、测试PID隔离
#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <sched.h>
#include <signal.h>
#include <unistd.h>
/* 定义一个给 clone 用的栈,栈大小1M */
#define STACK_SIZE (1024 * 1024)
static char container_stack[STACK_SIZE];
char* const container_args[] = {
"/bin/bash",
NULL
};
int container_main(void* arg)
{
/* 查看子进程的PID,我们可以看到其输出子进程的 pid 为 1 */
printf("Container [%5d] - inside the container!\n", getpid());
sethostname("container",10);
execv(container_args[0], container_args);
printf("Something's wrong!\n");
return 1;
}
int main()
{
printf("Parent [%5d] - start a container!\n", getpid());
/*启用PID namespace - CLONE_NEWPID*/
int container_pid = clone(container_main, container_stack+STACK_SIZE,
CLONE_NEWUTS | CLONE_NEWPID | SIGCHLD, NULL);
waitpid(container_pid, NULL, 0);
printf("Parent - container stopped!\n");
return 0;
}
image-20220322143402190
可以看到子进程的PID为1,可以看到和docker中的进程是类似的,即实现了进程隔离。
但是在新的pid namespace中执行ps、top等命令,还是会显示很多进程,原因是这些命令读取进程时通过“/proc”目录下的文件来获取的。
三、测试Mount隔离
#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <sched.h>
#include <signal.h>
#include <unistd.h>
/* 定义一个给 clone 用的栈,栈大小1M */
#define STACK_SIZE (1024 * 1024)
static char container_stack[STACK_SIZE];
char* const container_args[] = {
"/bin/bash",
NULL
};
int container_main(void* arg)
{
printf("Container [%5d] - inside the container!\n", getpid());
sethostname("container",10);
/* 重新mount proc文件系统到 /proc下 */
system("mount -t proc proc /proc");
execv(container_args[0], container_args);
printf("Something's wrong!\n");
return 1;
}
int main()
{
printf("Parent [%5d] - start a container!\n", getpid());
/* 启用Mount Namespace - 增加CLONE_NEWNS参数 */
int container_pid = clone(container_main, container_stack+STACK_SIZE,
CLONE_NEWUTS | CLONE_NEWPID | CLONE_NEWNS | SIGCHLD, NULL);
waitpid(container_pid, NULL, 0);
printf("Parent - container stopped!\n");
return 0;
}
proc是一个虚拟文件系统
隔离效果如下所示,在进程隔离的情况下,再进行文件挂载隔离,执行ps、top命令就只获取到本namespace下的资源。
image-20220322144830554
四、测试User隔离
创建一个新的user namespace ,
unshare --user -r --mount /bin/bash
效果如下图所示,通过-r参数,把新user namespace (
user:[4026532240]
)的root用户映射到了原user namespac的ctf用户。这就形成了同一个用户在不同user namespace
中有不同的uid和gid。image-20220322152031078
docker默认是没有使用
user namespace
的。它创建的容器和宿主机是同一 user namespace
,如下所示五、测试Network隔离
network namespace主要是用来进行网络隔离的,将网络划分为不同的namespace,不同namespace的操作互不影响,每个network namespace包含:
- 网卡和mac地址
- arp表
- ip地址,端口,路由表
- iptables等网络资源
在宿主机上抓取容器中的网络流量
1、获取某个容器的pid
❯ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
7ea005a0b2fb hive_agent:3.401.140-120 "/bin/bash /titan/ag…" 5 weeks ago Up About a minute hiveagent
ea9f21f54b50 pwn_awd "/run.sh" 5 months ago Up About an hour 0.0.0.0:10004->9999/tcp awd
root@ubuntu ~
❯ docker inspect --format "{{.State.Pid}}" awd
5345
2、通过 进入到容器的网络空间
通过前后ip a来查看变化
root@ubuntu ~
❯ ip a 127 ↵
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: enp0s5: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether 00:1c:42:bf:dc:a6 brd ff:ff:ff:ff:ff:ff
inet 10.211.55.9/24 brd 10.211.55.255 scope global dynamic enp0s5
valid_lft 1676sec preferred_lft 1676sec
inet6 fdb2:2c26:f4e4:0:8391:699:255e:b634/64 scope global noprefixroute dynamic
valid_lft 2591983sec preferred_lft 604783sec
inet6 fe80::593f:cc6d:4234:e5e6/64 scope link
valid_lft forever preferred_lft forever
3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:7f:af:0d:34 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
valid_lft forever preferred_lft forever
inet6 fe80::42:7fff:feaf:d34/64 scope link
valid_lft forever preferred_lft forever
5: veth9d533b3@if4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default
link/ether 66:fb:dd:d0:6d:68 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet6 fe80::64fb:ddff:fed0:6d68/64 scope link
valid_lft forever preferred_lft forever
root@ubuntu ~
❯ nsenter -n -t 5345
root@ubuntu ~
❯ ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
4: eth0@if5: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
valid_lft forever preferred_lft forever
root@ubuntu ~
3、tcpdump 抓取流量
tcpdump -vv
六、Chroot
该命令的作用是改变根目录位置,在指定的根目录运行命令。在Linux系统中,默认的跟系统是“/”。在某些场景下使用chroot之后,会增加系统的安全性,限制用户的权限,chroot作用范围在当前进程和当前进程的子进程中。其次是建立一个与原系统隔离的系统目录结构,方便用户的开发。