【Docker 内核详解】namespace 资源隔离(五):User namespaces

【Docker 内核详解 - namespace 资源隔离】系列包含:

  • namespace 资源隔离(一):进行 namespace API 操作的 4 种方式
  • namespace 资源隔离(二):UTS namespace & IPC namespace
  • namespace 资源隔离(三):PID namespace
  • namespace 资源隔离(四):Mount namespace & Network namespace
  • namespace 资源隔离(五):User namespaces

namespace 资源隔离(五):User namespaces

user namespace 主要隔离了安全相关的 标识符identifier)和 属性attribute),包括用户 ID、用户组 ID、root 目录、key(指密钥)以及特殊权限。通俗地讲,一个普通用户的进程通过 clone() 创建的新进程在新 user namespace 中可以拥有不同的用户和用户组。这意味着一个进程在容器外属于一个没有特权的普通用户,但是它创建的容器进程却属于拥有所有权限的超级用户,这个技术为容器提供了极大的自由。

user namespace 是目前的 6 6 6namespace 中最后一个支持的,并且直到 Linux 内核 3.8 3.8 3.8 版本的时候还未完全实现(还有部分文件系统不支持)。user namespace 实际上并不算完全成熟,很多发行版担心安全问题,在编译内核的时候并未开启 USER_NS。Docker 在 1.10 1.10 1.10 版本中对 user namespace 进行了支持。只要用户在启动 Docker daemon 的时候指定了 --userns-remap,那么当用户运行容器时,容器内部的 root 用户并不等于宿主机内的 root 用户,而是映射到宿主上的普通用户。在进行接下来的代码实验时,请确保系统的 Linux 内核版本高于 3.8 3.8 3.8 并且内核编译时开启了 USER_NS(如果不会选择,请使用 Ubuntu 14.04 14.04 14.04)。

Linux 中,特权用户的 user ID 就是 0 0 0,演示的最后将看到 user ID 非 0 0 0 的进程启动 user namespace 后 user ID 可以变为 0 0 0。使用 user namespace 的方法跟别的 namespace 相同,即调用 clone()unshare() 时加入 CLONE_NEWUSER 标识位。修改代码并另存为 userns.c,为了看到用户权限(Capabilities),还需要安装 libcap-dev 包。

首先包含以下头文件以调用 Capabilities 包。

#include <sys/capability.h>

其次在子进程函数中加入 geteuid()getegid() 得到 namespace 内部的 user ID,通过 cap_get_proc() 得到当前进程的用户拥有的权限,并通过 cap_to_text() 输出。

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;
}

在主函数的 clone() 调用中加人我们熟悉的标识符。

// [...]
int child_pid = clone(child_main, child_stack + STACK_SIZE, CLONE_NEWUSER | SIGCHLD, NULL);
// [...]

至此,第一部分的代码修改就结束了。在编译之前先查看一下当前用户的 uidguid,请注意此时显示的是普通用户。

$ id -u
1000
$ id -g
1000

然后开始编译运行,并进入新建的 user namespace,会发现 shell 提示符前的用户名已经变为 nobody

$ gcc userns.c -Wall -lcap -o userns.o && ./userns.o
程序开始:
在子进程中!
eUID=65534; eGID=65534; capabilities: = cap_chown,cap_dac_override,[...]37+ep     <<--此处省略部分输出,已拥有全部权限
nobody@ubuntu$

通过验证可以得到以下信息。

  • user namespace 被创建后,第一个进程被赋予了该 namespace 中的全部权限,这样该 init 进程就可以完成所有必要的初始化工作,而不会因权限不足出现错误。
  • namespace 内部观察到的 UID 和 GID 已经与外部不同了,默认显示为 65534 65534 65534,表示尚未与外部 namespace 用户映射。此时需要对 user namespace 内部的这个初始 user 和它外部 namespace 的某个用户建立映射,这样可以保证当涉及一些对外部 namespace 的操作时,系统可以检验其权限(比如发送一个信号量或操作某个文件)。同样用户组也要建立映射。
  • 还有一点虽然不能从输出中发现,但却值得注意。用户在新 namespace 中有全部权限,但它在创建它的父 namespace 中不含任何权限,就算调用和创建它的进程有全部权限也是如此。因此哪怕是 root 用户调用了 clone()user namespace 中创建出的新用户,在外部也没有任何权限。
  • 最后,user namespace 的创建其实是一个层层嵌套的树状结构。最上层的根节点就是 root namespace,新创建的每个 user namespace 都有一个父节点 user namespace,以及零个或多个子节点 user namespace,这一点与 PID namespace 非常相似。

