R055LE/container-hardening-lab
GitHub: R055LE/container-hardening-lab
一个符合 CIS Benchmark 和 Iron Bank 标准的生产级容器加固实验室,集成了策略即代码、漏洞扫描、镜像签名、SBOM 生成与运行时检测的完整安全流水线。
Stars: 0 | Forks: 0
# 容器加固实验室
[](https://github.com/R055LE/container-hardening-lab/actions/workflows/container-security.yml)
一个展示生产级容器加固、漏洞扫描和策略即代码执行的作品集项目 —— 符合 [CIS Docker Benchmark](https://www.cisecurity.org/benchmark/docker) 和 [DoD Iron Bank](https://ironbank.dso.mil/) 实践。
## 本项目展示了什么
本实验室解决了生产环境中最常见的容器安全故障:
- **容器以 root 身份运行** — 本仓库中的每个镜像都使用专用的非 root 用户 (UID 10001)
- **臃肿的攻击面** — 多阶段构建剥离了构建工具;明确移除了 `wget`、`curl`、`perl` 和 SUID/SGID 二进制文件;Go 镜像使用 distroless(无 shell、无包管理器、无 libc)
- **未固定或不受信任的基础镜像** — OPA 白名单强制执行批准的注册表,并对未固定的摘要发出警告
- **无准入控制** — Kyverno ClusterPolicies 在部署时阻止特权 pod、主机命名空间共享以及缺失的能力降级
- **无证据追踪** — 每个镜像都附带 CycloneDX + SPDX SBOM 和 Trivy CVE 报告;检查结果会作为 CI 构建的门槛
- **未签名的镜像** — 推送到 ghcr.io 的每个镜像都使用 Cosign 签名(通过 Sigstore 进行无密钥签名);Kyverno `verifyImages` 策略会在准入时拒绝未签名的镜像
- **无运行时可见性** — Falco 自定义规则检测加固容器内部的后渗透行为(生成 shell、执行包管理器、出站 C2 连接)
## 仓库结构
```
.
├── images/
│ ├── python/ # python:3.12-slim — reference hardened image
│ ├── node/ # node:20-slim — Express API
│ ├── nginx/ # nginx:1.27-alpine — static file server
│ └── go/ # distroless — statically compiled Go binary
│
├── policies/
│ ├── opa/ # Rego policies evaluated by Conftest against Dockerfiles
│ │ ├── no-root.rego
│ │ ├── no-privileged.rego
│ │ └── image-source-allowlist.rego
│ └── kyverno/ # Kubernetes admission policies
│ ├── no-root.yaml
│ ├── no-privileged.yaml
│ └── image-source-allowlist.yaml
│
├── tests/
│ ├── opa/ # OPA unit tests (opa test)
│ ├── kyverno/ # Kyverno CLI policy tests + fixture Pods
│ └── structure/ # Container structure tests (runtime assertions)
│
├── examples/
│ └── unhardened/ # Deliberately insecure Dockerfile — failure demo
│
├── falco/
│ ├── rules/ # Custom Falco runtime detection rules
│ └── docker-compose.yml # Local Falco demo setup
│
├── .github/workflows/container-security.yml # Full CI pipeline
└── Makefile # Primary task interface
```
## 加固控制措施
所有镜像均应用以下控制措施,并映射到 CIS Docker Benchmark 部分:
| CIS 控制 | 功能描述 | 实现方式 |
|---|---|---|
| **4.1** 非 root 用户 | 防止容器被入侵后的权限提升 | 专用 `appuser`/`nginx` (UID 10001/101);在 `CMD` 之前设置 `USER` |
| **4.2** 受信任的基础镜像 | 防止通过恶意基础镜像导致的供应链入侵 | OPA 白名单策略;仅允许官方 Docker Hub、GCR Distroless、Chainguard、Iron Bank 和 ECR |
| **4.4** 多阶段构建 | 从最终镜像中剥离构建工具 | 构建器阶段安装依赖项;最终阶段仅复制构建产物 |
| **4.7** 移除不必要的包 | 减少攻击面 | 移除 `wget`、`curl`、`perl`、`gcc`、`binutils`;通过二进制路径移除 BusyBox applets |
| **4.8** 无 SUID/SGID 位 | 防止通过 setuid 二进制文件进行权限提升 | 在构建时运行 `find / -xdev -perm /4000 -o -perm /2000 -exec chmod ug-s` |
| **4.9** 镜像中无机密信息 | 防止通过镜像检查导致凭据泄露 | 不包含带有凭据的 `ENV`;仅限运行时注入的机密信息 |
| **5.2/5.3** 无主机命名空间 | 防止通过主机 PID/IPC/网络进行容器逃逸 | Kyverno 策略阻止 `hostPID`、`hostIPC` 和 `hostNetwork: true` |
| **5.4** 无特权容器 | 防止完全访问主机 | Kyverno 策略阻止 `privileged: true`,要求 `allowPrivilegeEscalation: false`,要求 `capabilities.drop: ALL` |
| **5.7** 非特权端口 | 避免需要 `CAP_NET_BIND_SERVICE` | 所有镜像暴露端口 ≥ 1024 (8000, 3000, 8080) |
## 测试层
测试套件包含四个独立的层,每层捕获不同类型的故障:
### 1. OPA 单元测试 — 策略逻辑
使用 `opa test` 测试 Rego 策略规则本身,无需 Docker。提供对策略正确性的快速反馈。
```
make test-opa
```
涵盖三个策略的 47 项测试:
| 策略 | 测试数 | 验证内容 |
|---|---|---|
| `no-root.rego` | 13 | 检测缺失 `USER`、`USER root`、`USER 0`、`USER 0:group` |
| `no-privileged.rego` | 14 | 检测 `RUN --privileged`,对低于 1024 的端口和 `--chown=root` 发出警告 |
| `image-source-allowlist.rego` | 20 | 批准/拒绝注册表,对未固定的摘要发出警告,覆盖多阶段场景 |
### 2. Kyverno 策略测试 — 准入控制
使用 `kyverno test` 针对 fixture Pod 清单测试 Kyverno `ClusterPolicy` 规则。无需集群。
```
make test-kyverno
```
涵盖每条规则通过和失败案例的 13 项测试:
| 测试 | Fixture | 预期结果 |
|---|---|---|
| 要求非 root 用户 | `pod-fail-runasroot.yaml` | 拒绝 |
| 阻止 `runAsNonRoot: false` | `pod-fail-runasnonroot-false.yaml` | 拒绝 |
| 阻止 `allowPrivilegeEscalation` | `pod-fail-privilege-escalation.yaml` | 拒绝 |
| 阻止 `privileged: true` | `pod-fail-privileged.yaml` | 拒绝 |
| 要求 `capabilities.drop: ALL` | `pod-fail-no-caps-drop.yaml` | 拒绝 |
| 阻止 `hostPID/hostIPC/hostNetwork` | 3 个 fixture 文件 | 拒绝 |
| 完全加固的 pod | `pod-pass-hardened.yaml` | 通过 |
### 3. Falco 规则验证 — 运行时检测规则
验证自定义 Falco 规则能否无错误加载 —— 语法、宏/列表解析以及字段引用都会被检查。使用 Falco 容器,因此不需要内核访问权限。
```
make test-falco
```
针对 Falco 的规则引擎,验证 `falco/rules/container-hardening-lab.yaml` 中的所有 5 条自定义规则。捕获 YAML 解析错误、无效的条件语法、未定义的宏/列表以及无法识别的字段名称。
### 4. 容器结构测试 — 运行时断言
使用 `container-structure-test` 在运行时测试实际构建的镜像。这些测试捕获仅在镜像构建后才会出现的回归 —— 例如,包管理器静默地重新添加 SUID 二进制文件。
```
make test-structure IMAGE=python # or node, nginx, go
```
涵盖四个镜像的 53 项测试:
| 镜像 | 测试数 | 检查内容 |
|---|---|---|
| `hardened-python` | 14 | UID 10001,无 SUID/SGID,无 wget/curl/perl/gcc,`/app` 工作目录,nologin shell,OCI 标签 |
| `hardened-node` | 14 | UID 10001,无 SUID/SGID,无 wget/curl/perl,Express 可导入,`src/index.js` 存在,OCI 标签 |
| `hardened-nginx` | 13 | UID 101,无 SUID/SGID,无 wget/curl,`server_tokens off`,端口 8080,安全标头,OCI 标签 |
| `hardened-go` | 12 | UID 65532,无 shell/bash,无 wget/curl/apt/apk/gcc/go,存在 CA 证书,OCI 标签 (distroless) |
## 故障演示
`examples/unhardened/Dockerfile` 是故意设计为不安全的。CI 流水线断言 OPA linter 和 Trivy 扫描器**必须**对其进行标记 —— 干净的结果将意味着工具未正常工作。
Conftest 策略捕获的违规项:
```
FAIL - examples/unhardened/Dockerfile - docker.security - CIS 4.1: No USER instruction found.
WARN - examples/unhardened/Dockerfile - docker.security - CIS 5.7: EXPOSE 80 uses a privileged port.
WARN - examples/unhardened/Dockerfile - docker.security - Files COPY'd with --chown=root will not be writable by the app user.
WARN - examples/unhardened/Dockerfile - docker.security - Image 'node:18' is not pinned to a digest.
```
Trivy 单独将 `node:18` 标记为带有 CRITICAL/HIGH CVE(该镜像有意固定为一个旧的、存在漏洞的版本)。
负向结构测试(`tests/structure/unhardened.yaml`)随后构建该镜像,并断言它**具有**加固镜像所消除的问题 —— 以 root 身份 (UID 0) 运行、存在 SUID 二进制文件、安装了 wget/curl/perl/apt-get。这使得前后的对比具体且可测试。
## 前置条件
| 工具 | 版本 | 安装方式 |
|---|---|---|
| Docker | ≥ 24 | [docs.docker.com](https://docs.docker.com/get-docker/) |
| Trivy | ≥ 0.69 | [aquasecurity.github.io/trivy](https://aquasecurity.github.io/trivy/latest/getting-started/installation/) |
| Syft | ≥ 1.0 | [github.com/anchore/syft](https://github.com/anchore/syft#installation) |
| Conftest | ≥ 0.57 | [conftest.dev](https://www.conftest.dev/install/) |
| OPA | ≥ 0.70 | [openpolicyagent.org](https://www.openpolicyagent.org/docs/latest/#1-download-opa) |
| Kyverno CLI | ≥ 1.17 | [kyverno.io/docs/kyverno-cli](https://kyverno.io/docs/kyverno-cli/) |
| container-structure-test | ≥ 1.19 | [github.com/GoogleContainerTools/container-structure-test](https://github.com/GoogleContainerTools/container-structure-test#installation) |
## 快速开始
```
# 仅运行 policy 测试 — 无需 Docker,快速反馈
make test
# 运行所有测试,包括 Falco rule 验证(需要 Docker)
make test-all
# 构建、扫描并为所有镜像生成 SBOMs
make all
# 运行 container structure 测试(必须先构建镜像)
make test-structure IMAGE=python
make test-structure IMAGE=node
make test-structure IMAGE=nginx
make test-structure IMAGE=go
# 单个镜像的独立 targets
make build IMAGE=python
make scan IMAGE=python
make sbom IMAGE=python
make lint IMAGE=python
```
扫描报告和 SBOM 会写入 `reports//`:
```
reports/python/
├── trivy.json # CVE scan results (JSON)
├── sbom.cyclonedx.json # Software Bill of Materials (CycloneDX)
└── sbom.spdx.json # Software Bill of Materials (SPDX)
```
## CI 流水线
GitHub Actions 工作流 (`.github/workflows/container-security.yml`) 会在每次推送和拉取请求时运行三个作业:
```
flowchart TD
trigger([push / pull_request]):::event --> pt
pt["**policy-tests**\nOPA unit tests — 47\nKyverno policy tests — 13\nFalco rule validation — 5 rules"]:::job
pt --> fd
pt --> ip
subgraph fd [" failure-demo "]
direction TB
f1["Conftest must reject\nunhardened Dockerfile"]:::check
f2["Trivy must find CVEs\nin node:18"]:::check
end
subgraph ip [" image-pipeline — python · node · nginx · go (parallel) "]
direction TB
i1["Lint Dockerfile\nConftest / OPA"]:::step --> i2
i2["Build image"]:::step --> i3
i3["Trivy scan\ngate: CRITICAL / HIGH = fail"]:::gate --> i4
i3 --> i5
i4["SARIF → GitHub\nSecurity tab"]:::output
i5["Generate SBOM\nSyft — CycloneDX + SPDX"]:::step --> i6
i6["Container structure tests\n41 assertions"]:::step --> i7
i6 --> i8
i7[("Reports artifact\n30-day retention")]:::output
i8["Push → ghcr.io\nCosign sign (keyless)"]:::sign
end
classDef event fill:#6366f1,stroke:#4f46e5,color:#fff
classDef job fill:#0ea5e9,stroke:#0284c7,color:#fff
classDef step fill:#10b981,stroke:#059669,color:#fff
classDef gate fill:#f59e0b,stroke:#d97706,color:#fff
classDef check fill:#ec4899,stroke:#db2777,color:#fff
classDef output fill:#64748b,stroke:#475569,color:#fff
classDef sign fill:#8b5cf6,stroke:#7c3aed,color:#fff
```
**`policy-tests`** 最先运行 —— 为每次推送提供快速反馈。OPA 和 Kyverno 测试不需要 Docker;Falco 验证使用 Falco 容器镜像来验证规则语法和字段引用。
**`failure-demo`** 断言未加固的 Dockerfile 和 `node:18` 基础镜像被正确标记。如果此处出现干净的结果,则作业失败,从而证明工具处于活动状态。
**`image-pipeline`** 在所有三个镜像上并行运行。Trivy 的扫描结果作为 SARIF 上传到 GitHub Security 标签页以进行分类处理。所有报告都将作为构建产物归档 30 天。
## 镜像签名
CI 流水线推送到 `ghcr.io` 的每个镜像都使用 [Cosign](https://github.com/sigstore/cosign) 并通过 [Sigstore](https://sigstore.dev/) 进行无密钥签名。任何地方都不存储私钥 —— 签名与工作流的 GitHub Actions OIDC 身份绑定。
验证已签名的镜像(无需密钥):
```
cosign verify \
--certificate-identity-regexp \
"https://github.com/R055LE/container-hardening-lab/.github/workflows/container-security.yml@refs/heads/main" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
ghcr.io/r055le/container-hardening-lab-python:latest
```
成功验证确认该镜像是由此 CI 工作流从 `main` 分支构建并推送的 —— 而不是手动推送或来自被入侵的流水线。`policies/kyverno/verify-signatures.yaml` ClusterPolicy 在准入时强制执行此操作,拒绝任何引用来自此注册表且未签名镜像的 Pod。
## 运行时安全
`falco/` 使用 [Falco](https://falco.org/)(CNCF 毕业项目)添加了一个运行时检测层。在构建时和部署时的控制措施减小攻击面的情况下,Falco 能够检测容器运行后发生的利用尝试。
五条自定义规则涵盖了后渗透的攻击链路:
| 规则 | 捕获内容 |
|---|---|
| 生成 Shell | 在加固容器内启动的交互式 shell |
| 包管理器 | 使用 `apt`、`pip`、`npm`、`apk` 安装攻击工具 |
| 读取敏感文件 | 打开了 `/etc/shadow`、SSH 密钥、sudoers 文件 |
| 意外出站连接 | 容器向外部地址发起 TCP 连接 (C2/数据渗透) |
| 文件系统写入 | 写入非临时路径 (持久化尝试) |
使用 Docker Compose 在本地运行演示 —— 请参阅 [falco/README.md](falco/README.md) 获取详细操作指南。
## 部署 (Kubernetes)
要在集群上强制执行准入策略:
```
# 应用 Kyverno ClusterPolicies
kubectl apply -f policies/kyverno/
# 验证 — 此 Pod 应被阻止
kubectl apply -f tests/kyverno/resources/pod-fail-privileged.yaml
```
策略默认以审计模式编写 (`validationFailureAction: Audit`)。在生产环境中请更改为 `Enforce`。
标签:DevSecOps, MITM代理, OPA/Kyverno, Web截图, 上游代理, 子域名突变, 容器安全, 日志审计, 策略即代码, 结构化提示词, 聊天机器人安全, 请求拦截, 逆向工具