学习Docker的User Namespace-阿里云开发者社区

开发者社区> 太公> 正文

学习Docker的User Namespace

简介: 本文假设你已经了解了Linux Container,CGroup和基本的Namespace概念。 User Namespace是Linux 3.8新增的一种namespace,用于隔离安全相关的标识和属性。使用了user namespace之后,进程在namespace内部和外部的uid/gid可
+关注继续查看

本文假设你已经了解了Linux Container,CGroup和基本的Namespace概念。

User Namespace是Linux 3.8新增的一种namespace,用于隔离安全相关的标识和属性。使用了user namespace之后,进程在namespace内部和外部的uid/gid可以不一样,常用来实现这种效果:进程在namespace外面是一个普通用户,但是在namespace里是root(uid=0),也就是进程在这个namespace里拥有所有的权限,在namespace外面只有普通用户的权限了。

既然都是解决安全方面问题的,就不得不提另外两个Linux安全方面的功能,另外两个常用的是capabilities和LSM (Linux Security Module),其中capabilities和User Namespace关系密切。通过调用带CLONE_NEWUSER参数的clone方法创建的子进程,自动拥有新User Namespace里所有capabilities。另外,进程通过unshare创建一个新的User Namespace,或者通过setns加入一个已有的Namespace,都自动获取对应User Namespace里所有的capabilities。如果接下来进程通过execve启动了新程序,就要按照capabilities计算规则重新计算新进程的capabilities。

