Podman 完全指南 / 11 - 安全加固
第 11 章 — 安全加固
11.1 容器安全全景
容器安全是一个多层次的防御体系:
┌──────────────────────────────────────────────┐
│ 容器安全分层模型 │
│ │
│ 第 6 层: 供应链安全 │
│ ├── 镜像签名与验证 │
│ ├── 镜像扫描(CVE 检测) │
│ └── 可信基础镜像 │
│ │
│ 第 5 层: 运行时安全 │
│ ├── seccomp 系统调用过滤 │
│ ├── AppArmor / SELinux │
│ └── 运行时异常检测 │
│ │
│ 第 4 层: 内核隔离 │
│ ├── 用户命名空间 (User Namespace) │
│ ├── 网络命名空间 (Network Namespace) │
│ ├── PID 命名空间 │
│ └── cgroup 资源限制 │
│ │
│ 第 3 层: 容器权限 │
│ ├── Linux Capability 管理 │
│ ├── 只读根文件系统 │
│ └── 无特权模式 │
│ │
│ 第 2 层: 镜像构建安全 │
│ ├── 最小化镜像 (distroless/alpine) │
│ ├── 多阶段构建 │
│ ├── 非 root 用户运行 │
│ └── 固定版本标签 │
│ │
│ 第 1 层: 宿主机安全 │
│ ├── 内核更新 │
│ ├── 文件系统隔离 │
│ └── 网络防火墙 │
└──────────────────────────────────────────────┘
11.2 SELinux
SELinux(Security-Enhanced Linux)是 Linux 内核强制访问控制(MAC)机制,Podman 原生支持 SELinux。
11.2.1 SELinux 容器标签
# 查看容器进程的 SELinux 标签
podman run --rm alpine cat /proc/1/attr/current
# system_u:system_r:container_t:s0:c123,c456
# 查看宿主机上的容器文件标签
ls -Z /var/lib/containers/storage/
# system_u:object_r:container_file_t:s0:c123,c456 ...
11.2.2 SELinux 模式管理
# 查看 SELinux 状态
getenforce
# Enforcing / Permissive / Disabled
# 临时切换为 Permissive(调试用)
sudo setenforce 0
# 恢复 Enforcing
sudo setenforce 1
# 永久配置
sudo vi /etc/selinux/config
# SELINUX=enforcing
11.2.3 SELinux 与容器卷
# SELinux 阻止容器访问未标记的目录
# 容器内报错: Permission denied
# 解决方案:使用 :Z 或 :z 标签
podman run -v /data:/data:Z myapp # :Z 私有标签
podman run -v /shared:/shared:z app1 # :z 共享标签
podman run -v /shared:/shared:z app2 # 多容器共享
# 手动标记目录
sudo semanage fcontext -a -t container_file_t "/data(/.*)?"
sudo restorecon -R /data
11.2.4 SELinux 布尔值
# 查看容器相关的 SELinux 布尔值
getsebool -a | grep container
# 常用布尔值
# container_connect_any — 允许容器连接任何端口
# container_manage_cgroup — 允许容器管理 cgroup
# container_use_cephfs — 允许容器使用 CephFS
# 设置布尔值(允许容器连接任何端口)
sudo setsebool -P container_connect_any on
11.3 seccomp
seccomp(Secure Computing Mode)用于限制容器可以使用的系统调用(syscall)。
11.3.1 默认 seccomp Profile
Podman 默认启用 seccomp,阻止约 50 个危险的系统调用:
# 查看默认阻止的系统调用
podman info | grep -A5 seccomp
# 被阻止的系统调用包括:
# - mount / umount2 — 防止挂载文件系统
# - reboot — 防止重启系统
# - kexec_load — 防止加载新内核
# - open_by_handle_at — 绕过文件权限
# - init_module / finit_module — 加载内核模块
# - ptrace — 调试其他进程
11.3.2 自定义 seccomp Profile
// custom-seccomp.json
{
"defaultAction": "SCMP_ACT_ERRNO",
"architectures": ["SCMP_ARCH_X86_64"],
"syscalls": [
{
"names": [
"accept", "access", "arch_prctl", "bind", "brk",
"chdir", "chmod", "chown", "clock_gettime",
"close", "connect", "dup", "dup2", "dup3",
"epoll_create1", "epoll_ctl", "epoll_wait",
"execve", "exit", "exit_group",
"fchmod", "fchown", "fcntl", "fstat", "futex",
"getcwd", "getdents64", "getpid", "getppid",
"getuid", "ioctl", "listen", "lseek",
"madvise", "mmap", "mprotect", "munmap",
"nanosleep", "newfstatat", "openat",
"pipe2", "poll", "prctl", "pread64",
"read", "readlink", "recvfrom", "rename",
"rt_sigaction", "rt_sigprocmask",
"sendto", "set_robust_list", "set_tid_address",
"setsockopt", "socket", "stat", "statfs",
"tgkill", "umask", "unlink", "wait4", "write",
"writev"
],
"action": "SCMP_ACT_ALLOW"
}
]
}
# 使用自定义 seccomp Profile
podman run --security-opt seccomp=custom-seccomp.json myapp
# 禁用 seccomp(仅调试,不推荐生产)
podman run --security-opt seccomp=unconfined myapp
11.4 Linux Capability
Linux Capability 将传统的 root 权限细分为多个独立的权限单元。
11.4.1 容器默认 Capability
# 查看容器的默认 Capability
podman run --rm alpine cat /proc/1/status | grep Cap
# CapPrm: 00000000a80425fb
# CapEff: 00000000a80425fb
# 解码
capsh --decode=00000000a80425fb
# 0x00000000a80425fb=cap_chown,cap_dac_override,cap_fowner,
# cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,
# cap_net_bind_service,cap_net_raw,cap_sys_chroot,cap_mknod,
# cap_audit_write,cap_setfcap
11.4.2 Capability 管理
# 移除所有 Capability,只添加需要的
podman run --cap-drop=ALL --cap-add=NET_BIND_SERVICE \
--name web nginx:1.27-alpine
# 添加特定 Capability
podman run --cap-add=SYS_PTRACE mydebugger:latest
# 常用 Capability 说明
| Capability | 作用 | 常见用途 |
|---|---|---|
NET_BIND_SERVICE | 绑定 < 1024 端口 | Web 服务器 |
NET_RAW | 使用 raw socket | ping、网络诊断 |
SYS_PTRACE | 调试进程 | 调试器 |
SYS_ADMIN | 大量系统管理操作 | 特殊场景(慎用) |
SYS_TIME | 修改系统时钟 | NTP 容器 |
SYS_RESOURCE | 绕过资源限制 | 特殊性能需求 |
DAC_OVERRIDE | 绕过文件权限 | 文件操作 |
CHOWN | 修改文件所有者 | 初始化脚本 |
SETUID/SETGID | 切换用户/组 | 启动脚本降权 |
# 查看容器 Capability
podman exec web capsh --print
# 生产推荐:先 drop ALL,再按需 add
podman run --cap-drop=ALL \
--cap-add=CHOWN \
--cap-add=SETUID \
--cap-add=SETGID \
--cap-add=NET_BIND_SERVICE \
myapp:latest
11.5 只读根文件系统
# 只读根文件系统 + tmpfs
podman run -d --name app \
--read-only \
--tmpfs /tmp:rw,size=100m,mode=1777 \
--tmpfs /run:rw,size=50m \
-v app-data:/app/data:Z \
myapp:latest
# 优势:
# 1. 防止恶意写入文件系统
# 2. 容器内的恶意进程无法持久化
# 3. 强制应用设计为无状态
# 在 Quadlet 中使用
# app.container
[Container]
ReadOnly=true
Tmpfs=/tmp:size=100m
Tmpfs=/run:size=50m
11.6 用户命名空间深度
11.6.1 –userns 配置
# 默认:Rootless 使用用户命名空间映射
podman run --rm alpine id
# uid=0(root) gid=0(root)
# keep-id:容器内 UID/GID 与宿主机用户一致
podman run --rm --userns=keep-id alpine id
# uid=1000 gid=1000
# 适用于开发环境,避免文件权限问题
# auto:自动分配映射
podman run --rm --userns=auto alpine id
# uid=0(root) gid=0(root)(但在独立的映射范围内)
# 指定 UID/GID 映射
podman run --rm --userns=keep-id:uid=1000,gid=1000 alpine id
# host:使用宿主机命名空间(放弃隔离)
podman run --rm --userns=host alpine id
# uid=0(root) gid=0(root)(宿主机 root,危险!)
11.6.2 多租户隔离
# 不同用户运行容器,天然隔离
alice$ podman run -d --name app1 myapp:v1
bob$ podman run -d --name app2 myapp:v2
# 容器存储完全隔离
# alice: ~/.local/share/containers/storage/
# bob: ~/.local/share/containers/storage/
11.7 镜像安全
11.7.1 镜像扫描
# 使用 Trivy 扫描镜像漏洞
podman images
trivy image nginx:1.27-alpine
# 使用 Grype 扫描
grype nginx:1.27-alpine
# 扫描本地构建的镜像
trivy image myapp:v1.0
11.7.2 安全基础镜像选择
# 镜像安全等级对比
| 镜像 | 大小 | 包管理器 | Shell | CVE 风险 |
|---|---|---|---|---|
ubuntu:24.04 | ~78MB | ✅ apt | ✅ | 中 |
debian:bookworm-slim | ~75MB | ✅ apt | ✅ | 中 |
alpine:3.20 | ~8MB | ✅ apk | ✅ | 低 |
distroless/static | ~2MB | ❌ | ❌ | 极低 |
scratch | 0MB | ❌ | ❌ | 无 |
# 使用 distroless 作为最终镜像
FROM golang:1.23 AS builder
WORKDIR /src
COPY . .
RUN CGO_ENABLED=0 go build -o /app
# 最终镜像:无 Shell、无包管理器
FROM gcr.io/distroless/static
COPY --from=builder /app /app
USER nonroot:nonroot
ENTRYPOINT ["/app"]
11.7.3 镜像签名验证
# 使用 cosign 验证镜像签名
# 安装 cosign
# brew install cosign (macOS)
# sudo dnf install cosign (Fedora)
# 验证签名
cosign verify \
--certificate-identity-regexp='.*' \
--certificate-oidc-issuer-regexp='.*' \
ghcr.io/sigstore/cosign/cosign
# Podman 配置镜像签名策略
# /etc/containers/policy.json
{
"default": [{"type": "insecureAcceptAnything"}],
"transports": {
"docker": {
"registry.example.com": [
{
"type": "signedBy",
"keyType": "GPGKeys",
"keyPath": "/etc/pki/rpm-gpg/RPM-GPG-KEY-example"
}
]
}
}
}
11.8 安全检查清单
生产环境安全检查表
#!/bin/bash
# security-audit.sh — 容器安全审计脚本
echo "=== 容器安全审计 ==="
# 1. 检查是否有特权容器
echo "--- 特权容器 ---"
podman ps --format '{{.Names}}' | while read name; do
privileged=$(podman inspect "$name" --format '{{.HostConfig.Privileged}}')
if [ "$privileged" = "true" ]; then
echo "⚠️ $name 运行在特权模式!"
fi
done
# 2. 检查是否有容器以 root 运行
echo "--- Root 容器 ---"
podman ps --format '{{.Names}}' | while read name; do
user=$(podman inspect "$name" --format '{{.Config.User}}')
if [ -z "$user" ] || [ "$user" = "root" ] || [ "$user" = "0" ]; then
echo "⚠️ $name 以 root 用户运行"
fi
done
# 3. 检查端口暴露
echo "--- 端口暴露 ---"
podman ps --format 'table {{.Names}}\t{{.Ports}}'
# 4. 检查卷挂载
echo "--- 卷挂载 ---"
podman ps --format '{{.Names}}' | while read name; do
mounts=$(podman inspect "$name" --format '{{range .Mounts}}{{.Source}}:{{.Destination}} {{end}}')
if echo "$mounts" | grep -qE '^/(etc|var/run|proc|sys|dev)'; then
echo "⚠️ $name 挂载了敏感宿主机目录: $mounts"
fi
done
# 5. 检查 Capability
echo "--- 额外 Capability ---"
podman ps --format '{{.Names}}' | while read name; do
caps=$(podman inspect "$name" --format '{{.HostConfig.CapAdd}}')
if [ "$caps" != "[]" ] && [ -n "$caps" ]; then
echo "⚠️ $name 添加了额外 Capability: $caps"
fi
done
echo "=== 审计完成 ==="
11.9 本章小结
| 知识点 | 要点 |
|---|---|
| SELinux | 强制访问控制,容器标签隔离 |
| seccomp | 系统调用过滤,限制危险操作 |
| Capability | 细粒度权限,--cap-drop=ALL --cap-add=... |
| 只读文件系统 | --read-only + --tmpfs |
| 用户命名空间 | 容器内 root ≠ 宿主机 root |
| 镜像扫描 | Trivy / Grype 检测 CVE |
| 最佳实践 | 非 root 运行、最小权限、最小镜像 |
下一步
- 👉 第 12 章:镜像仓库 — 私有 Registry 与镜像签名