mirror of
https://github.com/yeasy/docker_practice.git
synced 2026-03-11 12:21:17 +00:00
Add more content and fix format
This commit is contained in:
@@ -1,18 +1,85 @@
|
||||
## 18.1 内核命名空间
|
||||
|
||||
命名空间 (Namespace) 是 Linux 容器隔离的基础,它确保了容器内的进程无法干扰主机或其他容器。
|
||||
命名空间 (Namespace) 是 Linux 容器隔离的基础,它确保了容器内的进程无法直接干扰主机或其他容器。虽然在本书第 12 章中我们已经从底层实现的角度介绍了 Namespace,但在本节中,我们将重点探讨其**安全意义**及相关配置。
|
||||
|
||||
Docker 容器和 LXC 容器很相似,所提供的安全特性也差不多。当用 `docker run` 启动一个容器时,在后台 Docker 为容器创建了一个独立的命名空间和控制组集合。
|
||||
### 18.1.1 隔离的安全本质
|
||||
|
||||
命名空间提供了最基础也是最直接的隔离,在容器中运行的进程不会被运行在主机上的进程和其它容器发现和作用。
|
||||
Docker 守护进程在启动容器时,会在后台为容器创建一套独立的命名空间。命名空间提供了最基础也是最直接的隔离:
|
||||
|
||||
每个容器都有自己独有的网络栈,意味着它们不能访问其他容器的 sockets 或接口。不过,如果主机系统上做了相应的设置,容器可以像跟主机交互一样的和其他容器交互。当指定公共端口或使用 links 来连接 2 个容器时,容器就可以相互通信了 (可以根据配置来限制通信的策略)。
|
||||
- **PID Namespace**:防止容器内的进程查看或终止宿主机或其他容器的进程。恶意攻击者即使在容器内获得了 root 权限,也无法通过 `kill` 命令影响宿主机上的关键服务。
|
||||
- **NET Namespace**:每个容器都有自己独立的网络栈。如果没有显式地进行端口映射或将容器连接到同一网络,容器之间无法网络互通,从而限制了横向移动的能力。
|
||||
- **MNT Namespace**:为容器提供独立的文件系统视图。这可以防止容器不经意或恶意地修改宿主机的重要系统文件(如 `/etc/passwd`)。
|
||||
|
||||
从网络架构的角度来看,所有的容器通过本地主机的网桥接口相互通信,就像物理机器通过物理交换机通信一样。
|
||||
### 18.1.2 命名空间不是绝对安全的护城河
|
||||
|
||||
那么,内核中实现命名空间和私有网络的代码是否足够成熟?
|
||||
尽管命名空间提供了很好的隔离性,但我们必须认识到:**所有的容器依然共享同一个宿主机的 Linux 内核**。
|
||||
|
||||
内核命名空间从 2.6.15 版本 (2006 年 1 月发布) 之后被引入,数年间,这些机制的可靠性在诸多大型生产系统中被实践验证。
|
||||
这意味着,一旦宿主机的内核存在提权漏洞(如著名的 Dirty COW 漏洞),攻击者有可能通过突破 Namespace 的限制,直接在内核层面执行恶意代码,从而实现“容器逃逸”。
|
||||
|
||||
实际上,命名空间的想法和设计提出的时间要更早,最初是为了在内核中引入一种机制来实现 [OpenVZ](https://en.wikipedia.org/wiki/OpenVZ) 的特性。
|
||||
而 OpenVZ 项目早在 2005 年就发布了,其设计和实现都已经十分成熟。
|
||||
> [!WARNING]
|
||||
> 为了缓解内核漏洞带来的威胁,生产环境务必保持宿主机 Linux 内核的及时修补与更新,或者借助诸如 gVisor、Kata Containers 等提供了独立内核的安全容器技术。
|
||||
|
||||
通过命名空间,Docker 也能限制进程从外部环境获取信息。
|
||||
例如,由于进程环境被隔离,进程在内部其实是无法感知到外部宿主机的存在的。它既不能获取其他容器的进程列表,也无法通过网络与其他系统进行交互(除非经过配置)。
|
||||
|
||||
### 18.1.1 用户命名空间 与提权防护
|
||||
|
||||
在所有的 Namespace 中,**User Namespace** 对安全的影响尤为关键。
|
||||
|
||||
在默认情况下,容器内的 `root` 用户(UID=0)就是宿主机上的 `root` 用户。如果攻击者设法突破了容器的其他隔离机制获取了宿主机的访问权限,他将拥有宿主机的最高系统权限。
|
||||
|
||||
通过启用 **User Namespace Remapping (用户命名空间映射)**,我们可以将容器内的 `root` 用户映射到宿主机上的一个无特权普通用户。
|
||||
|
||||
#### 如何配置 User Namespace
|
||||
|
||||
要在 Docker 服务端启用这一特性,需要修改 Docker 的配置文件 `/etc/docker/daemon.json`。
|
||||
|
||||
1. **设置映射策略**
|
||||
|
||||
编辑配置文件,添加 `userns-remap` 配置项:
|
||||
|
||||
```json
|
||||
{
|
||||
"userns-remap": "default"
|
||||
}
|
||||
```
|
||||
|
||||
使用 `default` 值时,Docker 会自动在宿主机上创建一个名为 `dockremap` 的用户和用户组。
|
||||
|
||||
2. **验证子 UID 和子 GID 分配**
|
||||
|
||||
Docker 会通过 `/etc/subuid` 和 `/etc/subgid` 文件为 `dockremap` 分配一个高位的 UID 范围:
|
||||
|
||||
```bash
|
||||
$ cat /etc/subuid
|
||||
dockremap:165536:65536
|
||||
```
|
||||
这意味着:容器内的 UID `0`(root 用户)在宿主机上实际被映射成了 UID `165536`。如果是容器内的 UID `1`,对应宿主机的 `165537`,以此类推。
|
||||
|
||||
3. **重启 Docker 守护进程**
|
||||
|
||||
```bash
|
||||
$ sudo systemctl restart docker
|
||||
```
|
||||
|
||||
#### 验证映射效果
|
||||
|
||||
我们可以运行一个简单的容器并执行 `sleep` 命令,同时在宿主机上观察进程的所有者:
|
||||
|
||||
```bash
|
||||
## 在容器内以 root 身份运行
|
||||
$ docker run -d --name userns_test alpine sleep 3600
|
||||
|
||||
## 在宿主机上查看该 sleep 进程
|
||||
$ ps aux | grep sleep
|
||||
165536 12345 0.0 0.0 1568 4 ? Ss 14:20 0:00 sleep 3600
|
||||
```
|
||||
|
||||
你会发现,尽管在容器内该进程是由 root 启动的,但在宿主机上,它的属主是 `165536`(一个完全没有特权的用户)。
|
||||
|
||||
> [!TIP]
|
||||
> 启用 User Namespace 会对容器共享宿主机数据卷(Bind Mount)产生权限影响。你需要确保映射后的高位 UID 对宿主机上的挂载目录具有合适的读写权限。
|
||||
|
||||
### 18.1.4 总结
|
||||
|
||||
内核命名空间从 Linux 2.6.15 版本 (2006 年) 被引入,十余年间,这些机制的可靠性在诸多大型生产系统中被实践验证。通过合理利用命名空间(尤其是 User Namespace),可以极大地收窄攻击面,显著提升容器部署的安全性。
|
||||
|
||||
@@ -1,9 +1,102 @@
|
||||
## 18.2 控制组
|
||||
|
||||
控制组是 Linux 容器机制的另外一个关键组件,负责实现资源的审计和限制。
|
||||
控制组 (Cgroups) 是 Linux 容器机制的另外一个关键组件。如果说命名空间 (Namespace) 决定了容器能**看到**什么,那么控制组就决定了容器能**使用**多少资源。
|
||||
|
||||
它提供了很多有用的特性;以及确保各个容器可以公平地分享主机的内存、CPU、磁盘 IO 等资源;当然,更重要的是,控制组确保了当容器内的资源使用产生压力时不会连累主机系统。
|
||||
在安全领域中,资源的不可用性本身就是一种安全威胁。控制组负责实现资源的审计和限制,这对于抵御资源耗尽型攻击(如拒绝服务攻击 DoS)至关重要。
|
||||
|
||||
尽管控制组不负责隔离容器之间相互访问、处理数据和进程,它在防止拒绝服务 (DDOS) 攻击方面是必不可少的。尤其是在多用户的平台 (比如公有或私有的 PaaS) 上,控制组十分重要。例如,当某些应用程序表现异常的时候,可以保证一致地正常运行和性能。
|
||||
### 18.2.1 为什么资源限制关乎安全?
|
||||
|
||||
控制组机制始于 2006 年,内核从 2.6.24 版本开始被引入。
|
||||
默认情况下,Docker 容器对系统资源的使用是没有限制的:一个容器理论上可以使用宿主机所有的 CPU 计算能力、吃光所有的内存、耗尽所有的系统 PID。
|
||||
|
||||
想象一下以下场景:
|
||||
- 一个恶意用户向你暴露在公网的应用发起海量并发请求。
|
||||
- 应用程序逻辑中存在内存泄漏漏洞。
|
||||
- 黑客在入侵容器后,在里面运行了挖矿木马程序。
|
||||
|
||||
如果没有 Cgroups 的限制,某个容器内的异常行为(或恶意攻击)将会榨干宿主机的资源,导致宿主机上其他健康的容器甚至 Docker 守护进程自身因为 OOM(Out Of Memory)崩溃或 CPU 饥饿而停止响应。
|
||||
|
||||
### 18.2.2 核心资源限制实战
|
||||
|
||||
为了确保多租户平台(如公有或私有的 PaaS 平台)的稳定性,或者在生产环境防止服务级联故障,我们要养成在启动容器时**显式声明资源上限**的习惯。
|
||||
|
||||
#### 1. 内存限制
|
||||
|
||||
限制内存可以防止应用程序因内存泄漏或恶意载荷导致宿主机 OOM。
|
||||
|
||||
**关键参数:**
|
||||
- `-m, --memory=""`:硬限制,容器可使用的最大内存量。
|
||||
- `--memory-swap=""`:限制容器可使用的内存与 Swap 总量。
|
||||
|
||||
**实战示例:**
|
||||
|
||||
限制容器最多只能使用 512MB 内存,并且禁用 Swap(将 memory 和 memory-swap 设置成一样的值即可):
|
||||
|
||||
```bash
|
||||
$ docker run -d \
|
||||
--name web_app \
|
||||
--memory="512m" \
|
||||
--memory-swap="512m" \
|
||||
nginx:alpine
|
||||
```
|
||||
|
||||
如果该容器内的应用尝试分配超过 512MB 的内存,该进程将会被内核的 OOM Killer 杀掉,但绝不会波及到宿主机的其他部分。
|
||||
|
||||
#### 2. CPU 限制
|
||||
|
||||
限制 CPU 可以防止个别计算密集型的容器垄断 CPU 时间片,保证系统的调度公平性。
|
||||
|
||||
**关键参数:**
|
||||
- `--cpus=<value>`:指定容器可以使用的 CPU 核心数量(可以是小数)。
|
||||
- `-c, --cpu-shares=0`:软限制,设置容器使用 CPU 的相对权重(默认是 1024)。
|
||||
|
||||
**实战示例:**
|
||||
|
||||
限制容器最多使用 1.5 个 CPU 核心的算力:
|
||||
|
||||
```bash
|
||||
$ docker run -d \
|
||||
--name worker_app \
|
||||
--cpus="1.5" \
|
||||
busybox \
|
||||
md5sum /dev/urandom
|
||||
```
|
||||
|
||||
即使上面的命令是一个死循环的哈希计算进程,容器也永远无法吃满双核 CPU 系统的全部算力。
|
||||
|
||||
#### 3. 进程数限制
|
||||
|
||||
进程炸弹(Fork Bomb)是一种典型的拒绝服务攻击方式,它通过不断 `fork()` 新进程来耗尽系统的进程表条目,导致系统无法创建任何新任务。
|
||||
|
||||
**关键参数:**
|
||||
- `--pids-limit=<number>`:限制容器内允许创建的最大进程数。
|
||||
|
||||
**实战示例:**
|
||||
|
||||
一个常规的 Web 服务进程数通常在几十到上百之间。我们可以设定一个合理的上限来防范 Fork 炸弹:
|
||||
|
||||
```bash
|
||||
$ docker run -d \
|
||||
--name app_service \
|
||||
--pids-limit=100 \
|
||||
python:alpine python app.py
|
||||
```
|
||||
|
||||
当容器内的进程总数达到 100 时,任何尝试派生新进程的操作都会失败并返回 `Resource temporarily unavailable`,从而挫败相关的攻击行为。
|
||||
|
||||
### 18.2.3 最佳实践建议
|
||||
|
||||
在生产环境中,不仅要在单机使用 Docker 命令时设置这些参数,更应当在集群编排工具中将资源配额制度化。
|
||||
|
||||
例如,在 Kubernetes 中,强烈建议为每个 Pod 设置 `requests` 和 `limits`:
|
||||
|
||||
```yaml
|
||||
resources:
|
||||
requests:
|
||||
memory: "256Mi"
|
||||
cpu: "250m"
|
||||
limits:
|
||||
memory: "512Mi"
|
||||
cpu: "500m"
|
||||
```
|
||||
|
||||
通过 Cgroups 的资源边界控制,你可以从根本上切断一条导致整个系统雪崩的脆弱链路。这也进一步使得 Docker 以及容器技术成为了现代高可用服务的基础设施首选。
|
||||
|
||||
@@ -1,20 +1,86 @@
|
||||
## 18.3 Docker 服务端的防护
|
||||
## 18.3 服务端防护
|
||||
|
||||
运行一个容器或应用程序的核心是通过 Docker 服务端。Docker 服务的运行目前需要 root 权限,因此其安全性十分关键。
|
||||
Docker 守护进程(`dockerd`)是容器生命周期的核心驱动力。默认情况下,Docker 服务的运行需要极高的系统特权(root 权限),因此其安全性关系到整台宿主机的生死存亡。
|
||||
|
||||
首先,确保只有可信的用户才可以访问 Docker 服务。Docker 允许用户在主机和容器间共享文件夹,同时不需要限制容器的访问权限,这就容易让容器突破资源限制。例如,恶意用户启动容器的时候将主机的根目录 `/` 映射到容器的 `/host` 目录中,那么容器理论上就可以对主机的文件系统进行任意修改了。这听起来很疯狂?但是事实上几乎所有虚拟化系统都允许类似的资源共享,而没法禁止用户共享主机根文件系统到虚拟机系统。
|
||||
如果 Docker 守护进程的访问控制没有做好,恶意攻击者可以通过 Docker API 轻易地启动一个特权容器,并将宿主机的根目录(`/`)挂载到容器中,从而完全接管服务器。
|
||||
|
||||
这将会造成很严重的安全后果。因此,当提供容器创建服务时 (例如通过一个 web 服务器),要更加注意进行参数的安全检查,防止恶意的用户用特定参数来创建一些破坏性的容器。
|
||||
为了加强对服务端的保护,我们需要从访问控制、通信加密和权限最小化三个维度进行加固。
|
||||
|
||||
为了加强对服务端的保护,Docker 的 REST API (客户端用来跟服务端通信) 在 0.5.2 之后使用本地的 Unix 套接字机制替代了原先绑定在 127.0.0.1 上的 TCP 套接字,因为后者容易遭受跨站脚本攻击。现在用户使用 Unix 权限检查来加强套接字的访问安全。
|
||||
### 18.3.1 限制 API 访问
|
||||
|
||||
用户仍可以利用 HTTP 提供 REST API 访问。建议使用安全机制,确保只有可信的网络或 VPN,或证书保护机制 (例如受保护的 stunnel 和 ssl 认证) 下的访问可以进行。此外,还可以使用 [HTTPS 和证书](https://docs.docker.com/engine/security/https/)来加强保护。
|
||||
Docker 客户端(`docker` 命令)通过 REST API 与守护进程进行通信。
|
||||
|
||||
最近改进的 Linux 命名空间机制将可以实现使用非 root 用户来运行全功能的容器。这将从根本上解决了容器和主机之间共享文件系统而引起的安全问题。
|
||||
在早期版本中,Docker 有时会绑定在 `127.0.0.1` 的 TCP 套接字上,但这容易遭遇跨站脚本(跨协议)攻击。现在的发行版默认使用 Unix Domain Socket(`/var/run/docker.sock`)并依赖文件系统的权限控制。
|
||||
|
||||
终极目标是改进 2 个重要的安全特性:
|
||||
#### 原则 1:决不可将无认证的 TCP 端口暴露在公网
|
||||
|
||||
* 将容器的 root 用户[映射到本地主机上的非 root 用户](https://docs.docker.com/engine/security/userns-remap/),减轻容器和主机之间因权限提升而引起的安全问题;
|
||||
* 允许 Docker 服务端在[非 root 权限 (rootless 模式)](https://docs.docker.com/engine/security/rootless/) 下运行,利用安全可靠的子进程来代理执行需要特权权限的操作。这些子进程将只允许在限定范围内进行操作,例如仅仅负责虚拟网络设定或文件系统管理、配置操作等。
|
||||
这是最常见的 Docker 被入侵抓去挖矿的原因!绝不能在没有任何安全控制的情况下强行开启 `-H tcp://0.0.0.0:2375`。
|
||||
|
||||
最后,建议采用专用的服务器来运行 Docker 和相关的管理服务 (例如管理服务比如 ssh 监控和进程监控、管理工具 nrpe、collectd 等)。其它的业务服务都放到容器中去运行。
|
||||
如果业务确实需要远程访问 Docker 守护进程,**必须启用 TLS 认证机制**,让客户端和服务端互相进行证书校验。
|
||||
|
||||
#### 开启 TLS 认证双向加密
|
||||
|
||||
利用安全机制,确保只有经过授权的主机网络,并在强证书保护下进行通信:
|
||||
|
||||
1. 首先使用 `openssl` 或基于本地 CA 工具生成一套客户端与服务器的证书。
|
||||
2. 配置 Docker 守护进程(通常是 `daemon.json` 或 `dockerd` 启动参数),指定证书路径:
|
||||
|
||||
```bash
|
||||
dockerd \
|
||||
--tlsverify \
|
||||
--tlscacert=ca.pem \
|
||||
--tlscert=server-cert.pem \
|
||||
--tlskey=server-key.pem \
|
||||
-H=0.0.0.0:2376
|
||||
```
|
||||
|
||||
3. 客户端想要连接时,也必须出示客户端证书。
|
||||
|
||||
> [!TIP]
|
||||
> 配置 TLS 生成证书的完整步骤可以查阅 [Docker 官方 TLS 文档](https://docs.docker.com/engine/security/protect-access/)。在现代编排系统(如 Kubernetes)中,通常会有自动化方案管理这些凭据。
|
||||
|
||||
### 18.3.2 保护本地 Socket 访问
|
||||
|
||||
哪怕不开启网络端口,本地的 `/var/run/docker.sock` 也需要谨慎对待。
|
||||
|
||||
任何被授予该 Socket 读写权限的用户(通常被加入 `docker` 用户组),等同于拥有了对宿主机的零成本提权途径,即“无需密码的免密 `sudo` 权限”。
|
||||
|
||||
> [!CAUTION]
|
||||
> 永远不要将不可信的普通用户加入到 `docker` 用户组中。同样,在容器编排时尽量避免将宿主机的 `/var/run/docker.sock` 直接映射给普通容器使用,这种模式被称为 Docker-in-Docker (DinD) 或 Docker-out-of-Docker (DooD),存在极高的越权风险。
|
||||
|
||||
### 18.3.3 Rootless 模式 (非特权运行)
|
||||
|
||||
为了从根本上解决“拥有 Docker socket 就是 root”的问题,Docker 在近年推出了 **Rootless 模式**。
|
||||
|
||||
Rootless 模式允许在完全局限于非 `root` 用户的环境中运行 Docker 守护进程(`dockerd`)和容器。该模式利用了现代 Linux 内核的 User Namespace 技术和非特权网络命名空间实现。
|
||||
|
||||
#### 配置运行 Rootless Docker
|
||||
|
||||
要在非 root 环境中运行 Docker,只需要简单几步:
|
||||
|
||||
1. 安装必要的依赖(通常是 `uidmap` 工具包以便系统支持 `newuidmap` 和 `newgidmap`):
|
||||
```bash
|
||||
$ sudo apt-get install uidmap
|
||||
```
|
||||
|
||||
2. 切换到一个没有任何 `sudo` 权限的普通用户(假设用户名为 `testuser`):
|
||||
```bash
|
||||
$ su - testuser
|
||||
```
|
||||
|
||||
3. 运行 Docker 官方提供的 Rootless 安装脚本:
|
||||
```bash
|
||||
$ curl -fsSL https://get.docker.com/rootless | sh
|
||||
```
|
||||
|
||||
4. 配置环境变量指向新创建的私有 socket:
|
||||
```bash
|
||||
$ export DOCKER_HOST=unix:///run/user/1000/docker.sock
|
||||
$ docker version
|
||||
```
|
||||
|
||||
安装并暴露相应的配置后,该用户的环境将能独立启动属于他自己的 Docker Daemon。即使由于某些未知 0-Day 漏洞使得攻击者突破了容器,他们也只会受限于 `testuser` 这个非特权用户所在的有限系统环境内。
|
||||
|
||||
### 18.3.4 结语
|
||||
|
||||
保障 Docker 服务端的安全主要是做减法:关闭不必要的网络监听点,严管 Socket 访问权限。而一旦基础系统条件允许,**毫不犹豫地在生产环境启用 Rootless 模式**将是一项划算的安全加固选择。
|
||||
|
||||
@@ -1,30 +1,63 @@
|
||||
## 18.4 内核能力机制
|
||||
|
||||
Docker 利用 Linux 的能力机制 (Capabilities) 来限制容器的权限,从而提高系统的安全性。
|
||||
传统 Linux 的权限模型非常粗放:进程分为“特权进程”(以 root 用户 `UID 0` 运行)和“非特权进程”(其他 UID 运行)。这带来了一个致命问题——只要一个后台服务需要一个微小的特权(例如绑定低于 1024 的端口),就必须被赋予所有的 root 权限。一旦该服务被攻陷,系统便会全面沦陷。
|
||||
|
||||
[能力机制 (Capability)](https://man7.org/linux/man-pages/man7/capabilities.7.html) 是 Linux 内核一个强大的特性,可以提供细粒度的权限访问控制。
|
||||
Linux 内核自 2.2 版本起就支持能力机制,它将权限划分为更加细粒度的操作能力,既可以作用在进程上,也可以作用在文件上。
|
||||
为了解决这一问题,Linux 引入了 **能力机制(Capabilities)**。它将传统的全能 root 权限划分为几十个细粒度的操作能力。
|
||||
|
||||
例如,一个 Web 服务进程只需要绑定一个低于 1024 的端口的权限,并不需要 root 权限。那么它只需要被授权 `net_bind_service` 能力即可。此外,还有很多其他的类似能力来避免进程获取 root 权限。
|
||||
### 18.4.1 容器内置的 Capability 白名单
|
||||
|
||||
默认情况下,Docker 启动的容器被严格限制只允许使用内核的一部分能力。
|
||||
在默认情况下,即便一个容器是在以 `root` 用户运行,Docker 也只为其内核授予了所有可用能力中的**一小部分“白名单”能力**。
|
||||
|
||||
使用能力机制对加强 Docker 容器的安全有很多好处。通常,在服务器上会运行一堆需要特权权限的进程,包括有 ssh、cron、syslogd、硬件管理工具模块 (例如负载模块)、网络配置工具等等。容器跟这些进程是不同的,因为几乎所有的特权进程都由容器以外的支持系统来进行管理。
|
||||
常见的 Linux Capabilities 包含:
|
||||
- `CAP_CHOWN`: 修改文件所有者。
|
||||
- `CAP_NET_BIND_SERVICE`: 绑定特权端口(即 1024 以下的端口)。
|
||||
- `CAP_NET_ADMIN`: 网络管理的最高权限(例如调整路由配置,设置防火墙规则等)。
|
||||
- `CAP_SYS_ADMIN`: 被誉为“Linux 内核的特权网管”,允许各种高危操作(挂载磁盘、访问敏感设备等)。
|
||||
|
||||
* ssh 访问被主机上 ssh 服务来管理;
|
||||
* cron 通常应该作为用户进程执行,权限交给使用它服务的应用来处理;
|
||||
* 日志系统可由 Docker 或第三方服务管理;
|
||||
* 硬件管理无关紧要,容器中也就无需执行 udevd 以及类似服务;
|
||||
* 网络管理也都在主机上设置,除非特殊需求,容器不需要对网络进行配置。
|
||||
为了在**“最小特权原则”**的指导下加强安全,Docker 默认**移除了**大量可能导致容器大范围破坏宿主机的能力,例如:
|
||||
* 完全禁止了任何通过 `CAP_SYS_ADMIN` 进行的核心挂载或设备操作。
|
||||
* 禁止修改内核模块。
|
||||
* 禁止直接访问硬件套接字。
|
||||
|
||||
从上面的例子可以看出,大部分情况下,容器并不需要 “真正的” root 权限,容器只需要少数的能力即可。为了加强安全,容器可以禁用一些没必要的权限。
|
||||
这种“非完整”的 root 用户能保证大部分应用在拥有其所需权限的同时,把恶意行为对系统的影响降到最低。
|
||||
|
||||
* 完全禁止任何 mount 操作;
|
||||
* 禁止直接访问本地主机的套接字;
|
||||
* 禁止访问一些文件系统的操作,比如创建新的设备、修改文件属性等;
|
||||
* 禁止模块加载。
|
||||
### 18.4.2 实战:添加与剥夺能力
|
||||
|
||||
这样,就算攻击者在容器中取得了 root 权限,也不能获得本地主机的较高权限,能进行的破坏也有限。
|
||||
当启动一个 Docker 容器时,我们可以利用 `--cap-add`(增加特权)和 `--cap-drop`(剥夺特权)两个参数精细地控制进程环境。
|
||||
|
||||
默认情况下,Docker 采用[白名单](https://github.com/moby/moby/blob/master/oci/caps/defaults.go)机制,禁用必需功能之外的其它权限。
|
||||
当然,用户也可以根据自身需求来为 Docker 容器启用额外的权限。
|
||||
#### 实战场景一:构建极限安全的 Web 靶机
|
||||
|
||||
假设你正在提供一个公共的 Web 容器。你不希望里面的任何恶意脚本修改进程权限或者创建设备节点,你可以通过命令先移除**所有**默认能力,然后再按需授权该守护进程一个仅仅能绑端口的能力。
|
||||
|
||||
```bash
|
||||
$ docker run -d \
|
||||
--name max_secure_web \
|
||||
--cap-drop ALL \
|
||||
--cap-add NET_BIND_SERVICE \
|
||||
nginx:alpine
|
||||
```
|
||||
|
||||
这里的 `--cap-drop ALL` 是实现“特权最小化”的最强杀手锏。此时,即便某黑客利用 0-Day 手段拿到了 Web 服务的容器 root Shell,当他试图改变任何不属于他自己的进程配置或者所有权时,系统都会报错拒绝访问。
|
||||
|
||||
#### 实战场景二:需要捕获网络数据包的网络实验
|
||||
|
||||
假设容器内的主程序是一个网络嗅探器(如 tshark 或 tcpdump),这显然不在 Docker 提供的默认白名单之内,因为该程序试图直接操纵底层网卡流量,会触发 Permission Denied。
|
||||
|
||||
此时,我们需要给它适当补发缺失的部分核心管理能力:
|
||||
|
||||
```bash
|
||||
$ docker run -it --rm \
|
||||
--name network_sniffer \
|
||||
--cap-add NET_ADMIN \
|
||||
--cap-add NET_RAW \
|
||||
tshark-image /bin/bash
|
||||
```
|
||||
|
||||
我们只授予了所需的网络管理控制(NET_ADMIN)和侦听底层套接字的权限(NET_RAW),而免去了赋予整个容器终极杀器 `--privileged` 参数。
|
||||
|
||||
> [!WARNING]
|
||||
> 大量开发人员遇到了“权限遭到拒绝”的错误时,往往习惯性图省事添加 `--privileged` 这个核选项。但这将把**宿主机上一切特权和所有访问设备完全投射给容器内的根用户**,其危险性等价于根本没有做隔离!请务必查明进程出错的实际原因,精准施加必要的隔离 `CAP_*` 能力。
|
||||
|
||||
### 18.4.3 总结
|
||||
|
||||
利用能力机制(Capabilities)是进行精细化系统级访问控制的关键一环。遵循“**白名单剥夺一切不必要权利(--cap-drop ALL)**”的极端配置并不过分,这将使得即便程序本身漏洞百出,攻击面也被死死压缩在一个几乎毫无后续伸展潜力的受限维度中。
|
||||
|
||||
@@ -1,13 +1,88 @@
|
||||
## 18.5 其它安全特性
|
||||
|
||||
除了上述机制,Linux 内核还提供了一系列安全增强功能,可以进一步保护容器环境。
|
||||
除了上述的命名空间、控制组以及能力机制,Linux 内核与云原生生态还提供了大量安全增强功能,它们共同筑成了一道防御纵深的“马奇诺防线”。本节主要介绍强制访问控制、系统调用拦截以及自动化的容器漏洞扫描技术。
|
||||
|
||||
除了能力机制之外,还可以利用一些现有的安全机制来增强使用 Docker 的安全性,例如 TOMOYO,AppArmor,Seccomp,SELinux,GRSEC 等。
|
||||
### 18.5.1 系统调用过滤
|
||||
|
||||
Docker 当前默认只启用了能力机制。用户可以采用多种方案来加强 Docker 主机的安全,例如:
|
||||
`Seccomp`(Secure Computing mode)是 Linux 内核的一个安全机制,用于限制进程能够发起的系统调用数量。
|
||||
|
||||
* 在内核中启用 GRSEC 和 PAX,这将增加很多编译和运行时的安全检查;通过地址随机化避免恶意探测等。并且,启用该特性不需要 Docker 进行任何配置。
|
||||
* 使用一些有增强安全特性的容器模板,比如带 AppArmor 的模板和 Redhat 带 SELinux 策略的模板。这些模板提供了额外的安全特性。
|
||||
* 用户可以自定义访问控制机制来定制安全策略。
|
||||
一个普通的 Linux 内核提供了 300 多个系统调用,而一个正常运行的容器化应用(例如 Nginx 服务)通常只会用到几十个调用,这就给攻击者留下了大量的闲置入口点来进行内核层的缓冲区溢出攻击。
|
||||
|
||||
跟其它添加到 Docker 容器的第三方工具一样 (比如网络拓扑和文件系统共享),有很多类似的机制,在不改变 Docker 内核情况下就可以加固现有的容器。
|
||||
Docker 默认启用了 Seccomp 并利用预置的 [默认配置文件](https://github.com/moby/moby/blob/master/profiles/seccomp/default.json) 将可以利用的系统调用缩减到了不足一半(默认禁用了 44 个危险的统调用,比如修改时区或重启系统)。
|
||||
|
||||
如果你对应用的系统调用特征了如指掌,你可以为容器定制专属规则。
|
||||
|
||||
#### 实战:禁用 chmod 系统调用过滤
|
||||
|
||||
首先,编写一个 `no-chmod.json` 的策略文件:
|
||||
```json
|
||||
{
|
||||
"defaultAction": "SCMP_ACT_ALLOW",
|
||||
"syscalls": [
|
||||
{
|
||||
"name": "chmod",
|
||||
"action": "SCMP_ACT_ERRNO"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
在启动时告诉 Docker 载入这套过滤配置:
|
||||
|
||||
```bash
|
||||
$ docker run --rm -it \
|
||||
--security-opt seccomp=no-chmod.json \
|
||||
alpine sh
|
||||
|
||||
/ # chmod 777 /etc/passwd
|
||||
chmod: /etc/passwd: Operation not permitted
|
||||
```
|
||||
应用只要被劫持进行越界尝试,其操作系统层命令便会立刻吃瘪。
|
||||
|
||||
### 18.5.2 强制访问控制 (AppArmor / SELinux)
|
||||
|
||||
传统的 Linux 模型遵循 DAC(自主访问控制),这意味着如果一个文件被赋予了全员读写权限(`777`),普通隔离下任何人便都能修改。但 **MAC(强制访问控制)** 技术,诸如 `AppArmor` (常用于 Ubuntu/Debian) 或 `SELinux` (常用于 CentOS/RHEL),可以制定比“文件所有权”更宏观且优先的策略控制模块。
|
||||
|
||||
在开启了上述机制的机器上:
|
||||
- **AppArmor**: Docker 为所有启动的应用加载了一个默认的 `docker-default` 模板文件,如果你的某些异常写行为(比如往特殊的内核心脏目录写入配置)不在 AppArmor 许可列表之上,即使拥有物理 Root,写入同样失败。
|
||||
- **SELinux**: 所有的 Docker 操作强制附加特殊上下文标识标签。就算把主机的 `/` 绑定给了黑客的某服务,黑客对不属于 Docker 可见的标签的文件进行读写尝试亦会被阻止。
|
||||
|
||||
如果想为某些受信任应用施加特定的外部强化文件策略,可以通过如下方法指派规则表:
|
||||
|
||||
```bash
|
||||
$ docker run --rm -it \
|
||||
--security-opt apparmor=custom-nginx-profile \
|
||||
nginx
|
||||
```
|
||||
|
||||
### 18.5.3 容器镜像漏洞静态扫描
|
||||
|
||||
现代防护的防御已经不仅仅在运行阶段,而向“左”延伸至了构建与分发时期控制。很多安全隐患并不是用户代码中的直接逻辑异常,而是打包环境或者引入库的基础 `APT` 安装层面潜伏了开源界众所周知的历史漏洞。
|
||||
|
||||
#### 使用 Trivy 识别风险
|
||||
|
||||
[Trivy](https://github.com/aquasecurity/trivy) 是由 Aqua Security 发行的一款针对容器技术的快速镜像漏洞扫描利器。在分发应用前通过它的扫描可以规避绝大多数风险。
|
||||
|
||||
```bash
|
||||
## 如果使用本地命令行扫描容器镜像
|
||||
$ trivy image alpine:3.10
|
||||
|
||||
2024-03-01T10:05:07.124Z INFO Number of language-specific files: 1
|
||||
2024-03-01T10:05:07.124Z INFO Detecting vulnerabilities...
|
||||
|
||||
alpine:3.10 (alpine 3.10.3)
|
||||
===========================
|
||||
Total: 2 (UNKNOWN: 0, LOW: 0, MEDIUM: 1, HIGH: 1, CRITICAL: 0)
|
||||
|
||||
+---------+------------------+----------+-------------------+---------------+---------------------------------------+
|
||||
| LIBRARY | VULNERABILITY ID | SEVERITY | INSTALLED VERSION | FIXED VERSION | TITLE |
|
||||
+---------+------------------+----------+-------------------+---------------+---------------------------------------+
|
||||
| busybox | CVE-2022-28391 | HIGH | 1.30.1-r3 | 1.30.1-r4 | busybox: out-of-bounds read in... |
|
||||
...
|
||||
```
|
||||
|
||||
只要确保所有上传给私有或公共仓库分发服务的产物先被引入至 CI/CD 流水线,如果出现 `HIGH` 及 `CRITICAL` 严重的报错记录强行阻拦部署,这本身便是构建环节极其有力的自动化安全大门保障。除 Trivy 外,最新的 Docker 版本也已内置支持官方扫描利刃 **Docker Scout**。
|
||||
|
||||
### 18.5.4 容器核心层基石结语
|
||||
|
||||
到这里,Docker 为保障宿主和容器界限安全的几个护城河:从**资源剥离限制** (`Cgroups`) 到**进程/网络/身份蒙蔽** (`Namespace`)、到**特权能力回收** (`Capabilities`) 再到**内核强制策略拦截管制** (`Seccomp`/`AppArmor`) 已悉数交代完毕。虽然绝没有“100% 免疫网络穿刺的防线”,只要开发者牢记 **权限最小化原则** ,容器的堡垒就可以做到令攻击者望洋兴叹。
|
||||
|
||||
@@ -391,4 +391,4 @@ $ cosign verify --key cosign.pub $IMAGE
|
||||
|
||||
- [命名空间](../12_implementation/12.2_namespace.md):隔离机制详解
|
||||
- [控制组](../12_implementation/12.3_cgroups.md):资源限制详解
|
||||
- [最佳实践](../appendix/20.1_best_practices.md):Dockerfile 安全配置
|
||||
- [最佳实践](../appendix/best_practices.md):Dockerfile 安全配置
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
## 18.6 总结
|
||||
## 本章小结
|
||||
|
||||
Docker 的安全性依赖于多层隔离机制的协同工作,同时需要用户遵循最佳实践。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user