容器安全实战——镜像扫描、Pod 安全策略、运行时防护的完整方案
容器安全实战——镜像扫描、Pod 安全策略、运行时防护的完整方案
适读人群:对容器安全感到迷茫、或者刚开始做安全加固的工程师 | 阅读时长:约 17 分钟 | 核心价值:从镜像到运行时,建立完整的容器安全防御体系
去年我们做了一次红队演练,结果让我印象深刻。
攻击方拿到了一个 Web 服务的注入漏洞,进入了容器。然后用了不到 20 分钟,通过容器逃逸 + 横向移动,拿到了集群的 API Server 权限,最后在某台节点上找到了数据库凭证。
全程用的都是公开的 CVE 漏洞和配置错误,没有什么魔法。
事后复盘,暴露的问题:镜像里有高危漏洞(三个月前就有 CVE 没打补丁);Pod 以 root 运行;某些容器挂载了宿主机目录;ServiceAccount 权限过大;没有运行时行为检测……
这篇文章把容器安全的几个关键层面都覆盖一遍。
容器安全的四个层面
Layer 1: 镜像安全(构建阶段)
└── 基础镜像漏洞扫描、镜像内容最小化
Layer 2: 容器配置安全(部署阶段)
└── 非 root 运行、只读文件系统、禁止特权容器
Layer 3: 网络安全(运行阶段)
└── NetworkPolicy 微隔离(前面文章已讲)
Layer 4: 运行时安全(运行阶段)
└── 检测异常行为(容器内 shell 访问、异常进程等)Layer 1:镜像扫描
在 CI/CD 里集成 Trivy
Trivy 是最常用的开源容器镜像扫描工具(Aqua Security 维护):
# 本地扫描
trivy image --severity HIGH,CRITICAL your-image:latest
# 扫描结果示例
# your-image:latest (ubuntu 22.04)
# =====================
# Total: 12 (HIGH: 8, CRITICAL: 4)
#
# ┌───────────────┬───────────────┬──────────┬───────┐
# │ Library │ Vulnerability │ Severity │ Fixed │
# ├───────────────┼───────────────┼──────────┼───────┤
# │ openssl │ CVE-2023-xxxx │ CRITICAL │ 3.0.9 │
# └───────────────┴───────────────┴──────────┴───────┘在 GitHub Actions 里集成:
# .github/workflows/security-scan.yml
name: Security Scan
on: [push, pull_request]
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build image
run: docker build -t myapp:${{ github.sha }} .
- name: Scan with Trivy
uses: aquasecurity/trivy-action@master
with:
image-ref: myapp:${{ github.sha }}
format: 'table'
exit-code: '1' # 有高危漏洞时 CI 失败
severity: 'CRITICAL' # 只对 CRITICAL 失败,HIGH 只告警
vuln-type: 'os,library'
- name: Upload scan results
uses: actions/upload-artifact@v3
if: always()
with:
name: trivy-results
path: trivy-results.json在 Harbor 里配置自动扫描
# Harbor 2.0+ 支持在镜像推送后自动触发 Trivy 扫描
# 设置:Administration → Interrogation Services → 选择 Trivy
# 然后在 Project 设置里开启:Project → Configuration → Automatically scan images on push
# 设置阻止高危镜像部署(在 Project 里设置阻止策略)
# Prevent vulnerable images from running: CRITICALLayer 2:Pod 安全配置
K8s 1.25 废弃了 PodSecurityPolicy,改用 Pod Security Standards(PSS) + Pod Security Admission。
Pod Security Standards 三个级别
| 级别 | 描述 | 适用场景 |
|---|---|---|
privileged | 不限制,允许一切 | 系统组件(如 CNI、CSI 插件) |
baseline | 基本限制,禁止明显的高危配置 | 一般业务应用 |
restricted | 最严格,遵循安全最佳实践 | 安全要求高的应用 |
给命名空间设置安全策略:
# 为 production 命名空间开启 baseline 强制模式
kubectl label namespace production \
pod-security.kubernetes.io/enforce=baseline \
pod-security.kubernetes.io/warn=restricted \
pod-security.kubernetes.io/audit=restrictedenforce:违反策略时,Pod 创建被拒绝warn:违反时允许但发出警告audit:违反时记录到审计日志
安全 Pod 配置模板
apiVersion: apps/v1
kind: Deployment
metadata:
name: secure-app
spec:
template:
spec:
# Pod 级别安全上下文
securityContext:
runAsNonRoot: true # 禁止以 root 运行
runAsUser: 1001 # 指定 UID
runAsGroup: 1001 # 指定 GID
fsGroup: 1001 # 文件系统组
seccompProfile:
type: RuntimeDefault # 使用默认 seccomp profile
automountServiceAccountToken: false # 不挂载 SA token(如果不需要)
containers:
- name: app
image: myapp:v2.3.1
# 容器级别安全上下文
securityContext:
allowPrivilegeEscalation: false # 禁止提权
readOnlyRootFilesystem: true # 只读根文件系统
capabilities:
drop: ["ALL"] # 删除所有 Linux capabilities
add: [] # 按需添加(如需要绑定 80 端口加 NET_BIND_SERVICE)
privileged: false
# 资源限制
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
memory: 512Mi
# 需要写入的目录单独挂载
volumeMounts:
- name: tmp-dir
mountPath: /tmp
- name: app-logs
mountPath: /app/logs
volumes:
- name: tmp-dir
emptyDir: {}
- name: app-logs
emptyDir: {}readOnlyRootFilesystem: true 是一个非常有效的安全配置——即使攻击者进入容器,也无法写入任何文件(包括安装攻击工具)。只需要把程序确实需要写的目录(/tmp、日志目录)单独挂载为 emptyDir。
踩坑实录一:readOnlyRootFilesystem 导致应用无法写临时文件崩溃
现象:开启 readOnlyRootFilesystem: true 后,应用启动就失败,报错 Read-only file system,发现应用在写 /tmp/xxx 目录。
排查:
# 先不开 readOnly,进容器看哪些目录需要写入
kubectl exec -it <pod> -- sh
strace -e trace=file myapp 2>&1 | grep "EACCES\|O_RDWR"解法:找到所有需要写的路径,全部挂载为 emptyDir:
volumeMounts:
- name: tmp
mountPath: /tmp
- name: var-cache
mountPath: /var/cache/nginx # nginx 需要写缓存
- name: var-run
mountPath: /var/run # nginx 需要写 pid 文件
volumes:
- name: tmp
emptyDir: {}
- name: var-cache
emptyDir: {}
- name: var-run
emptyDir: {}Layer 3 & 4:运行时安全检测
Falco:开源运行时威胁检测
Falco 是 CNCF 的运行时安全工具,通过系统调用(eBPF 或内核模块)检测异常行为:
helm repo add falcosecurity https://falcosecurity.github.io/charts
helm install falco falcosecurity/falco \
--namespace falco \
--create-namespace \
--set driver.kind=ebpf \
--set falcosidekick.enabled=true \
--set falcosidekick.webui.enabled=trueFalco 规则示例(自定义告警):
# custom-falco-rules.yaml
- rule: Shell spawned in container
desc: 检测容器内启动 shell(攻击者常用行为)
condition: >
spawned_process and container and
proc.name in (shell_binaries) and
container.image.repository != "my-debug-image"
output: >
Shell spawned in container
(user=%user.name command=%proc.cmdline
container=%container.name image=%container.image.repository:%container.image.tag)
priority: WARNING
tags: [container, shell, mitre_execution]
- rule: Write below root
desc: 尝试写入根目录(可能是逃逸行为)
condition: >
open_write and container and fd.directory in (/) and
not fd.name in (/tmp, /var/log)
output: >
File write below root directory in container
(file=%fd.name container=%container.name)
priority: ERROR
- rule: Unexpected outbound connection
desc: 容器建立非预期的出站连接(可能是 C2 通信)
condition: >
outbound and container and
not fd.sport in (80, 443, 3306, 6379, 5432) and
not container.image.repository in (known-images-list)
output: >
Unexpected outbound connection
(dest=%fd.rip:%fd.rport container=%container.name)
priority: WARNINGFalco 告警可以推送到企业微信/Slack/PagerDuty:
# falcosidekick 配置
falcosidekick:
config:
webhook:
address: "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxx"
minimumpriority: "warning"踩坑实录二:特权容器被攻击者用于逃逸
现象:红队演练发现,某个运维工具的 Pod 配置了 privileged: true,攻击者进入后,通过挂载宿主机 /dev/sda,读取了宿主机磁盘内容,获取了其他 Pod 的敏感数据。
原因:privileged: true 相当于给容器宿主机的全部权限,包括访问设备、挂载文件系统等。这是非常危险的配置。
解法:
- 扫描所有 Pod 的
privileged: true配置,逐一评估是否真正需要 - 绝大多数运维工具不需要
privileged,需要的只是特定的 Linux capability(如SYS_PTRACE、NET_ADMIN) - 用 Kyverno/OPA Gatekeeper 阻止 privileged Pod 创建:
# Kyverno Policy:禁止 privileged Pod
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: disallow-privileged-containers
spec:
validationFailureAction: Enforce
background: true
rules:
- name: check-privileged
match:
any:
- resources:
kinds: [Pod]
validate:
message: "禁止创建特权容器"
pattern:
spec:
containers:
- =(securityContext):
=(privileged): "false"踩坑实录三:镜像里包含了 .git 目录泄露源码
现象:安全审计发现,某个镜像里包含了完整的 .git 目录,包括所有提交历史,其中有一次提交在注释里包含了数据库密码(后来改到 Secret 里了,但 git 历史保留了)。
原因:Dockerfile 里用了 COPY . .,而 .dockerignore 里没有排除 .git。
解法:
.dockerignore必须包含.git:
.git
.gitignore
.github
*.md- 用镜像扫描工具检测敏感信息:
# 使用 truffleHog 扫描镜像里的敏感信息
docker save myimage:latest | trufflehog docker --image -- 定期检查现有镜像:
# 检查镜像里是否有 .git 目录
docker run --rm myimage:latest find / -name ".git" -type d 2>/dev/null安全加固 Checklist
最后给一个可以直接拿去用的 Checklist:
镜像安全
Pod 配置安全
集群级安全
运行时安全
# kube-bench:检查集群是否符合 CIS K8s 安全基准
kubectl apply -f https://raw.githubusercontent.com/aquasecurity/kube-bench/main/job.yaml
kubectl logs -l app=kube-bench容器安全不是一次性的工作,它需要嵌入到开发、构建、部署、运行的每个环节。从今天开始,哪怕先把 Trivy 加到 CI 里,再把 allowPrivilegeEscalation: false 加到所有 Deployment——这两步就已经让安全水位提升一大截了。