从下图中可以看到,namespace 实际上就是按层次关联起来,每个 namespace 都发源于最初的 root namespace 并与之建立映射。

在这里插入图片描述
接下来就要进行用户绑定操作,通过在 /proc/[pid]/uid_map/proc/[pid]/gid_map 两个文件中写入对应的绑定信息就可以实现这一点,格式如下。

ID-inside-ns ID-outside-ns length

写这两个文件时需要注意以下几点。

  • 这两个文件只允许由拥有该 user namespaceCAP_SETUID 权限的进程写入一次,不允许修改。
  • 写入的进程必须是该 user namespace 的父 namespace 或者子 namespace
  • 第一个字段 ID-inside-ns 表示新建的 user namespace 中对应的 user/group ID,第二个字段 ID-outside-ns 表示 namespace 外部映射的 user/group ID。最后一个字段表示映射范围,通常填 1 1 1,表示只映射一个,如果填大于 1 1 1 的值,则按顺序建立一一映射。

明白了上述原理,再次修改代码,添加设置 uidgid 的函数。

// [...]
void set_uid_map(pid_t pid, int inside_id, int outside_id, int length) {char path[256];sprintf(path, "/proc/%d/uid_map", getpid());FILE* uid_map = fopen(path, "w");fprintf(uid_map, "%d %d %d", inside_id, outside_id, length);fclose(uid_map);
}
void set_gid_map(pid_t pid, int inside_id, int outside_id, int length) {char path[256];sprintf(path, "/proc/%d/gid_map", getpid());FILE* gid_map = fopen(path, "w");fprintf(gid_map, "%d %d %d", inside_id, outside_id, length);fclose(gid_map);
}
int child_main(void* args){cap_t caps;printf("在子进程中!\n")set_uid_map(getpid(), 0, 1000, 1);set_gid_map(getpid(), 0, 1000, 1);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;
}
// [...]

编译后即可看到 user 已经变成了 root

$ gcc uscrns.c -Wall -lcap -o usernc.o && ./usernc.o
程序开始:
在子进程中!
eUID = 0; eGID = 0; capabilities: = [..],37+ep
root@ubuntu:~#

至此,就已经完成了绑定的工作,可以看到演示全程都是在普通用户下执行的,最终实现了在 user namespace 中成为 root 用户,对应到外部则是一个 uid 1000 1000 1000 的普通用户。

如果要把 user namespace 与其他 namespace 混合使用,那么依旧需要 root 权限。解决方案是先以普通用户身份创建 user namespace,然后在新建的 namespace 中作为 root,在 clone() 进程加入其他类型的 namespace 隔离。

讲解完 user namespace,再来谈谈 Docker。Docker 不仅使用了 user namespace,还使用了在 user namespace 中涉及的 Capabilities 机制。从内核 2.2 2.2 2.2 版本开始,Linux 把原来和超级用户相关的高级权限划分为不同的单元,称为 Capability。这样管理员就可以独立对特定的 Capability 进行使用或禁止。Docker 同时使用 user namespace 和 Capability,这在很大程度上加强了容器的安全性。

说到安全,namespace 6 6 6 项隔离看似全面,实际上依旧没有完全隔离 Linux 的资源,比如 SELinuxcgroups/sys/proc/sys/dev/sd* 等目录下的资源。关于安全,将会在后续博客中进一步探讨。


本系列从 namespace 使用的 API 开始,结合 Docker 逐步对 6 个 namespace 进行了讲解。相信把讲解过程中所有的代码整合起来,读者也能实现一个属于自己的 “shell” 容器了。虽然 namespace 技术使用非常简单,但要真正把容器做到安全易用却并非易事。PID namespace 中,需要实现一个完善的 init 进程来维护好所有进程;network namespace 中,还有复杂的路由表和 iptables 规则没有配置;user namespace 中还有许多权限问题需要考虑。其中的某些方面 Docker 已经做得不错,而有些方面才刚刚起步,这些内容我们会在后续博客中详细介绍。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/106600.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

