vault backup: 2026-11-07 16:33:44
This commit is contained in:
@@ -0,0 +1,426 @@
|
||||
很多人对 Docker 有个误解,觉得容器就是轻量级虚拟机。这个想法很危险。虚拟机有自己独立的内核,而容器是共享宿主机内核的,区别大了去了。
|
||||
|
||||
Docker 的隔离主要靠两样东西:**[Namespace](https://zhida.zhihu.com/search?content_id=784190073&content_type=Answer&match_order=1&q=Namespace&zhida_source=entity)** 和 **[Cgroup](https://zhida.zhihu.com/search?content_id=784190073&content_type=Answer&match_order=1&q=Cgroup&zhida_source=entity)**。
|
||||
|
||||
Namespace 做的是资源视角的隔离,让容器里的进程觉得自己是系统唯一的主人。Linux 目前提供了 6 个主要的 Namespace:
|
||||
|
||||
- **PID Namespace**:容器里的进程看不到宿主机和其他容器的进程
|
||||
- **Mount Namespace**:容器有自己独立的文件系统挂载点
|
||||
- **Network Namespace**:容器有独立的网络栈、IP地址、端口
|
||||
- **IPC Namespace**:进程间通信隔离
|
||||
- **UTS Namespace**:主机名隔离
|
||||
- **[User Namespace](https://zhida.zhihu.com/search?content_id=784190073&content_type=Answer&match_order=1&q=User+Namespace&zhida_source=entity)**:用户和用户组隔离
|
||||
|
||||
Cgroup 做的是资源使用的限制,防止一个容器把宿主机的 CPU、内存、磁盘 IO 全吃光。
|
||||
|
||||
听着挺完善的对吧?但问题在于,这些隔离都是"软隔离",不是"硬隔离"。共享内核意味着,一旦内核有漏洞,或者你的配置有疏漏,容器里的恶意代码就有可能突破这些限制,逃到宿主机上。
|
||||
|
||||
我之前看过一个案例,某在线编程平台因为配置不当,用户通过 Python 的 `os.system()` 直接在宿主机上执行了命令,把整台服务器搞崩了。所以,做代码沙箱,安全这根弦必须绷紧。
|
||||
|
||||
## **第一道锁:资源限制,别让恶意代码把机器吃干抹净**
|
||||
|
||||
做代码沙箱,最基本的就是限制资源。你想想,要是用户提交一个死循环,或者疯狂 fork 进程的 fork 炸弹,不限制的话宿主机直接就废了。
|
||||
|
||||
我一般会在 `docker run` 的时候加上这些参数:
|
||||
|
||||
```bash
|
||||
docker run \
|
||||
--memory="512m" \
|
||||
--memory-swap="512m" \
|
||||
--cpus="1" \
|
||||
--pids-limit="50" \
|
||||
--cpu-shares=512 \
|
||||
sandbox-runner
|
||||
```
|
||||
|
||||
逐个解释下:
|
||||
|
||||
- `--memory="512m"`:限制容器最多使用 512MB 内存,超过就直接 OOM Kill,不给任何商量余地
|
||||
- `--memory-swap="512m"`:这个和 memory 设成一样的值,意思就是禁用 swap。别小看这个,有些恶意代码会利用 swap 来绕过内存限制
|
||||
- `--cpus="1"`:最多使用 1 个 CPU 核心的算力
|
||||
- `--pids-limit="50"`:限制容器内最多 50 个进程。这个是防 fork 炸弹的关键,`:(){ :|:& };:` 这种东西直接就废了
|
||||
- `--cpu-shares=512`:CPU 时间的权重分配,多个容器竞争 CPU 时按这个比例来
|
||||
|
||||
还有一点容易忽略的,磁盘 IO 也得限制:
|
||||
|
||||
```bash
|
||||
# 创建限速的 cgroup
|
||||
mkdir /sys/fs/cgroup/blkio/sandbox
|
||||
echo "8:0 1048576" > /sys/fs/cgroup/blkio/sandbox/blkio.throttle.read_bps_device
|
||||
```
|
||||
|
||||
不过说实话,磁盘 IO 限制在实际操作中比较麻烦,我更倾向于直接把容器的文件系统设成只读,后面会讲。
|
||||
|
||||
## **第二道锁:Capability 裁剪,别给容器不需要的权限**
|
||||
|
||||
Linux 把 root 的权限拆分成了几十个 Capability,Docker 默认只保留了容器运行所需的一小部分。但即便如此,默认配置下容器还是有一些不必要的权限。
|
||||
|
||||
你可以用这个命令看看默认情况下容器有哪些 Capability:
|
||||
|
||||
```bash
|
||||
docker run --rm alpine capsh --print
|
||||
```
|
||||
|
||||
我这边输出大概有这些:`CAP_CHOWN`, `CAP_DAC_OVERRIDE`, `CAP_FSETID`, `CAP_FOWNER`, `CAP_MKNOD`, `CAP_NET_RAW`, `CAP_SETGID`, `CAP_SETUID`, `CAP_SETFCAP`, `CAP_SETPCAP`, `CAP_NET_BIND_SERVICE`, `CAP_SYS_CHROOT`, `CAP_KILL`, `CAP_AUDIT_WRITE`。
|
||||
|
||||
对于代码沙箱来说,这里面很多都是多余的。我的做法是把所有 Capability 都去掉,只加回必须的:
|
||||
|
||||
```bash
|
||||
docker run \
|
||||
--cap-drop=ALL \
|
||||
--cap-add=NET_BIND_SERVICE \
|
||||
sandbox-runner
|
||||
```
|
||||
|
||||
`--cap-drop=ALL` 先把所有权限都干掉,然后 `--cap-add=NET_BIND_SERVICE` 只加回绑定 1024 以下端口的权限(如果你的沙箱不需要网络,这个也省了)。
|
||||
|
||||
千万别用 `--privileged` 参数!这个参数等于把所有安全机制全关了,容器里的 root 和宿主机的 root 基本没区别。我见过有人在生产环境用 `--privileged` 跑容器,被领导骂了三天。
|
||||
|
||||
## **第三道锁:[Seccomp](https://zhida.zhihu.com/search?content_id=784190073&content_type=Answer&match_order=1&q=Seccomp&zhida_source=entity) 系统调用过滤,把攻击面缩到最小**
|
||||
|
||||
Seccomp 是我最喜欢的一个安全机制。它可以在内核层面限制容器进程能调用哪些系统调用,相当于给容器加了一层"系统调用防火墙"。
|
||||
|
||||
Docker 默认就启用了 Seccomp,使用一个白名单配置文件,默认会屏蔽大约 44 个危险或不常用的系统调用。你可以通过 `docker info` 确认:
|
||||
|
||||
```text
|
||||
Security Options:
|
||||
seccomp
|
||||
Profile: default
|
||||
```
|
||||
|
||||
但是,默认配置对于代码沙箱来说还是太宽松了。我一般会写一个自定义的 Seccomp Profile,只允许代码执行真正需要的系统调用。
|
||||
|
||||
举个实际的例子,如果沙箱只用来运行 Python 代码,我可以通过 `strace` 先抓取 Python 运行时需要哪些系统调用:
|
||||
|
||||
```bash
|
||||
strace -c -f python3 /tmp/test.py 2>&1 | head -50
|
||||
```
|
||||
|
||||
然后根据抓取结果编写 Seccomp Profile。一个精简版的 Profile 大概长这样:
|
||||
|
||||
```json
|
||||
{
|
||||
"defaultAction": "SCMP_ACT_ERRNO",
|
||||
"architectures": ["SCMP_ARCH_X86_64"],
|
||||
"syscalls": [
|
||||
{
|
||||
"names": [
|
||||
"read", "write", "open", "close", "fstat", "lseek",
|
||||
"mmap", "mprotect", "munmap", "brk", "rt_sigaction",
|
||||
"rt_sigprocmask", "ioctl", "access", "pipe", "select",
|
||||
"poll", "mremap", "nanosleep", "clock_gettime",
|
||||
"clone", "fork", "vfork", "execve", "exit", "wait4",
|
||||
"uname", "fcntl", "flock", "fsync", "dup", "dup2",
|
||||
"getpid", "getppid", "getuid", "getgid", "geteuid",
|
||||
"getegid", "getrlimit", "gettimeofday", "arch_prctl",
|
||||
"set_tid_address", "set_robust_list", "futex",
|
||||
"clock_getres", "clock_nanosleep"
|
||||
],
|
||||
"action": "SCMP_ACT_ALLOW"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
关键点在于 `defaultAction` 设成了 `SCMP_ACT_ERRNO`,意思就是默认拒绝所有系统调用,只在白名单里的才放行。这比默认配置安全得多。
|
||||
|
||||
使用自定义 Profile 启动容器:
|
||||
|
||||
```bash
|
||||
docker run \
|
||||
--security-opt seccomp=/path/to/seccomp-profile.json \
|
||||
sandbox-runner
|
||||
```
|
||||
|
||||
有个真实的案例很有说服力:CVE-2017-16995 这个内核漏洞,攻击者可以通过 bpf 系统调用实现提权。但 Docker 默认的 Seccomp 配置屏蔽了 bpf 系统调用,所以这个漏洞在 Docker 容器里根本没法利用。这就是 Seccomp 的价值。
|
||||
|
||||
不过写 Seccomp Profile 是个苦力活,不同语言运行时需要的系统调用不一样,你得一个一个测。我建议先用 `SCMP_ACT_LOG` 模式跑一段时间收集日志,确认没问题再切换到 `SCMP_ACT_ERRNO`。
|
||||
|
||||
## **第四道锁:[AppArmor](https://zhida.zhihu.com/search?content_id=784190073&content_type=Answer&match_order=1&q=AppArmor&zhida_source=entity) / SELinux,强制访问控制再加一道门**
|
||||
|
||||
Seccomp 管的是系统调用,AppArmor 管的是资源访问。它可以在文件读写、网络访问、Capability 等层面做更细粒度的限制。
|
||||
|
||||
Ubuntu 默认使用 AppArmor,CentOS/RHEL 默认使用 SELinux。Docker 在 Ubuntu 上默认会给容器加载一个 AppArmor Profile,叫做 `docker-default`。
|
||||
|
||||
对于代码沙箱,我一般会写一个更严格的自定义 AppArmor Profile:
|
||||
|
||||
```text
|
||||
#include <tunables/global>
|
||||
|
||||
profile sandbox-profile flags=(attach_disconnected,mediate_deleted) {
|
||||
#include <abstractions/base>
|
||||
|
||||
# 只允许读 /usr 和 /lib
|
||||
/usr/** r,
|
||||
/lib/** r,
|
||||
/lib64/** r,
|
||||
|
||||
# 允许写临时目录
|
||||
/tmp/** rw,
|
||||
/dev/null rw,
|
||||
/dev/urandom r,
|
||||
|
||||
# 禁止网络访问(除了已建立的连接)
|
||||
deny network,
|
||||
deny network inet,
|
||||
deny network inet6,
|
||||
|
||||
# 禁止 ptrace(防止调试其他进程)
|
||||
deny ptrace,
|
||||
|
||||
# 禁止 mount
|
||||
deny mount,
|
||||
|
||||
# 禁止加载内核模块
|
||||
deny /sys/module/** rw,
|
||||
}
|
||||
```
|
||||
|
||||
加载并使用这个 Profile:
|
||||
|
||||
```bash
|
||||
# 编译并加载 Profile
|
||||
apparmor_parser -r /etc/apparmor.d/sandbox-profile
|
||||
|
||||
# 使用 Profile 启动容器
|
||||
docker run \
|
||||
--security-opt apparmor=sandbox-profile \
|
||||
sandbox-runner
|
||||
```
|
||||
|
||||
AppArmor 还有个好处是它的日志很好用。当容器进程尝试访问被禁止的资源时,`/var/log/syslog` 或 `/var/log/audit/audit.log` 里会有详细记录,方便你排查问题。
|
||||
|
||||
如果你的系统是 CentOS,那就用 SELinux,思路是一样的,只是配置方式不同。SELinux 用标签和策略来管理访问控制,比 AppArmor 更严格但也更复杂。不过对于代码沙箱场景,AppArmor 够用了。
|
||||
|
||||
## **第五道锁:User Namespace,让容器里的 root 变成宿主机的 nobody**
|
||||
|
||||
这是个很容易被忽略但非常重要的安全机制。默认情况下,Docker 容器里的 root 用户(UID 0)和宿主机的 root 用户在文件权限上是等价的。也就是说,一旦容器逃逸,攻击者拿到的是宿主机的 root 权限,想想都后怕。
|
||||
|
||||
User Namespace 的作用是把容器内的 UID 映射到宿主机的非特权 UID。比如容器内的 root(UID 0)在宿主机上实际是 UID 100000,这样就算逃逸了,攻击者在宿主机上也没有任何特权。
|
||||
|
||||
Docker 启用 User Namespace 的方式:
|
||||
|
||||
```bash
|
||||
# 先在 /etc/docker/daemon.json 中配置
|
||||
{
|
||||
"userns-remap": "default"
|
||||
}
|
||||
|
||||
# 重启 Docker
|
||||
systemctl restart docker
|
||||
```
|
||||
|
||||
Docker 会自动创建一个叫 `dockremap` 的用户,并配置 subuid 和 subgid 映射。你可以验证一下:
|
||||
|
||||
```bash
|
||||
cat /etc/subuid
|
||||
# dockremap:100000:65536
|
||||
|
||||
cat /etc/subgid
|
||||
# dockremap:100000:65536
|
||||
```
|
||||
|
||||
这意味着容器内的 UID 0-65535 会映射到宿主机的 UID 100000-165535。
|
||||
|
||||
启用 User Namespace 之后有个副作用:一些需要特权的操作(比如挂载 NFS、使用 `--net=host`)就不能用了。但对于代码沙箱来说,这些功能本来就不需要,所以影响不大。
|
||||
|
||||
## **第六道锁:只读文件系统 + 临时存储,不给恶意代码留后门**
|
||||
|
||||
代码沙箱最怕的就是恶意代码往文件系统里写东西,比如写个后门脚本、改个配置文件什么的。我的做法是把容器的根文件系统设成只读:
|
||||
|
||||
```bash
|
||||
docker run \
|
||||
--read-only \
|
||||
--tmpfs /tmp:size=100m,mode=1777 \
|
||||
--tmpfs /run:size=10m,mode=0755 \
|
||||
sandbox-runner
|
||||
```
|
||||
|
||||
`--read-only` 把整个根文件系统挂载为只读,`--tmpfs` 给需要写入的目录单独挂载内存文件系统。`/tmp` 给 100MB,`/run` 给 10MB,够用了。
|
||||
|
||||
有些运行时还需要 `/dev/shm`,也可以加上:
|
||||
|
||||
```bash
|
||||
--tmpfs /dev/shm:size=64m,mode=1777
|
||||
```
|
||||
|
||||
这样恶意代码就算想写东西,也只能写到 `/tmp` 里,而且容器一销毁就没了,啥也留不下。
|
||||
|
||||
还有一点,别把宿主机的目录挂载到容器里,尤其是敏感目录。我见过有人把 `/var/run/docker.sock` 挂进容器的,这等于把宿主机的 Docker 控制权拱手让人,攻击者可以直接创建一个特权容器然后逃逸。
|
||||
|
||||
## **第七道锁:网络隔离,让恶意代码连不上外网**
|
||||
|
||||
代码沙箱里的程序一般不需要网络访问。就算需要,也应该严格限制。
|
||||
|
||||
最简单粗暴的方式就是直接禁用网络:
|
||||
|
||||
```bash
|
||||
docker run \
|
||||
--network=none \
|
||||
sandbox-runner
|
||||
```
|
||||
|
||||
这样容器里连 `ping` 都用不了,恶意代码想外传数据?没门。
|
||||
|
||||
如果你的沙箱确实需要网络(比如代码里要请求某个 API),那可以用自定义网络 + iptables 规则来做白名单:
|
||||
|
||||
```bash
|
||||
# 创建自定义网络
|
||||
docker network create --driver bridge sandbox-net
|
||||
|
||||
# 启动容器
|
||||
docker run \
|
||||
--network=sandbox-net \
|
||||
sandbox-runner
|
||||
|
||||
# 在宿主机上用 iptables 限制容器只能访问特定 IP
|
||||
iptables -I FORWARD -s 172.20.0.0/16 -d 允许的IP -j ACCEPT
|
||||
iptables -I FORWARD -s 172.20.0.0/16 -j DROP
|
||||
```
|
||||
|
||||
这样容器只能访问你允许的 IP,其他一概不通。
|
||||
|
||||
## **第八道锁:超时机制,别让代码跑到天荒地老**
|
||||
|
||||
恶意代码不一定要搞破坏,它也可以就是跑个死循环,占着你的计算资源不放。所以超时机制必须有。
|
||||
|
||||
我一般这样处理:
|
||||
|
||||
```bash
|
||||
timeout 10 docker run \
|
||||
--rm \
|
||||
--memory="512m" \
|
||||
--cpus="1" \
|
||||
sandbox-runner
|
||||
```
|
||||
|
||||
`timeout 10` 表示 10 秒后强制终止。`--rm` 表示容器结束后自动删除,不留痕迹。
|
||||
|
||||
但在生产环境中,我更倾向于在代码层面控制超时,因为 `timeout` 命令发的是 SIGTERM,有些进程可能捕获信号后不退出。更可靠的做法是在容器内部用进程管理器来控制:
|
||||
|
||||
```python
|
||||
import subprocess
|
||||
import signal
|
||||
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["python3", "/tmp/user_code.py"],
|
||||
timeout=10,
|
||||
capture_output=True,
|
||||
preexec_fn=lambda: signal.signal(signal.SIGALRM, lambda *_: (_ for _ in ()).throw(TimeoutError()))
|
||||
)
|
||||
except subprocess.TimeoutExpired:
|
||||
# 超时处理
|
||||
pass
|
||||
```
|
||||
|
||||
## **终极大招:gVisor / [Kata Containers](https://zhida.zhihu.com/search?content_id=784190073&content_type=Answer&match_order=1&q=Kata+Containers&zhida_source=entity),真正的沙箱容器**
|
||||
|
||||
前面说的那些加固措施,都是基于传统 Docker 容器的,本质上还是共享内核。如果你的安全要求特别高(比如多租户环境、运行完全不可信的代码),那可以考虑使用沙箱容器运行时。
|
||||
|
||||
**gVisor** 是 Google 开源的一个项目,它实现了一个用户态内核叫 Sentry,拦截容器进程的所有系统调用,在用户态处理后再转发给宿主机内核。这样容器进程和宿主机内核之间就多了一层防护,攻击面大幅缩小。
|
||||
|
||||
gVisor 和宿主机内核的通信只用了不到 20 个系统调用,而传统容器可以用 300 多个。这差距,安全程度可想而知。
|
||||
|
||||
安装和使用 gVisor 也比较简单:
|
||||
|
||||
```bash
|
||||
# 安装 runsc
|
||||
curl -fsSL https://gvisor.dev/archive.key | sudo gpg --dearmor -o /usr/share/keyrings/gvisor-archive-keyring.gpg
|
||||
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/gvisor-archive-keyring.gpg] https://storage.googleapis.com/gvisor/releases release main" | sudo tee /etc/apt/sources.list.d/gvisor.list
|
||||
sudo apt-get update && sudo apt-get install runsc
|
||||
|
||||
# 配置 Docker 使用 runsc
|
||||
# 在 /etc/docker/daemon.json 中添加
|
||||
{
|
||||
"runtimes": {
|
||||
"runsc": {
|
||||
"path": "/usr/bin/runsc"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# 重启 Docker
|
||||
systemctl restart docker
|
||||
|
||||
# 使用 gVisor 运行容器
|
||||
docker run --runtime=runsc sandbox-runner
|
||||
```
|
||||
|
||||
gVisor 的缺点也很明显:系统调用开销导致性能有损耗,不适合系统调用密集型的应用;不支持 GPU 直通;没有实现全部的 Linux 系统调用,有些应用可能跑不起来。
|
||||
|
||||
**Kata Containers** 是另一种方案,它本质上是在轻量级虚拟机里跑容器。每个容器(或 Pod)都有自己独立的内核,隔离性接近虚拟机,但使用体验和普通容器一样。
|
||||
|
||||
Kata 的安全边界比 gVisor 更强,但资源开销也更大,每个容器至少需要几十 MB 的额外内存。对于在线编程平台这种需要同时运行成百上千个沙箱的场景,gVisor 可能更合适。
|
||||
|
||||
## **我的最终方案:层层叠加,纵深防御**
|
||||
|
||||
说了这么多,最后晒一下我在生产环境中实际使用的完整配置。我把它封装成了一个脚本:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
docker run \
|
||||
--rm \
|
||||
--runtime=runsc \
|
||||
--network=none \
|
||||
--read-only \
|
||||
--memory="512m" \
|
||||
--memory-swap="512m" \
|
||||
--cpus="1" \
|
||||
--pids-limit="50" \
|
||||
--cap-drop=ALL \
|
||||
--security-opt seccomp=/etc/docker/seccomp/sandbox.json \
|
||||
--security-opt apparmor=sandbox-profile \
|
||||
--tmpfs /tmp:size=100m,mode=1777 \
|
||||
--tmpfs /run:size=10m,mode=0755 \
|
||||
--user=1000:1000 \
|
||||
sandbox-runner \
|
||||
timeout 10 python3 /tmp/user_code.py
|
||||
```
|
||||
|
||||
这里有几个值得注意的点:
|
||||
|
||||
1. `--runtime=runsc`:使用 gVisor 运行时,从内核层面加强隔离
|
||||
2. `--network=none`:完全禁用网络
|
||||
3. `--read-only`:只读文件系统
|
||||
4. `--cap-drop=ALL`:去掉所有 Linux Capabilities
|
||||
5. `--security-opt seccomp=...`:自定义 Seccomp Profile
|
||||
6. `--security-opt apparmor=...`:自定义 AppArmor Profile
|
||||
7. `--user=1000:1000`:以非 root 用户运行
|
||||
8. `timeout 10`:10 秒超时
|
||||
|
||||
这套配置不是一次性就定下来的,是在实际运行中不断调整的。刚开始的时候,因为 Seccomp 太严格导致很多正常代码跑不了,我就把 `SCMP_ACT_ERRNO` 改成 `SCMP_ACT_LOG` 先观察了一周,收集了所有被拒绝的系统调用,确认哪些是必须的后才逐步放开。
|
||||
|
||||
还有个经验:每次更新语言运行时版本后,都要重新测试 Seccomp Profile,因为新版本可能会用到之前不需要的系统调用。我就吃过这个亏,升级 Python 版本后用户代码全报错,排查了半天才发现是 Seccomp 屏蔽了一个新加的系统调用。
|
||||
|
||||
## **一些血的教训**
|
||||
|
||||
最后分享几个我在实战中遇到的真实问题:
|
||||
|
||||
**1. docker.sock 千万别挂进去**
|
||||
|
||||
这个前面提过了,但还是要强调。我见过不止一次,有人为了在容器里操作 Docker,把 `/var/run/docker.sock` 挂进容器。这就等于把宿主机的 root 权限交出去了。
|
||||
|
||||
**2. 别用 latest 标签跑生产环境**
|
||||
|
||||
镜像版本要固定,用 digest 更好。恶意镜像替换这种攻击虽然少见,但不是不可能。
|
||||
|
||||
**3. 定期更新内核**
|
||||
|
||||
Docker 的安全很大程度上依赖内核。脏牛漏洞(CVE-2016-5195)可以直接从容器里逃逸,就是因为内核的问题。保持内核更新是最基本的安全措施。
|
||||
|
||||
**4. CVE-2019-5736 的启示**
|
||||
|
||||
这个漏洞非常经典。攻击者通过覆写宿主机上的 runc 二进制文件,实现了容器逃逸。根本原因是 runc 进入容器命名空间时,`/proc/self/exe` 指向了宿主机的 runc 程序,而容器内的 root 用户可以写入这个文件。这个漏洞的修复方式是在 runc 进入容器前先克隆一份自身的二进制文件。这个案例告诉我们,不要低估攻击者的创造力,即使看起来很小的疏漏也可能被利用。
|
||||
|
||||
**5. 监控和日志不能少**
|
||||
|
||||
再安全的沙箱也需要监控。我会在宿主机上用 auditd 记录所有和容器相关的系统调用,用 Falco 做运行时安全检测。一旦发现异常行为(比如容器进程尝试访问 `/proc` 或 `/sys` 下的敏感文件),立即告警。
|
||||
|
||||
## **总结**
|
||||
|
||||
做 Docker 代码沙箱,没有银弹,只有纵深防御。单靠任何一层安全机制都不够可靠,必须把 Namespace、Cgroup、Capability、Seccomp、AppArmor、User Namespace、只读文件系统、网络隔离这些手段叠加起来,再加上 gVisor 这类沙箱运行时,才能把风险降到可接受的水平。
|
||||
|
||||
记住一个原则:**最小权限**。容器只需要能完成任务的最低权限,多给一点都不行。安全这事儿,宁可过度,不能不足。
|
||||
|
||||
还有就是,安全是一个持续的过程,不是一次性的配置。新的漏洞会不断出现,你的防御措施也要跟着更新。保持关注 Docker 和 Linux 内核的安全公告,及时打补丁,这是最基本的运维素养。
|
||||
Reference in New Issue
Block a user