先看一个例子 (from http://coolshell.cn/articles/17029.html)

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/mount.h>
#include <sys/capability.h>
#include <stdio.h>
#include <sched.h>
#include <signal.h>
#include <unistd.h>
 
#define STACK_SIZE (1024 * 1024)
 
static char container_stack[STACK_SIZE];
char* const container_args[] = {
    "/bin/bash",
    NULL
};
 
int pipefd[2];
 
void set_map(char* file, int inside_id, int outside_id, int len) {
    FILE* mapfd = fopen(file, "w");
    if (NULL == mapfd) {
        perror("open file error");
        return;
    }
    fprintf(mapfd, "%d %d %d", inside_id, outside_id, len);
    fclose(mapfd);
}
 
void set_uid_map(pid_t pid, int inside_id, int outside_id, int len) {
    char file[256];
    sprintf(file, "/proc/%d/uid_map", pid);
    set_map(file, inside_id, outside_id, len);
}
 
void set_gid_map(pid_t pid, int inside_id, int outside_id, int len) {
    char file[256];
    sprintf(file, "/proc/%d/gid_map", pid);
    set_map(file, inside_id, outside_id, len);
}
 
int container_main(void* arg)
{
 
    printf("Container [%5d] - inside the container!\n", getpid());
 
    printf("Container: eUID = %ld;  eGID = %ld, UID=%ld, GID=%ld\n",
            (long) geteuid(), (long) getegid(), (long) getuid(), (long) getgid());
 
    /* 等待父进程通知后再往下执行(进程间的同步) */
    char ch;
    close(pipefd[1]);
    read(pipefd[0], &ch, 1);
 
    printf("Container [%5d] - setup hostname!\n", getpid());
    //set hostname
    sethostname("container",10);
 
    //remount "/proc" to make sure the "top" and "ps" show container's information
    mount("proc", "/proc", "proc", 0, NULL);
 
    execv(container_args[0], container_args);
    printf("Something's wrong!\n");
    return 1;
}
 
int main()
{
    const int gid=getgid(), uid=getuid();
 
    printf("Parent: eUID = %ld;  eGID = %ld, UID=%ld, GID=%ld\n",
            (long) geteuid(), (long) getegid(), (long) getuid(), (long) getgid());
 
    pipe(pipefd);
  
    printf("Parent [%5d] - start a container!\n", getpid());
 
    int container_pid = clone(container_main, container_stack+STACK_SIZE, 
            CLONE_NEWUTS | CLONE_NEWPID | CLONE_NEWNS | CLONE_NEWUSER | SIGCHLD, NULL);
 
     
    printf("Parent [%5d] - Container [%5d]!\n", getpid(), container_pid);
 
    //To map the uid/gid, 
    //   we need edit the /proc/PID/uid_map (or /proc/PID/gid_map) in parent
    //The file format is
    //   ID-inside-ns   ID-outside-ns   length
    //if no mapping, 
    //   the uid will be taken from /proc/sys/kernel/overflowuid
    //   the gid will be taken from /proc/sys/kernel/overflowgid
    (container_pid, 0, uid, 1);
    set_gid_map(container_pid, 0, gid, 1);
 
    printf("Parent [%5d] - user/group mapping done!\n", getpid());
 
    /* 通知子进程 */
    close(pipefd[1]);
 
    waitpid(container_pid, NULL, 0);
    printf("Parent - container stopped!\n");
    return 0;
}

上面的例子创建了一个子进程,其中包含了UTS, PID, MOUNT和USER Namespace。编译并且运行上面的代码

gcc -Wall u.c
sudo /sbin/setcap all+eip a.out
./a.out

运行效果如下

Screen_Shot_2016_04_17_at_7_12_21_PM

可以看到,在container内部已经是root了。非常神奇的是,原来uid=1000的文件,在container内部自动显示成uid=0,原来uid=0的,自动变成uid=65534 (nobody),很完美。

代码中关键的部分在set_uid_map中,要设置新创建User Namespace和Parent User Namespace(这里例子中是系统默认的Namespace)中uid和gid的映射,只要写进程对应的两个文件即可:

  • /proc/PID/uid_map
  • /proc/PID/gid_map

这里的PID是运行在User Namespace中的进程id。写入的格式是

id-inside-ns id-outside-ns length

  • id-inside-ns: Namespace内部的uid/gid
  • id-outside-ns: Namespace外部的uid/gid
  • length 映射范围

有人可能主要到 上面的setcap操作,这一步也很关键,创建username也需要特定的capabilities才行,为了方便,这里直接设置为all

User Namespace In Docker

待续

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
doctype文档类型|学习笔记
快速学习doctype文档类型
17 0
Docker学习笔记——制作容器与容器概念
Docker能做些什么? 1.docker能够解决虚拟机能够解决的问题 2.隔离应用依赖 3.创建应用镜像并复制 4.创建容易分发的即启即用的应用 5.docker的想法是创建软件程序可移植的轻量容器   镜像 1.
1343 0
hbase 学习(十四)Facebook针对hbase的优化方案分析
使用hbase的目的是为了海量数据的随机读写,但是在实际使用中却发现针对随机读的优化和gc是一个很大的问题,而且hbase的数据是存储在Hdfs,而Hdfs是面向流失数据访问进行设计的,就难免带来效率的下降。
2263 0
基于Spark的机器学习实践 (四) - 数据可视化
# 0 相关源码 1 数据可视化的作用及常用方法 1.1 为什么要数据可视化 1.1.1 何为数据可视化? ◆ 将数据以图形图像的形式展现出来 ◆ 人类可以对三维及以下的数据产生直观的感受 1.1.
901 0
docker学习 - docker启动和镜像
# docker daemon启动 载体为daemon,调度管理engine,任务执行靠job Engine是map[string]Handler,type Handler func(*Job) Status Daemon的启动流程: - 注册serve job、pull job、create job、start job等 - 构建serveapi job,并执行ServeApi h
1356 0
docker简要学习
先放学习地址 : 第一个docker化的java应用Docker入门 未收广告费,另外不要谢我。 什么是docker 官网:https://www.docker.com/ docker is ther world's leading software containerization platform 容器 开源的,跨平台,托管在github docker的思想:集装箱,标准化,隔离 镜像,仓库,容器 使用docker就是去仓库拉取一个镜像,然后运行一条命令把镜像运行为一个容器。
687 0
+关注
太公
姜继忠,阿里云高级技术专家,负责阿里云容器服务和容器Hub等产品。目前关注容器相关技术。
19
文章
0
问答
来源圈子
更多
容器服务 Kubernetes 版(简称 ACK)提供高性能可伸缩的容器应用管理能力,支持企业级 Kubernetes 容器化应用的全生命周期管理。容器服务 Kubernetes 版简化集群的搭建和扩容等工作,整合阿里云虚拟化、存储、网络和安全能力,打造云端最佳的 Kubernetes 容器化应用运行环境。
+ 订阅
文章排行榜
最热
最新
相关电子书
更多
《2021云上架构与运维峰会演讲合集》
立即下载
《零基础CSS入门教程》
立即下载
《零基础HTML入门教程》
立即下载