项目管理软件中注释功能的作用是什么?

在项目管理软件中&#xff0c;注释功能允许您对任务、文件夹和项目进行详细的标注。这一功能不仅便于团队成员之间的沟通与协作&#xff0c;还能提高项目管理的效率。通过在项目中添加评论&#xff0c;您可以及时了解项目的最新动态&#xff0c;提出疑问并寻求解决方案。此外&a…

汇编语言基础

引言 汇编语言是直接在硬件之上工作的编程语言&#xff0c;首先要了解硬件系统的结构&#xff0c;才能有效的应用汇编语言对其编程。汇编课程的研究重点放在如何利用硬件系统的编程结构和指令集有效灵活的控制系统进行工作。 基础知识 1.1机器语言 机器语言是机器指令的集合…

单链表---结构体实现

定义 链表称为线性表的链式存储&#xff0c;顺序表逻辑上相邻的数据&#xff0c;存储位置也相邻。链表逻辑上相邻的数据&#xff0c;存储位置是随机分布在内存的各个位置上的。 故 对于每一个结点&#xff0c;定义的结构体是&#xff1a; typedef struct _LinkNode {int d…

Java-使用sqlSessionTemplate实现批量更新-模拟mybatis 动态sql

环境准备&#xff08;非核心方法&#xff09; 创建表 创建表的sql(下表是基于Oracle创建的) CREATE TABLE "SYSTEM"."STUDENT" ("ID" NUMBER(10, 0),"NAME" VARCHAR2(20 BYTE),"ADDRES" CLOB,PRIMARY KEY ( …

milvus和相似度检索

流程 milvus的使用流程是 创建collection -> 创建partition -> 创建索引(如果需要检索) -> 插入数据 -> 检索 这里以Python为例, 使用的milvus版本为2.3.x 首先按照库&#xff0c; python3 -m pip install pymilvus Connect from pymilvus import connections c…

mac电脑版数字图像处理软件:ACDSee Photo Studio 9最新 for Mac

ACDSee Photo Studio 9是一款由ACD Systems开发的功能强大的照片管理和编辑软件&#xff0c;专为Mac用户提供一站式解决方案&#xff0c;方便用户轻松浏览、管理和编辑照片。该软件提供了许多实用的工具和功能&#xff0c;包括高效的导入和排序工具、强大的编辑工具、智能组织和…

CustomTabBar 自定义选项卡视图

1. 用到的技术点 1) Generics 泛型 2) ViewBuilder 视图构造器 3) PreferenceKey 偏好设置 4) MatchedGeometryEffect 几何效果 2. 创建枚举选项卡项散列&#xff0c;TabBarItem.swift import Foundation import SwiftUI//struct TabBarItem: Hashable{ // let ico…

Java练习题-获取数组元素最大值

✅作者简介&#xff1a;CSDN内容合伙人、阿里云专家博主、51CTO专家博主、新星计划第三季python赛道Top1&#x1f3c6; &#x1f4c3;个人主页&#xff1a;hacker707的csdn博客 &#x1f525;系列专栏&#xff1a;Java练习题 &#x1f4ac;个人格言&#xff1a;不断的翻越一座又…

Qt之给控件添加右键菜单

一、设置控件 在对应控件的属性中&#xff0c;将contextMenuPolicy设置为CustomContextMenu。 二、添加槽函数 在对应控件上右键选择槽函数customContextMenuRequested(QPoint)。 三、在槽函数中添加右键菜单 在槽函数中输入如下代码&#xff0c;添加右键菜单。 //右键菜单 …

Flutter 剪裁(Clip)

&#x1f525; ClipOval &#x1f525; 子组件为正方形时剪裁成内贴圆形&#xff1b;为矩形时&#xff0c;剪裁成内贴椭圆 裁剪纯色背景 ClipOval(child: Container(width: 300.w,height: 300.w,decoration: const BoxDecoration(color: Colors.red),),), 裁剪背景图片 裁剪前…

《Deep Residual Learning for Image Recognition》阅读笔记

论文标题 《Deep Residual Learning for Image Recognition》 撑起CV界半边天的论文Residual &#xff1a;主要思想&#xff0c;残差。 作者 何恺明&#xff0c;超级大佬。微软亚研院属实是人才辈出的地方。 初读 摘要 提问题&#xff1a; 更深层次的神经网络更难训练。 …

(vue3)大事记管理系统 文章管理页

[element-plus进阶] 文章列表渲染&#xff08;带搜索&到分页&#xff09; 表单架设&#xff1a;当前el-form标签配置一个inline属性&#xff0c;里面的元素就会在一行显示了 中英国际化处理&#xff1a;App.vue中el-config-provider标签包裹组件&#xff0c;意味着整个组…

【LeetCode高频SQL50题-基础版】打卡第6天:第31~35题

文章目录 【LeetCode高频SQL50题-基础版】打卡第6天&#xff1a;第31~35题⛅前言员工的直属部门&#x1f512;题目&#x1f511;题解 判断三角形&#x1f512;题目&#x1f511;题解 连续出现的数字&#x1f512;题目&#x1f511;题解 指定日期的产品价格&#x1f512;题目&am…

Java实现hack汇编器

Hack汇编语言是一种特定于计算机体系结构的汇编语言&#xff0c;使用Hack架构的机器码指令来编写程序。Hack是一种基于Von Neumann结构的计算机体系结构&#xff0c;由Harvard大学的Nand to Tetris项目开发出来&#xff0c;用于实现计算机硬件和软件。 Hack汇编语言主要用于在…

linux 内核中的pid和前缀树

前言&#xff1a; 写这个文章的初衷是因为今天手写了一个字典树&#xff0c;然后写字典树以后忽然想到了之前看的技术文章&#xff0c;linux kernel 之前的pid 申请方式已经从 bitmap 变成了 基数树&#xff0c;所以打算写文章再回顾一下这种数据结构算法 一、内核中pid的申请…

【学习笔记】minIO分布式文件服务系统

MinIO 一、概述 1.1 minIO是什么&#xff1f; MinIO是专门为海量数据存储、人工智能、大数据分析而设计的对象存储系统。&#xff08;早前流行的还有FastDFS&#xff09; 据官方介绍&#xff0c;单个对象最大可存储5T&#xff0c;非常适合存储海量图片、视频、日志文件、备…

java.sql.SQLFeatureNotSupportedException解决方法

使用MyBatis访问数据库查询数据时报错&#xff1a; Caused by: java.sql.SQLFeatureNotSupportedExceptionat com.alibaba.druid.pool.DruidPooledResultSet.getObject(DruidPooledResultSet.java:1771)at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)at sun…

03在命令行环境中创建Maven版的Java工程,了解pom.xml文件的结构,了解Java工程的目录结构并编写代码,执行Maven相关的构建命令

创建Maven版的Java工程 Maven工程的坐标 数学中使用x、y、z三个向量可以在空间中唯一的定位一个点, Maven中也可以使用groupId,artifactId,version三个向量在Maven的仓库中唯一的定位到一个jar包 groupId: 公司或组织域名的倒序, 通常也会加上项目名称代表公司或组织开发的一…

JDBC操作BLOB类型字段

JDBC中Statement接口本身不能直接操作BLOB数据类型 操作BLOB数据类型需要使用PreparedStatement或者CallableStatement(存储过程) 这里演示通过PreparedStatement操作数据库BLOB字段 设置最大传入字节 一般是4M 可以通过以下命令修改 set global max_allowed_packet1024*1…

网页在线打开PDF_网站中在线查看PDF之pdf.js

一、pdf.js简介 PDF.js 是一个使用 HTML5 构建的便携式文档格式查看器。 pdf.js 是社区驱动的&#xff0c;并由 Mozilla 支持。我们的目标是为解析和呈现 PDF 创建一个通用的、基于 Web 标准的平台。 pdf.js 将 PDF 文档转换为 HTML5 Canvas 元素&#xff0c;并使用 JavaScr…