Compliance Operator 详细设计文档
| 属性 | 值 |
|---|---|
| 文档版本 | v1.0 |
| 关联提案 | security-committee/oFEP-0003 |
| API Group | compliance.openfuyao.cn/v1alpha1 |
| 状态 | Draft |
| 日期 | 2026-05-28 |
1. 概述
1.1 项目背景与动机
随着 Kubernetes 在生产环境中的大规模采用,集群的安全合规性成为平台运维团队面临的核心挑战。CIS (Center for Internet Security) Benchmark 和 DISA STIG (Security Technical Implementation Guide) 是业界公认的两大 Kubernetes 安全基线标准:
- CIS Kubernetes Benchmark 涵盖控制平面与 Worker 节点的 100+ 项检查,涉及 API Server、Scheduler、Controller Manager、etcd、kubelet 等组件的配置、权限、网络策略等维度。
- DISA Kubernetes STIG (V2R4) 包含 55 条安全规则(V-242376 至 V-245544),聚焦于本地文件系统层面的配置合规性检查,涵盖 TLS 配置、认证授权、审计日志、证书管理、文件权限等关键安全领域。
当前 openFuyao 平台缺乏统一的合规扫描能力,运维人员需要手动在各节点执行 kube-bench 或 oscap 命令,收集分散的结果文件,再人工汇总为报告。这种方式存在以下问题:
- 操作复杂度高:需要 SSH 登录每个节点,手动执行扫描命令,收集结果文件。
- 缺乏统一视图:不同节点的扫描结果格式各异,无法在集群层面统一查看合规状态。
- 无法追溯历史:扫描结果散落在各节点本地,无法方便地对比不同时间点的合规变化趋势。
- 缺乏自动化:无法与 CI/CD 流水线或定时任务集成,难以实现持续合规监控。
compliance-operator 旨在解决上述问题,通过 Kubernetes Operator 模式提供声明式的合规扫描能力。
1.2 设计目标
- 声明式扫描:用户通过创建/更新 CRD 即可触发扫描,无需 SSH 登录节点。
- 双引擎支持:同时支持 kube-bench(CIS 基准)和 OpenSCAP(STIG 规则)两种扫描引擎。
- 统一结果模型:将两种引擎的不同输出格式统一为通用的检查结果模型。
- 历史可追溯:保留最近 5 次扫描历史,支持按 scanID 精准导出历史报告。
- 最小权限:扫描 Pod 以最小权限运行,hostPath 仅只读挂载。
- etcd 友好:结果存储策略充分考虑 etcd 1.5MB 对象大小限制。
1.3 非目标
- 不定义 CIS/STIG 规则内容本身,仅定义运行框架与数据模型。
- 不支持无限历史保留,固定为最近 5 次。
- 不提供 Web UI,全命令行操作。
- 不支持自动修复(remediation),仅提供修复建议。
- 不支持跨集群扫描。
1.4 与 oFEP-0003 的关系
本文档是 oFEP-0003 提案的详细设计细化。在 oFEP-0003 定义的总体框架基础上,本设计进行了以下关键改进:
| 改进点 | oFEP-0003 初始设计 | 本详细设计 |
|---|---|---|
| CRD 结构 | 单一 ScannerTask CRD | 拆分为 ComplianceScan + ComplianceScanResult + ComplianceScanNodeResult 三个 CRD |
| 拆分原因 | 未考虑 etcd 对象大小限制 | ComplianceScanResult 存储轻量聚合摘要(NodeSummaries),ComplianceScanNodeResult 存储单节点详细结果(NonPassResults),避免单个 CR 膨胀超过 etcd 1.5MB 限制 |
| 扫描引擎选择 | spec 中未明确引擎字段 | 新增 spec.scanner 字段显式选择 kube-bench 或 openscap |
| 触发机制 | 仅注解触发 | 支持注解触发、CR 创建触发、CronJob 定时触发三种方式 |
| 报告导出 | 子资源式 API(需聚合 API Server) | Operator 内置 report 子命令(更简单,无需聚合 API) |
| 扫描执行单元 | Job/Pod 未明确 | 明确使用 Kubernetes Job,利用其重试/超时/清理语义 |
| 节点调度 | 未详细说明 | 按节点类型(control-plane/worker)分别创建 Job |
| 状态管理 | 简单状态字段 | 完整的状态机(PENDING -> LAUNCHING -> RUNNING -> COLLECTING -> DONE,含 IGNORED 单例保护) |
1.5 术语定义
| 术语 | 说明 |
|---|---|
| CIS Benchmark | Center for Internet Security 发布的 Kubernetes 安全基线检查标准 |
| STIG | Security Technical Implementation Guide,DISA 发布的安全技术实施指南 |
| XCCDF | Extensible Configuration Checklist Description Format,安全配置检查清单描述格式 |
| OVAL | Open Vulnerability and Assessment Language,开放漏洞评估语言 |
| ARF | Asset Reporting Format,资产报告格式 |
| kube-bench | Aqua Security 开源的 CIS Kubernetes Benchmark 扫描工具 |
| OpenSCAP | 开源的 SCAP 合规扫描引擎,支持 XCCDF/OVAL 标准 |
| scanID | 每次扫描的唯一标识符,格式为时间戳+随机串(如 20260528-143022-a7f3) |
2. 架构设计 -- 4+1 视图
2.1 逻辑视图
2.1.1 核心组件
系统由三个核心逻辑组件构成:

Compliance Operator Controller Manager
核心控制器,运行在集群中作为 Deployment 部署。职责包括:
- Watch ComplianceScan CRD 的创建、更新、注解变更事件
- 管理扫描生命周期状态机(PENDING -> LAUNCHING -> RUNNING -> COLLECTING -> DONE)
- 根据 spec.scanner 和 spec.scanType 创建对应的 Scan Job
- 监控 Scan Job 执行状态,回收扫描结果
- 将聚合摘要写入 ComplianceScanResult CRD,将单节点详细结果写入 ComplianceScanNodeResult CRD
- 管理扫描历史(最多保留 5 条,超出则淘汰最旧记录)
- 处理定时扫描调度(基于 spec.schedule 字段)
Scan Job
一次性 Kubernetes Job,由 Controller 动态创建,运行在目标节点上。职责包括:
- kube-bench:直接使用镜像内置的扫描工具二进制文件
- OpenSCAP:使用镜像内置的 SCAP 规则文件(/scap-content/scap-rule.xml),无需额外加载
- 主容器执行扫描命令(kube-bench 或 oscap)
- 将扫描结果输出到 stdout(供 Operator 通过 Pod logs API 读取)
- 同时将结果写入共享卷
/results/作为备份(供日志解析失败时兜底读取)
结果回收(内嵌于 Controller)
结果回收逻辑内嵌于 Compliance Operator Controller 的 Collecting 阶段,不依赖额外的 sidecar 或独立 Pod。职责包括:
- 通过 Kubernetes API 读取已完成 Scan Job Pod 的 stdout 日志
- 解析并转换为 UnifiedCheckResult 统一模型
- 通过 Kubernetes API 创建 ComplianceScanNodeResult CRD(单节点详细结果)和 ComplianceScanResult CRD(聚合摘要)
- 设置 OwnerReference 指向对应的 ComplianceScan
- 若日志解析失败,创建临时 debug Pod 从共享卷读取备份文件
2.1.2 CRD 层级关系
ComplianceScan (Cluster-scoped, 长期存在)
|
|-- spec: 扫描配置(scanner, scanType, profile, schedule...)
|-- status: 状态信息(phase, lastScanTime, scanHistory, activeScanner...)
|
|--[OwnerReference]--> ComplianceScanResult #1 (最近一次, 含 nodeSummaries 轻量摘要)
|--[OwnerReference]--> ComplianceScanResult #2
|--[OwnerReference]--> ComplianceScanResult #3
|--[OwnerReference]--> ComplianceScanResult #4
|--[OwnerReference]--> ComplianceScanResult #5 (最旧)
|
|--[OwnerReference]--> ComplianceScanNodeResult #1-<nodeHash> (单节点详细结果)
|--[OwnerReference]--> ComplianceScanNodeResult #2-<nodeHash>
|--[OwnerReference]--> ComplianceScanNodeResult #N-<nodeHash>
|
|--[OwnerReference]--> Job/<scanName>-<scanID>-<nodeName>
|--[OwnerReference]--> Job/<scanName>-<scanID>-<nodeName>
三级 CRD 职责分工:
| CRD | 职责 | 数据量 |
|---|---|---|
| ComplianceScan | 扫描配置 + 状态 + 历史 | < 10KB |
| ComplianceScanResult | 单次扫描聚合摘要(NodeSummaries + 全局 Summary) | < 50KB |
| ComplianceScanNodeResult | 单节点详细结果(NonPassResults + PassCount) | 每节点 < 100KB |
2.1.3 数据流向

2.2 开发视图
2.2.1 项目目录结构
compliance-operator/
|-- cmd/
| |-- manager/
| |-- main.go # Operator 入口(含 manager 和 report 子命令)
|
|-- api/
| |-- v1alpha1/
| |-- compliancescan_types.go # ComplianceScan CRD 类型定义
| |-- compliancescanresult_types.go # ComplianceScanResult CRD 类型定义(聚合摘要)
| |-- compliancescannoderesult_types.go # ComplianceScanNodeResult CRD 类型定义(单节点详细结果)
| |-- groupversion_info.go # API Group 注册
| |-- zz_generated.deepcopy.go # 自动生成的 DeepCopy 方法
|
|-- controllers/
| |-- compliancescan_controller.go # 主 Reconciler
| |-- scan_job_builder.go # Job Spec 构造器
| |-- result_collector.go # 结果回收逻辑
| |-- history_manager.go # 历史记录管理(淘汰策略)
| |-- schedule_manager.go # 定时扫描调度
|
|-- pkg/
| |-- scanner/
| | |-- interface.go # Scanner 接口定义
| | |-- kubebench/
| | | |-- parser.go # kube-bench JSON 输出解析
| | |-- openscap/
| | |-- parser.go # OpenSCAP XCCDF Results XML 解析
| |
| |-- report/
| | |-- knowledge.go # 规则知识库(KnowledgeBase)
| | |-- cis_loader.go # CIS Benchmark 规则加载(go:embed YAML)
| | |-- stig_loader.go # STIG 规则加载(go:embed XCCDF XML)
| | |-- generator.go # HTML 报告生成
| | |-- template.go # HTML 模板定义
| |
| |-- unified/
| | |-- model.go # UnifiedCheckResult 模型
| |
| |-- util/
| |-- node.go # 节点选择工具
| |-- scanid.go # scanID 生成
|
|-- config/
| |-- crd/
| | |-- bases/ # CRD YAML(3 个 CRD 定义)
| |-- rbac/
| | |-- role.yaml # Operator ClusterRole
| | |-- scan_job_role.yaml # Scan Job ClusterRole
| | |-- role_binding.yaml # ClusterRoleBinding
| | |-- service_account.yaml # ServiceAccount
| |-- manager/
| | |-- manager.yaml # Deployment
| |-- samples/
| |-- compliance_v1alpha1_compliancescan_kubebench.yaml
| |-- compliance_v1alpha1_compliancescan_openscap.yaml
|
|-- charts/
| |-- compliance-operator/
| |-- Chart.yaml
| |-- values.yaml
| |-- crds/ # CRD YAML(Helm 安装时自动加载)
| |-- templates/ # 部署模板(含 deployment, RBAC, service 等)
|
|-- docs/
| |-- detailed-design.md # 本文档
| |-- user_guide.md # 用户指南
| |-- developer_guide.md # 开发者指南
| |-- diagrams/ # 架构图
|
|-- build/
| |-- Dockerfile # Operator 镜像构建文件
|
|-- hack/
| |-- boilerplate.go.txt # Kubebuilder 代码头模板
|
|-- Makefile
|-- go.mod
|-- go.sum
2.2.2 模块依赖关系
cmd/manager/main.go
|-- cmd/manager/client.go # K8sClient 封装(report 子命令使用)
|
|-- controllers/compliancescan_controller.go # 主 Reconciler
| |-- pkg/scanner/interface.go
| | |-- pkg/scanner/kubebench/
| | |-- pkg/scanner/openscap/
| |-- pkg/unified/model.go
| |-- controllers/scan_job_builder.go # Job Spec 构造器
| |-- controllers/result_collector.go # 结果回收逻辑
| |-- controllers/history_manager.go # 历史记录管理
| |-- controllers/schedule_manager.go # 定时扫描调度
| |-- controllers/metrics.go # Prometheus 指标定义
|
|-- pkg/report/ (report 子命令)
| |-- generator.go
| |-- template.go
| |-- knowledge.go # KnowledgeBase 规则知识库
| |-- cis_loader.go # CIS 规则加载(go:embed embed/cis/*/*.yaml)
| |-- stig_loader.go # STIG 规则加载(go:embed embed/stig/*/*.xml)
|
|-- api/v1alpha1/ (CRD 类型定义)
2.2.3 外部依赖
| 依赖 | 版本 | 用途 |
|---|---|---|
| kubernetes-sigs/controller-runtime | v0.19.0 | Operator 框架 |
| k8s.io/api, k8s.io/apimachinery, k8s.io/client-go | v0.32.0 | Kubernetes API 类型与客户端 |
| github.com/robfig/cron/v3 | v3.0.1 | Cron 表达式解析 |
| golang.org/x/sync | v0.8.0 | errgroup 并发控制(Job 并行创建、结果并行回收) |
| github.com/prometheus/client_golang | v1.19.1 | Prometheus 指标暴露 |
| gopkg.in/yaml.v3 | v3.0.1 | CIS Benchmark YAML 规则文件解析 |
| encoding/xml (标准库) | - | XCCDF/ARF XML 解析 |
| encoding/json (标准库) | - | kube-bench JSON 解析 |
| html/template (标准库) | - | HTML 报告渲染 |
2.3 进程视图
2.3.1 扫描生命周期状态机

状态转换规则:
| 当前状态 | 触发条件 | 目标状态 |
|---|---|---|
| (初始) | CR 创建 / 注解触发 / Cron 触发 | PENDING |
| (初始) | 集群中已有其他活跃的 ComplianceScan(单例保护) | IGNORED |
| PENDING | Reconciler 开始处理 | LAUNCHING |
| LAUNCHING | Job 创建成功 | RUNNING |
| RUNNING | Job 执行成功完成 | COLLECTING |
| RUNNING | Job 执行失败或超时 | FAILED |
| COLLECTING | 结果写入 ComplianceScanNodeResult + ComplianceScanResult 成功 | DONE |
| COLLECTING | 结果写入失败 | FAILED |
| DONE | 注解触发新一轮扫描 | PENDING |
| FAILED | 注解触发重新扫描 | PENDING |
| IGNORED | 活跃 CR 被删除,本 CR 变为最旧 | PENDING |
2.3.2 并发控制
- 单 ComplianceScan 串行:同一个 ComplianceScan CR 同一时间只能有一个活跃的扫描流程。当 status.phase 不是 DONE 或 FAILED 时,新的触发请求会被忽略并记录 Event。
- 集群级并发限制:通过 Controller 的 MaxConcurrentReconciles 参数控制同时处理的 ComplianceScan 数量,默认值为 1。由于单例保护机制(同一时间仅最旧的 ComplianceScan CR 被活跃协调),默认并发数为 1 即可满足需求。
- Job 数量:一次扫描可能创建多个 Job(每个目标节点一个),但由同一个 ComplianceScan 管理。
2.3.3 结果回收流程
结果回收采用 Pod logs 主通道 + 文件备份兜底 的双通道机制,通过 4 层防护确保可靠性:

为什么不用 sidecar collector:
- sidecar 需要额外的 K8s API 权限来写入 CRD,违反最小权限原则
- sidecar 增加了 Pod 复杂度和资源消耗
- Pod logs API 是 K8s 原生机制,简单可靠
- Operator 已经在集群内运行,有完整的 RBAC 权限
Pod logs 可靠性分析:
| 风险 | 严重度 | 触发条件 | 防护措施 |
|---|---|---|---|
| kubelet 日志轮转截断 | 低 | containerLogMaxSize 默认 10MB,我们的输出 < 1MB |
Layer 4 大小阈值检测 |
| API Server 响应截断 | 低 | --max-request-body-size 默认 3MB+,go client 默认 5MB buffer |
Layer 2 格式校验 |
| 扫描器输出非 JSON 前缀 | 中 | kube-bench 可能在 JSON 前输出 warning 行 | Layer 1 标记提取 |
| Pod 被驱逐/节点宕机 | 中 | 日志丢失 | Layer 3 文件备份 |
多节点结果汇总流程:
Node-1 Job 完成 ──> Operator 读取 Pod-1 日志 ──> 解析为 CollectedNodeData-1
Node-2 Job 完成 ──> Operator 读取 Pod-2 日志 ──> 解析为 CollectedNodeData-2
Node-N Job 完成 ──> Operator 读取 Pod-N 日志 ──> 解析为 CollectedNodeData-N
│
v
所有 Job 完成后, Collecting 阶段:
1. 为每个节点创建 ComplianceScanNodeResult CR
(存储 NonPassResults + PassCount, 不存储 PASS 详情)
2. 构建 NodeSummary 列表 (轻量摘要 + resultRef 指向 NodeResult)
3. 计算全局 Summary (pass/fail/warn/error/notApplicable/notChecked 累加)
4. 创建 ComplianceScanResult CR (含 NodeSummaries + 全局 Summary)
2.4 物理视图
2.4.1 部署拓扑

2.4.2 网络通信
| 通信路径 | 协议 | 说明 |
|---|---|---|
| Controller -> K8s API Server | HTTPS | Watch CRD 事件、创建/删除 Job、更新 Status、读取 Pod logs |
| Controller -> Scan Job Pod | HTTPS (pods/log) | 通过 K8s API 读取 Pod stdout 日志回收扫描结果 |
| Scan Job Pod -> 节点文件系统 | hostPath (只读) | 读取配置文件、证书、进程信息 |
| Pod 间通信 | 无 | 各 Job Pod 独立运行,不互相通信 |
2.4.3 存储策略
- CRD 存储:所有 ComplianceScan、ComplianceScanResult 和 ComplianceScanNodeResult 资源存储在 etcd 中。
- 对象大小控制:
- ComplianceScan CR:仅存储配置和状态摘要,预计 < 10KB。
- ComplianceScanResult CR:存储单次扫描的聚合摘要(NodeSummaries 轻量列表 + 全局 Summary),不含详细检查结果。NodeSummaries 每项仅含 nodeName、nodeType、summary 统计和 resultRef 引用,预计 < 50KB。
- ComplianceScanNodeResult CR:存储单节点的详细结果。仅存储 NonPassResults(FAIL、WARN、ERROR、NOT_APPLICABLE 等非 PASS 结果),PASS 项仅记录 PassCount 计数。kube-bench 单节点非 PASS 结果通常 < 50 条,每条约 80 字节,总计 < 10KB。OpenSCAP 单节点非 PASS 结果通常 < 20 条,总计 < 5KB。均在 etcd 1.5MB 限制内。
- rawResult 字段(可选):存储压缩后的原始输出,使用 gzip + base64 编码。若超过 1MB 则截断并标记。
- 临时存储:Scan Job Pod 使用 emptyDir 共享卷作为结果文件备份通道。
2.4.4 节点调度策略
ComplianceScan 需要确保扫描 Job 能够部署到集群中所有目标节点上。调度策略采用 nodeName 直接绑定 + tolerations 容忍 的方式:
节点发现流程:
Controller Pending 阶段:
|
|-- 1. 调用 K8s API: GET /api/v1/nodes
|
|-- 2. 根据 spec.scanType 过滤节点:
| |-- control-plane: label "node-role.kubernetes.io/control-plane" 存在
| |-- worker: label "node-role.kubernetes.io/worker" 存在 (或无 control-plane label)
| |-- all: 所有 Ready 状态的节点
|
|-- 3. 进一步过滤: spec.nodeSelector 自定义标签匹配
|
|-- 4. 排除: 状态为 NotReady/SchedulingDisabled 的节点
|
|-- 5. 为每个符合条件的节点创建一个 Job
| |-- Job.spec.template.spec.nodeName = <nodeName> (硬绑定)
| |-- Job.spec.template.spec.tolerations = 默认容忍 + spec.tolerations
|
v
每个 Job 被调度到指定节点执行
调度保障机制:
| 机制 | 说明 | 作用 |
|---|---|---|
nodeName 字段 |
绕过调度器直接绑定到指定节点 | 不受调度策略影响,确保 Job 运行在目标节点 |
tolerations: [{operator: Exists}] |
容忍所有污点 | 包括 master 节点的 NoSchedule、自定义污点 |
backoffLimit: 1 |
Job 允许一次重试 | 节点临时不可用时自动重试 |
hostPID: true |
访问主机进程命名空间 | kube-bench 需要查看主机进程信息 |
默认 tolerations 配置:
tolerations:
# 控制平面节点
- key: node-role.kubernetes.io/control-plane
operator: Exists
effect: NoSchedule
- key: node-role.kubernetes.io/master
operator: Exists
effect: NoSchedule
# 用户自定义 tolerations (来自 spec.tolerations)
节点类型识别:
| 节点类型 | 识别方式 | 扫描目标 (kube-bench) |
|---|---|---|
| Control Plane | label node-role.kubernetes.io/control-plane 存在 |
master,controlplane,etcd,policies |
| Worker | label node-role.kubernetes.io/worker 存在,或无 control-plane label |
node |
| etcd (独立) | label node-role.kubernetes.io/etcd 存在 |
etcd |
2.5 场景视图
2.5.1 场景 1:用户使用 kube-bench 扫描 CIS 基准
前置条件:集群已部署 compliance-operator。
操作步骤:
# 1. 创建 ComplianceScan CR
kubectl apply -f - <<EOF
apiVersion: compliance.openfuyao.cn/v1alpha1
kind: ComplianceScan
metadata:
name: cis-benchmark-scan
spec:
scanner: kube-bench
scanType: all
benchmark: cis-1.8
timeout: 30m
maxHistory: 5
EOF
# 2. 查看扫描状态
kubectl get compliancescan cis-benchmark-scan -w
# 3. 扫描完成后查看结果摘要
kubectl get compliancescan cis-benchmark-scan -o wide
# 4. 查看扫描历史
kubectl get compliancescan cis-benchmark-scan -o jsonpath='{.status.scanHistory}'
# 5. 导出指定扫描 ID 的报告
kubectl exec -n compliance-operator-system deploy/compliance-operator -- \
/compliance-operator report cis-benchmark-scan --scan-id 20260528-143022-a7f3 > report.html
系统行为:
- Controller 检测到新 CR 创建,进入 PENDING 状态。
- Controller 查询集群节点,识别 control-plane 和 worker 节点。
- 为每个 control-plane 节点创建一个 Job(targets: master,controlplane,etcd,policies)。
- 为每个 worker 节点创建一个 Job(targets: node)。
- Job Pod 在目标节点上执行
kube-bench run --targets <targets> --json --benchmark cis-1.8。 - Controller 在 Collecting 阶段读取 Pod logs,解析 JSON 输出,创建 ComplianceScanResult CRD。
- Controller 更新 ComplianceScan 状态为 DONE。
2.5.2 场景 2:用户使用 OpenSCAP 扫描 STIG 规则
操作步骤:
kubectl apply -f - <<EOF
apiVersion: compliance.openfuyao.cn/v1alpha1
kind: ComplianceScan
metadata:
name: stig-scan
spec:
scanner: openscap
scanType: control-plane
profile: xccdf_mil.disa.stig_profile_MAC-1_Classified
timeout: 45m
maxHistory: 5
EOF
系统行为:
- Controller 检测到新 CR,进入 PENDING。
- 仅选择 control-plane 节点(scanType: control-plane)。
- 为每个 control-plane 节点创建一个 Job。
- 主容器执行
OSCAP_PROBE_ROOT=/host oscap xccdf eval --profile <profile> --results-arf /results/arf.xml --results /results/results.xml /scap-content/scap-rule.xml。 - Controller 在 Collecting 阶段读取 Pod logs,解析 XCCDF Results XML,创建 ComplianceScanResult。
- Controller 更新状态为 DONE。
2.5.3 场景 3:用户查看扫描历史并导出报告
# 查看扫描历史列表
kubectl get compliancescan stig-scan \
-o jsonpath='{range .status.scanHistory[*]}{.scanID}{"\t"}{.scanTime}{"\t"}{.result}{"\t"}{.summary.pass}/{.summary.total}{"\n"}{end}'
# 输出示例:
# 20260528-143022-a7f3 2026-05-28T14:30:22Z NonCompliant 42/55
# 20260521-100015-b2c1 2026-05-21T10:00:15Z NonCompliant 40/55
# 20260514-100012-d4e5 2026-05-14T10:00:12Z Compliant 55/55
# 导出特定历史报告
kubectl exec -n compliance-operator-system deploy/compliance-operator -- \
/compliance-operator report stig-scan --scan-id 20260528-143022-a7f3 > stig-report-20260528.html
# 导出最新报告(不指定 scan-id)
kubectl exec -n compliance-operator-system deploy/compliance-operator -- \
/compliance-operator report stig-scan > stig-report-latest.html
2.5.4 场景 4:定时扫描(CronJob 触发)
kubectl apply -f - <<EOF
apiVersion: compliance.openfuyao.cn/v1alpha1
kind: ComplianceScan
metadata:
name: weekly-cis-scan
spec:
scanner: kube-bench
scanType: all
benchmark: cis-1.8
schedule: "0 2 * * 0"
timeout: 30m
maxHistory: 5
EOF
系统行为:
- Controller 创建 CR 后立即执行一次扫描。
- Controller 内部维护一个 cron 调度器,根据 spec.schedule 字段注册定时任务。
- 每次定时触发时,Controller 将 ComplianceScan 状态重置为 PENDING 并启动新一轮扫描。
- 扫描完成后自动追加历史记录,超过 maxHistory 时淘汰最旧记录。
3. CRD 完整设计
3.1 ComplianceScan (Cluster-scoped)
3.1.1 Go 类型定义
// ComplianceScanSpec 定义扫描配置
// 注意:代码中使用 typed enum(ScannerType, ScanTargetType, ScanResult)而非 plain string
type ComplianceScanSpec struct {
// scanner 指定扫描引擎类型(ScannerType typed enum)
// +kubebuilder:validation:Enum=kube-bench;openscap
// +kubebuilder:validation:Required
Scanner ScannerType `json:"scanner"`
// scanType 指定扫描目标节点类型(ScanTargetType typed enum)
// +kubebuilder:validation:Enum=control-plane;worker;all
// +kubebuilder:default=all
// +optional
ScanType ScanTargetType `json:"scanType,omitempty"`
// profile 指定 OpenSCAP 扫描使用的 XCCDF Profile ID
// 仅当 scanner=openscap 时有效
// 示例: "xccdf_mil.disa.stig_profile_MAC-1_Classified"
// +optional
Profile string `json:"profile,omitempty"`
// benchmark 指定 kube-bench 使用的 CIS Benchmark 版本
// 仅当 scanner=kube-bench 时有效
// 若为空则根据 K8s 版本自动检测
// 示例: "cis-1.8"
// +optional
Benchmark string `json:"benchmark,omitempty"`
// schedule 定义定时扫描的 Cron 表达式
// 若为空则仅在 CR 创建或注解触发时执行扫描
// 示例: "0 2 * * 0" (每周日凌晨 2 点)
// +optional
Schedule string `json:"schedule,omitempty"`
// nodeSelector 覆盖默认的节点选择策略
// 若为空则根据 scanType 使用默认 label selector
// +optional
NodeSelector map[string]string `json:"nodeSelector,omitempty"`
// tolerations 指定 Scan Job Pod 的容忍策略
// 用于调度到带有特殊污点的节点(如 control-plane 节点)
// +optional
Tolerations []corev1.Toleration `json:"tolerations,omitempty"`
// timeout 指定扫描超时时间(指针类型,区分"未设置"和"零值")
// +kubebuilder:default="30m"
// +optional
Timeout *metav1.Duration `json:"timeout,omitempty"`
// maxHistory 指定保留的最大扫描历史记录数
// +kubebuilder:validation:Minimum=1
// +kubebuilder:validation:Maximum=20
// +kubebuilder:default=5
// +optional
MaxHistory int `json:"maxHistory,omitempty"`
// imagePullPolicy 指定扫描器镜像的拉取策略(corev1.PullPolicy typed enum)
// +kubebuilder:validation:Enum=Always;IfNotPresent;Never
// +kubebuilder:default=IfNotPresent
// +optional
ImagePullPolicy corev1.PullPolicy `json:"imagePullPolicy,omitempty"`
// imagePullSecrets 指定私有仓库认证 Secret 引用列表
// +optional
ImagePullSecrets []corev1.LocalObjectReference `json:"imagePullSecrets,omitempty"`
// resources 指定扫描容器的资源需求覆盖
// 若为空则使用默认值(CPU: 100m/500m, Memory: 128Mi/512Mi)
// +optional
Resources *corev1.ResourceRequirements `json:"resources,omitempty"`
// priorityClassName 指定扫描 Job Pod 的优先级类名
// +optional
PriorityClassName string `json:"priorityClassName,omitempty"`
// jobTTL 指定已完成的扫描 Job 保留多长时间后清理
// 用于调试:设为 "1h" 可在完成后查看 Job 日志
// 若为空或 "0s",Job 在结果回收后立即清理
// 示例: "30m", "1h", "24h"
// +optional
JobTTL *metav1.Duration `json:"jobTTL,omitempty"`
}
// ComplianceScanStatus 定义扫描状态
type ComplianceScanStatus struct {
// phase 表示当前扫描生命周期阶段
// +kubebuilder:validation:Enum=Pending;Launching;Running;Collecting;Done;Failed;Ignored
Phase ScanPhase `json:"phase,omitempty"`
// lastScanTime 最近一次扫描的启动时间
// +optional
LastScanTime *metav1.Time `json:"lastScanTime,omitempty"`
// lastScanID 最近一次扫描的唯一标识
// +optional
LastScanID string `json:"lastScanID,omitempty"`
// lastScanResult 最近一次扫描的总体结果(ScanResult typed enum)
// +kubebuilder:validation:Enum=Compliant;NonCompliant;Error
// +optional
LastScanResult ScanResult `json:"lastScanResult,omitempty"`
// scanHistory 扫描历史记录列表(按时间倒序,最多 maxHistory 条)
// +optional
ScanHistory []ScanHistoryEntry `json:"scanHistory,omitempty"`
// conditions 标准 Kubernetes Conditions
// +optional
Conditions []metav1.Condition `json:"conditions,omitempty"`
// activeJobs 当前活跃的 Scan Job 数量
ActiveJobs int32 `json:"activeJobs,omitempty"`
// activeScanner 是当前扫描周期实际使用的扫描器类型
// 在 Pending -> Launching 转换时从 Spec.Scanner 快照,
// 在整个扫描周期内不可变。这解耦了解析器选择与 Spec.Scanner,
// 防止用户在扫描进行中修改 scanner 类型时导致解析器不匹配。
// +optional
ActiveScanner ScannerType `json:"activeScanner,omitempty"`
// nextScheduleTime 下次定时扫描时间(仅当 schedule 不为空时有值)
// +optional
NextScheduleTime *metav1.Time `json:"nextScheduleTime,omitempty"`
}
// ScanPhase 定义扫描阶段枚举(typed enum,非 plain string)
type ScanPhase string
const (
PhasePending ScanPhase = "Pending"
PhaseLaunching ScanPhase = "Launching"
PhaseRunning ScanPhase = "Running"
PhaseCollecting ScanPhase = "Collecting"
PhaseDone ScanPhase = "Done"
PhaseFailed ScanPhase = "Failed"
// PhaseIgnored 表示该 CR 未被处理,因为集群中已存在另一个活跃的
// ComplianceScan CR(单例模式)。当活跃 CR 被删除后,最旧的 Ignored
// CR 自动转换为 Pending。
PhaseIgnored ScanPhase = "Ignored"
)
// ScannerType 定义扫描引擎类型(typed enum)
type ScannerType string
const (
ScannerKubeBench ScannerType = "kube-bench"
ScannerOpenSCAP ScannerType = "openscap"
)
// ScanTargetType 定义扫描目标节点类型(typed enum)
type ScanTargetType string
const (
ScanTargetControlPlane ScanTargetType = "control-plane"
ScanTargetWorker ScanTargetType = "worker"
ScanTargetAll ScanTargetType = "all"
)
// ScanResult 定义扫描总体结果(typed enum)
type ScanResult string
const (
ResultCompliant ScanResult = "Compliant"
ResultNonCompliant ScanResult = "NonCompliant"
ResultError ScanResult = "Error"
)
// ScanHistoryEntry 定义单条历史记录
type ScanHistoryEntry struct {
// scanID 扫描唯一标识
ScanID string `json:"scanID"`
// scanTime 扫描启动时间
ScanTime metav1.Time `json:"scanTime"`
// phase 扫描最终状态(ScanPhase typed enum)
Phase ScanPhase `json:"phase"`
// result 扫描结果(ScanResult typed enum)
Result ScanResult `json:"result"`
// summary 扫描结果摘要(所有节点汇总,8 个字段)
Summary ScanSummary `json:"summary"`
// resultRef 指向 ComplianceScanResult 资源的名称
ResultRef string `json:"resultRef"`
}
// ScanSummary 定义扫描结果统计摘要(8 个字段)
type ScanSummary struct {
Pass int `json:"pass"`
Fail int `json:"fail"`
Warn int `json:"warn"`
Info int `json:"info"`
Error int `json:"error"`
Total int `json:"total"`
NotApplicable int `json:"notApplicable"`
NotChecked int `json:"notChecked"`
}
// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// +kubebuilder:resource:scope=Cluster,shortName=cs
// +kubebuilder:printcolumn:name="Scanner",type=string,JSONPath=`.spec.scanner`
// +kubebuilder:printcolumn:name="Phase",type=string,JSONPath=`.status.phase`
// +kubebuilder:printcolumn:name="Result",type=string,JSONPath=`.status.lastScanResult`
// +kubebuilder:printcolumn:name="LastScan",type=date,JSONPath=`.status.lastScanTime`
// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`
type ComplianceScan struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec ComplianceScanSpec `json:"spec,omitempty"`
Status ComplianceScanStatus `json:"status,omitempty"`
}
3.1.2 字段说明表格
Spec 字段:
| 字段名 | 类型 | 必填 | 默认值 | 说明 | 示例值 |
|---|---|---|---|---|---|
| scanner | ScannerType | 是 | - | 扫描引擎类型(typed enum),kube-bench 或 openscap | kube-bench |
| scanType | ScanTargetType | 否 | all |
扫描目标节点类型(typed enum) | control-plane |
| profile | string | 否 | - | OpenSCAP XCCDF Profile ID | xccdf_mil.disa.stig_profile_MAC-1_Classified |
| benchmark | string | 否 | 自动检测 | kube-bench CIS Benchmark 版本 | cis-1.8 |
| schedule | string | 否 | - | Cron 表达式,定时扫描 | 0 2 * * 0 |
| nodeSelector | map[string]string | 否 | 根据 scanType 自动选择 | 节点标签选择器 | {"kubernetes.io/os": "linux"} |
| tolerations | []Toleration | 否 | 包含 control-plane 容忍 | Pod 容忍策略 | 见下文 |
| timeout | *Duration | 否 | 30m |
扫描超时时间(指针类型) | 45m |
| maxHistory | int | 否 | 5 |
最大历史记录数(1-20) | 5 |
| imagePullPolicy | corev1.PullPolicy | 否 | IfNotPresent |
镜像拉取策略(typed enum):Always、IfNotPresent、Never | Always |
| imagePullSecrets | []LocalObjectReference | 否 | - | 私有仓库认证 Secret 引用列表 | [{name: harbor-secret}] |
| resources | *ResourceRequirements | 否 | CPU: 100m/500m, Memory: 128Mi/512Mi | 扫描容器资源需求覆盖 | 见下文 |
| priorityClassName | string | 否 | - | 扫描 Job Pod 优先级类名 | compliance-scan-low |
| jobTTL | *Duration | 否 | 立即清理 | 已完成 Job 保留时间,用于调试(如 "30m", "1h") | 1h |
Status 字段:
| 字段名 | 类型 | 说明 |
|---|---|---|
| phase | ScanPhase | 当前生命周期阶段(含 Ignored 单例保护状态) |
| lastScanTime | *Time | 最近扫描启动时间 |
| lastScanID | string | 最近扫描 ID |
| lastScanResult | ScanResult | 最近扫描结果(Compliant/NonCompliant/Error) |
| scanHistory | []ScanHistoryEntry | 历史记录列表(每条记录包含该次扫描的全局摘要和 ComplianceScanResult 引用) |
| conditions | []Condition | 标准 Conditions |
| activeJobs | int32 | 活跃 Job 数量 |
| activeScanner | ScannerType | 当前扫描周期实际使用的扫描器类型(Pending→Launching 时快照,防止 mid-scan 变更) |
| nextScheduleTime | *Time | 下次定时扫描时间 |
3.1.3 用户 CR 示例
# 示例 1: kube-bench CIS 扫描(全节点)
apiVersion: compliance.openfuyao.cn/v1alpha1
kind: ComplianceScan
metadata:
name: cis-full-scan
spec:
scanner: kube-bench
scanType: all
benchmark: cis-1.8
timeout: 30m
maxHistory: 5
---
# 示例 2: OpenSCAP STIG 扫描(仅控制平面,定时执行)
apiVersion: compliance.openfuyao.cn/v1alpha1
kind: ComplianceScan
metadata:
name: stig-control-plane
spec:
scanner: openscap
scanType: control-plane
profile: xccdf_mil.disa.stig_profile_MAC-1_Classified
schedule: "0 3 * * 1"
timeout: 45m
maxHistory: 5
tolerations:
- key: node-role.kubernetes.io/control-plane
operator: Exists
effect: NoSchedule
3.2 ComplianceScanResult (Cluster-scoped)
3.2.1 Go 类型定义
// ComplianceScanResultSpec 定义扫描聚合摘要
// 详细 per-node 结果存储在独立的 ComplianceScanNodeResult 资源中
type ComplianceScanResultSpec struct {
// schemaVersion 用于未来 schema 迁移的版本标识
// +kubebuilder:default="v1alpha1"
// +optional
SchemaVersion string `json:"schemaVersion,omitempty"`
// scanID 关联的扫描唯一标识
// +kubebuilder:validation:Pattern=`^[a-z0-9-]+$`
// +kubebuilder:validation:MaxLength=63
ScanID string `json:"scanID"`
// scanName 关联的 ComplianceScan 资源名称
ScanName string `json:"scanName"`
// scanner 使用的扫描引擎(ScannerType typed enum)
// +kubebuilder:validation:Enum=kube-bench;openscap
Scanner ScannerType `json:"scanner"`
// benchmark 使用的基准版本
// kube-bench: "cis-1.8", "cis-1.9" 等
// openscap: "stig-v2r4" 等
// +optional
Benchmark string `json:"benchmark,omitempty"`
// profile 使用的 Profile(仅 openscap)
// +optional
Profile string `json:"profile,omitempty"`
// nodeCount 本次扫描的节点总数
NodeCount int `json:"nodeCount"`
// nodeSummaries 轻量级 per-node 概览信息列表
// 详细检查结果存储在 ComplianceScanNodeResult 资源中
// +optional
NodeSummaries []NodeSummary `json:"nodeSummaries,omitempty"`
// summary 全局扫描结果统计摘要(所有节点汇总)
Summary ScanSummary `json:"summary"`
// rawResult 压缩后的原始扫描输出(gzip + base64 编码)
// 用于调试和审计,可选字段
// +optional
RawResult string `json:"rawResult,omitempty"`
// scanStartTime 扫描开始时间
ScanStartTime metav1.Time `json:"scanStartTime"`
// scanEndTime 扫描结束时间
ScanEndTime metav1.Time `json:"scanEndTime"`
}
// NodeSummary 包含单个节点的轻量级概览信息
// 详细检查结果存储在引用的 ComplianceScanNodeResult 资源中
type NodeSummary struct {
// nodeName 节点名称
NodeName string `json:"nodeName"`
// nodeType 节点类型
// +kubebuilder:validation:Enum=control-plane;worker
NodeType string `json:"nodeType"`
// summary 该节点的结果统计摘要
Summary ScanSummary `json:"summary"`
// resultRef 指向 ComplianceScanNodeResult 资源的名称
ResultRef string `json:"resultRef"`
}
// ComplianceScanResultStatus 定义扫描结果的状态信息
type ComplianceScanResultStatus struct {
// phase 结果处理阶段
// +kubebuilder:validation:Enum=Pending;Processing;Completed;Failed
Phase string `json:"phase,omitempty"`
// completionTime 结果处理完成时间
// +optional
CompletionTime *metav1.Time `json:"completionTime,omitempty"`
// message 状态消息(用于记录错误或进度信息)
// +optional
Message string `json:"message,omitempty"`
}
// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// +kubebuilder:resource:scope=Cluster,shortName=csr
// +kubebuilder:printcolumn:name="ScanName",type=string,JSONPath=`.spec.scanName`
// +kubebuilder:printcolumn:name="ScanID",type=string,JSONPath=`.spec.scanID`
// +kubebuilder:printcolumn:name="Pass",type=integer,JSONPath=`.spec.summary.pass`
// +kubebuilder:printcolumn:name="Fail",type=integer,JSONPath=`.spec.summary.fail`
// +kubebuilder:printcolumn:name="Total",type=integer,JSONPath=`.spec.summary.total`
// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`
type ComplianceScanResult struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec ComplianceScanResultSpec `json:"spec,omitempty"`
Status ComplianceScanResultStatus `json:"status,omitempty"`
}
3.2.2 字段说明表格
ComplianceScanResultSpec 字段:
| 字段名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| schemaVersion | string | 否 | Schema 版本标识,用于未来迁移(默认 "v1alpha1") |
| scanID | string | 是 | 扫描唯一标识,与 ComplianceScan.status.scanHistory[].scanID 对应 |
| scanName | string | 是 | 关联的 ComplianceScan 名称 |
| scanner | ScannerType | 是 | 扫描引擎(typed enum,kube-bench/openscap) |
| benchmark | string | 否 | 使用的基准版本 |
| profile | string | 否 | 使用的 Profile(仅 openscap) |
| nodeCount | int | 是 | 本次扫描的节点总数 |
| nodeSummaries | []NodeSummary | 否 | 轻量级 per-node 概览列表(含 resultRef 指向 ComplianceScanNodeResult) |
| summary | ScanSummary | 是 | 全局统计摘要(所有节点汇总,含 8 个字段) |
| scanStartTime | Time | 是 | 扫描开始时间 |
| scanEndTime | Time | 是 | 扫描结束时间 |
NodeSummary 字段(轻量级概览,不含详细检查结果):
| 字段名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| nodeName | string | 是 | 节点名称 |
| nodeType | string | 是 | 节点类型(control-plane/worker) |
| summary | ScanSummary | 是 | 该节点的结果统计摘要 |
| resultRef | string | 是 | 指向 ComplianceScanNodeResult 资源的名称 |
3.2.3 与 ComplianceScan 和 ComplianceScanNodeResult 的关系
三级 CRD 模型:一次扫描产生:
- 1 个 ComplianceScanResult CR:存储聚合摘要(NodeSummaries 轻量列表 + 全局 Summary),不含详细检查结果。
- N 个 ComplianceScanNodeResult CR(每节点一个):存储单节点详细结果(NonPassResults + PassCount)。

ComplianceScanResult 通过以下机制与 ComplianceScan 关联:
- OwnerReference:ComplianceScanResult 和 ComplianceScanNodeResult 的 metadata.ownerReferences 均指向 ComplianceScan,确保 ComplianceScan 被删除时级联删除所有关联的 Result。
- scanHistory 引用:ComplianceScan.status.scanHistory[].resultRef 存储 ComplianceScanResult 的资源名称(如
cis-scan-20260528-143022-a7f3)。 - NodeSummary.resultRef:ComplianceScanResult.spec.nodeSummaries[].resultRef 存储对应 ComplianceScanNodeResult 的资源名称。
命名规则:
- ComplianceScanResult:
<scanName>-<scanID>,例如cis-scan-20260528-143022-a7f3 - ComplianceScanNodeResult:
<scanName>-<scanID>-<nodeHash>,例如cis-scan-20260528-143022-a7f3-a1b2c3d4
推荐 Labels:
Controller 在创建 ComplianceScanResult 时应添加以下 labels,用于快速过滤和查询:
metadata:
labels:
compliance.openfuyao.cn/scan-id: "<scanID>"
compliance.openfuyao.cn/scan-name: "<scanName>"
compliance.openfuyao.cn/scanner: "<scanner>"
使用场景:
- 查询特定 scanID 的所有结果:
kubectl get compliancescanresults -l compliance.openfuyao.cn/scan-id=20260528-143022-a7f3 - 查询特定扫描器的所有结果:
kubectl get compliancescanresults -l compliance.openfuyao.cn/scanner=kube-bench - 历史淘汰时快速定位:通过 scan-id label 快速找到需要删除的 CR
Finalizers 机制:
系统定义了 finalizer 常量用于资源保护:
| Finalizer 常量 | 值 | 应用对象 | 实现状态 |
|---|---|---|---|
ScanFinalizerName |
compliance.openfuyao.cn/scan-cleanup |
ComplianceScan | 已实现:删除时清理关联的 Job 资源 |
FinalizerName |
compliance.openfuyao.cn/result-cleanup |
ComplianceScanResult | 已定义但未使用:常量存在但 ComplianceScanResultReconciler 当前为空壳实现 |
# ComplianceScan finalizer(已实现)
metadata:
finalizers:
- compliance.openfuyao.cn/scan-cleanup
ComplianceScan Finalizer 工作流程(已实现):
- 创建时:Controller 在 ComplianceScan CR 上添加
scan-cleanupfinalizer - 删除时:
- Kubernetes 设置
metadata.deletionTimestamp,但不立即删除 - Controller 检测到 deletionTimestamp,通过
LabelScanName标签删除所有关联的 Job 资源 - 清理完成后,Controller 移除 finalizer
- Kubernetes 完成删除
- Kubernetes 设置
ComplianceScanResult 清理机制:
ComplianceScanResult 和 ComplianceScanNodeResult 的清理依赖 OwnerReference 级联删除而非独立 finalizer。当 ComplianceScan 被删除时,Kubernetes 自动级联删除所有 OwnerReference 指向它的 Result 和 NodeResult CR。ComplianceScanResultReconciler 当前为被动 Watch 实现(仅日志记录),不包含 finalizer 管理逻辑。
设计决策说明:
选择"聚合摘要 CR + 单节点详细 CR"的三级模型的原因:
- etcd 友好:ComplianceScanResult 仅存储轻量 NodeSummaries(每项约 200 字节),即使 50 节点也只有 ~10KB。详细结果分散在独立的 ComplianceScanNodeResult 中,每个 CR < 100KB,远低于 etcd 1.5MB 限制。
- 按需加载:整体报告只需读取 ComplianceScanResult(一次 API 调用)。单节点详细报告通过 NodeSummary.resultRef 精准读取对应的 ComplianceScanNodeResult。
- 运维简单:5 次历史 = 5 个 ComplianceScanResult CR + N 个 ComplianceScanNodeResult CR。淘汰时通过 OwnerReference 级联删除。
- 引用简洁:scanHistory 使用简单的 resultRef 字符串指向 ComplianceScanResult,NodeSummary.resultRef 指向 ComplianceScanNodeResult。
3.2.4 报告层级结构
报告分为两个层级:整体汇总报告和单节点详细报告。
整体汇总报告 (Overall Summary)
|
|-- 扫描元信息: scanID, scanner, benchmark/profile, 时间范围
|-- 全局统计: 所有节点汇总的 pass/fail/warn/info/total
|-- 合规率: pass / total * 100%
|-- 节点概览表:
| +------------------+--------+------+------+------+-------+
| | Node | Role | Pass | Fail | Warn | Score |
| +------------------+--------+------+------+------+-------+
| | cp-node-1 | CP | 45 | 5 | 3 | 84.9% |
| | cp-node-2 | CP | 47 | 3 | 3 | 88.7% |
| | worker-1 | Worker | 28 | 2 | 1 | 90.3% |
| | worker-2 | Worker | 29 | 1 | 1 | 93.5% |
| +------------------+--------+------+------+------+-------+
|-- 失败项汇总: 所有节点的失败项去重合并
|
+-- 单节点详细报告 (Per-Node Detail)
|
|-- 节点元信息: nodeName, nodeType
|-- 节点统计: 该节点的 pass/fail/warn/info/total
|-- 失败项详情:
[FAIL] 1.2.1 Ensure anonymous-auth is disabled
Severity: high
Actual Value: anonymous-auth=true
Expected: anonymous-auth=false
Remediation: Edit kube-apiserver manifest...
数据来源:
| 报告层级 | 数据来源 | 说明 |
|---|---|---|
| 整体汇总 | ComplianceScanResult CR 的 summary 字段 + nodeSummaries 数组 | 全局统计从 summary 获取,节点概览从 nodeSummaries 数组构建 |
| 单节点详细 | ComplianceScanNodeResult CR(通过 NodeSummary.resultRef 查找) | 按 resultRef 读取对应的 ComplianceScanNodeResult,获取 NonPassResults + PassCount |
报告导出命令:
# 导出整体汇总报告(包含所有节点概览 + 失败项汇总)
kubectl exec -n compliance-operator-system deploy/compliance-operator -- \
/compliance-operator report cis-scan --scan-id 20260528-143022-a7f3 > report.html
# 导出单节点详细报告
kubectl exec -n compliance-operator-system deploy/compliance-operator -- \
/compliance-operator report cis-scan --scan-id 20260528-143022-a7f3 \
--node worker-1 > report-worker-1.html
# 导出 JSON 格式(整体汇总 + 所有节点详细数据)
kubectl exec -n compliance-operator-system deploy/compliance-operator -- \
/compliance-operator report cis-scan --scan-id 20260528-143022-a7f3 --format json
# 导出单节点 JSON
kubectl exec -n compliance-operator-system deploy/compliance-operator -- \
/compliance-operator report cis-scan --scan-id 20260528-143022-a7f3 \
--node worker-1 --format json
report 子命令参数:
| 参数 | 必填 | 说明 |
|---|---|---|
<scan-name> |
是 | ComplianceScan 资源名称 |
--scan-id |
否 | 指定 scanID,留空使用最近一次 |
--node |
否 | 指定节点名称,导出单节点详细报告;留空导出整体汇总报告 |
--format |
否 | 输出格式:html(默认)或 json |
-o |
否 | 输出文件路径,留空输出到 stdout |
3.3 ComplianceScanNodeResult (Cluster-scoped)
3.3.1 Go 类型定义
// ComplianceScanNodeResultSpec 定义单节点的详细扫描结果
// 仅存储非 PASS 结果以减少 etcd 存储;PASS 项仅记录计数
type ComplianceScanNodeResultSpec struct {
// scanID 关联的扫描唯一标识
ScanID string `json:"scanID"`
// scanName 关联的 ComplianceScan 资源名称
ScanName string `json:"scanName"`
// nodeName 节点名称
NodeName string `json:"nodeName"`
// nodeType 节点类型
// +kubebuilder:validation:Enum=control-plane;worker
NodeType string `json:"nodeType"`
// summary 该节点的结果统计摘要
Summary ScanSummary `json:"summary"`
// nonPassResults 包含非 PASS 的检查结果(FAIL, WARN, ERROR, NOT_APPLICABLE 等)
// PASS 项不存储以减少 etcd 存储,仅记录 passCount
// +optional
NonPassResults []UnifiedCheckResult `json:"nonPassResults,omitempty"`
// passCount 该节点通过的检查数量
// 不存储单个 PASS 检查的详情
PassCount int `json:"passCount"`
// scanStartTime 扫描开始时间
ScanStartTime metav1.Time `json:"scanStartTime"`
// scanEndTime 扫描结束时间
ScanEndTime metav1.Time `json:"scanEndTime"`
}
// +kubebuilder:object:root=true
// +kubebuilder:resource:scope=Cluster,shortName=csnsr
// +kubebuilder:printcolumn:name="ScanName",type=string,JSONPath=`.spec.scanName`
// +kubebuilder:printcolumn:name="Node",type=string,JSONPath=`.spec.nodeName`
// +kubebuilder:printcolumn:name="Pass",type=integer,JSONPath=`.spec.passCount`
// +kubebuilder:printcolumn:name="Fail",type=integer,JSONPath=`.spec.summary.fail`
// +kubebuilder:printcolumn:name="Warn",type=integer,JSONPath=`.spec.summary.warn`
// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`
type ComplianceScanNodeResult struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec ComplianceScanNodeResultSpec `json:"spec,omitempty"`
}
3.3.2 字段说明表格
ComplianceScanNodeResultSpec 字段:
| 字段名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| scanID | string | 是 | 扫描唯一标识 |
| scanName | string | 是 | 关联的 ComplianceScan 名称 |
| nodeName | string | 是 | 节点名称 |
| nodeType | string | 是 | 节点类型(control-plane/worker) |
| summary | ScanSummary | 是 | 该节点的结果统计摘要(8 个字段) |
| nonPassResults | []UnifiedCheckResult | 否 | 仅非 PASS 的检查结果列表 |
| passCount | int | 是 | PASS 计数(不存储 PASS 详情) |
| scanStartTime | Time | 是 | 该节点扫描开始时间 |
| scanEndTime | Time | 是 | 该节点扫描结束时间 |
3.3.3 存储优化策略
ComplianceScanNodeResult 仅存储 NonPassResults(FAIL、WARN、ERROR、NOT_APPLICABLE、NOT_CHECKED),PASS 项仅记录 PassCount 计数。这是因为:
- PASS 项占大多数:典型扫描中 PASS 项占 80-90%,但信息量低(仅表示"配置正确")。
- 减少 etcd 存储:不存储 PASS 详情可将单节点 CR 大小从 ~100KB 降至 ~10KB。
- 报告仍可完整:报告导出时,PASS 项可从知识库中按规则 ID 补全描述信息。
3.4 统一检查结果模型 (UnifiedCheckResult)
3.4.1 设计动机
kube-bench 和 OpenSCAP 的输出格式差异显著:
| 维度 | kube-bench | OpenSCAP |
|---|---|---|
| 输出格式 | JSON | XCCDF XML |
| 检查项 ID | 数字编号(如 1.1.1) | STIG Vuln ID(如 V-242376) |
| 状态值 | PASS/FAIL/WARN/INFO | pass/fail/error/unknown/notchecked/notapplicable |
| 严重级别 | 无(通过 scored 标识) | 通过 Rule.severity 标识 |
| 分类 | 通过 section/node_type | 通过 platform(ControlPlane/WorkerNode) |
UnifiedCheckResult 将两者统一为通用模型,便于存储、查询和报告生成。
3.4.2 Go 类型定义
存储模型(精简版,存入 etcd):
仅存储每次扫描的差异数据(检查项 ID + 状态 + 实际值/期望值),不存储描述、修复建议等公共文本。这些公共信息通过规则知识库在报告导出时动态关联。
type UnifiedCheckResult struct {
// id 检查项唯一标识(关联规则知识库的键)
// kube-bench: 如 "1.1.1", "2.3.4"
// OpenSCAP: 如 "V-242376", "V-245544"
ID string `json:"id"`
// status 检查结果状态
// +kubebuilder:validation:Enum=PASS;FAIL;WARN;INFO;NOT_APPLICABLE;NOT_CHECKED;ERROR
Status string `json:"status"`
// actualValue 实际检测到的值(仅存储差异部分)
ActualValue string `json:"actualValue,omitempty"`
// expectedValue 期望的值或合规条件
ExpectedValue string `json:"expectedValue,omitempty"`
}
存储优化效果对比:
| 指标 | 优化前(含完整文本) | 优化后(精简版) | 节省比例 |
|---|---|---|---|
| 单条结果大小 | ~400B | ~80B | 80% |
| 10 节点 × 300 条 CIS 检查 | ~1.2MB | ~240KB | 80% |
| 5 次历史总计 | ~6MB | ~1.2MB | 80% |
渲染模型(完整版,报告导出时使用):
报告导出时,Operator 从规则知识库中按 ID 查找完整信息,组装为完整版:
// RuleDefinition 规则知识库条目(内置在 Operator 中,不存入 etcd)
type RuleDefinition struct {
// ID 检查项唯一标识
ID string `json:"id"`
// Description 检查项描述
Description string `json:"description"`
// Remediation 修复建议
Remediation string `json:"remediation"`
// Severity 严重级别
Severity string `json:"severity"`
// Category 检查项分类
Category string `json:"category"`
// Scored 是否为计分项
Scored bool `json:"scored"`
// Benchmark 所属基准标准
Benchmark string `json:"benchmark"`
}
// RenderedCheckResult 渲染后的完整检查结果(仅用于报告导出)
type RenderedCheckResult struct {
UnifiedCheckResult // 嵌入精简版结果
RuleDefinition // 嵌入规则知识库信息
}
规则知识库数据来源:
| 扫描引擎 | 知识库来源 | 加载方式 |
|---|---|---|
| kube-bench | pkg/report/embed/cis/*/*.yaml (CIS Benchmark 定义文件,按版本子目录) |
编译时嵌入二进制 (go:embed),由 cis_loader.go 解析 |
| OpenSCAP | pkg/report/embed/stig/*/*.xml (STIG XCCDF 数据流,按版本子目录) |
编译时嵌入二进制 (go:embed),由 stig_loader.go 解析 |
规则知识库存储方式:
// pkg/report/knowledge.go
// KnowledgeBase 提供 CIS Benchmark 和 DISA STIG 标准的规则定义
// 支持多版本基准,规则按 version -> ruleID -> RuleDefinition 组织
type KnowledgeBase struct {
cisRules map[string]map[string]unified.RuleDefinition // version -> ruleID -> rule
stigRules map[string]map[string]unified.RuleDefinition // version -> ruleID -> rule
}
// Lookup 根据扫描器类型、基准版本和规则 ID 查找规则定义
// 若版本为空或精确匹配失败,回退到搜索所有版本
func (kb *KnowledgeBase) Lookup(scanner, version, ruleID string) (*unified.RuleDefinition, error)
// pkg/report/cis_loader.go
//go:embed embed/cis/*/*.yaml
var cisFS embed.FS // CIS YAML 规则文件,按版本子目录组织
// pkg/report/stig_loader.go
//go:embed embed/stig/*/*.xml
var stigFS embed.FS // STIG XCCDF XML 规则文件,按版本子目录组织
3.4.3 状态映射表
| kube-bench status | OpenSCAP result | UnifiedCheckResult status |
|---|---|---|
| PASS | pass | PASS |
| FAIL | fail | FAIL |
| WARN | error | WARN |
| WARN | unknown | WARN |
| INFO | informational | INFO |
| INFO | notapplicable | NOT_APPLICABLE |
| INFO | notchecked | NOT_CHECKED |
| - | notselected | (不包含在结果中) |
3.4.4 严重级别映射
| 来源 | 映射规则 |
|---|---|
| kube-bench | scored=true 且 FAIL -> high; scored=true 且 WARN -> medium; scored=false -> info |
| OpenSCAP | Rule.severity="high" -> high; "medium" -> medium; "low" -> low |
3.5 CRD 版本策略与升级
3.5.1 版本策略
- 初始版本为
v1alpha1,表示 API 处于 Alpha 阶段,可能存在不兼容变更。 - 使用
apiextensions.k8s.io/v1CustomResourceDefinition 定义。 - 通过
served: true, storage: true标记当前版本。
3.5.2 升级路径
当 API 从 v1alpha1 升级到 v1beta1 或 v1 时:
- 在 CRD 中添加新版本,同时保留旧版本(
served: true, storage: false)。 - 实现 Conversion Webhook 处理版本间转换。
- 使用
kubectl convert验证转换逻辑。 - 将所有存储对象迁移到新版本后,移除旧版本。
3.5.3 降级策略
- 删除 Operator Deployment 后,CRD 和 CR 数据仍保留在 etcd 中。
- 可通过
kubectl delete crd compliancescans.compliance.openfuyao.cn清理。 - 降级不影响集群其他组件运行。
4. 扫描引擎集成设计
4.1 kube-bench 集成
4.1.1 命令行构造
kube-bench 扫描命令由 Controller 根据 ComplianceScan.spec 动态构造:
kube-bench run \
--targets <targets> \
--json \
--benchmark <benchmark-version> \
--noremediations=false
参数说明:
| 参数 | 来源 | 说明 |
|---|---|---|
| --targets | 根据节点类型决定 | control-plane 节点: master,controlplane,etcd,policies; worker 节点: node |
| --json | 固定 | 输出 JSON 格式,便于程序解析 |
| --benchmark | spec.benchmark 或自动检测 | CIS Benchmark 版本号 |
| --noremediations=false | 固定 | 包含修复建议 |
4.1.2 版本映射策略
CIS Kubernetes Benchmark support
| Source Kubernetes Benchmark | kube-bench config | Kubernetes versions |
|---|---|---|
| CIS 1.5.1 | cis-1.5 | 1.15 |
| CIS 1.6.0 | cis-1.6 | 1.16-1.18 |
| CIS 1.20 | cis-1.20 | 1.19-1.21 |
| CIS 1.23 | cis-1.23 | 1.22-1.23 |
| CIS 1.24 | cis-1.24 | 1.24 |
| CIS 1.7 | cis-1.7 | 1.25 |
| CIS 1.8 | cis-1.8 | 1.26 |
| CIS 1.9 | cis-1.9 | 1.27 |
| CIS 1.10 | cis-1.10 | 1.28 |
| CIS 1.11 | cis-1.11 | 1.29-1.31 |
| CIS 1.12 | cis-1.12 | 1.32-1.34 |
4.1.3 Job Pod Spec
Job 命名策略:
Kubernetes 资源名称限制为 63 字符。Job 命名采用以下截断策略:
格式: <scanName>-<scanID>-<nodeHash>
- scanName: 截断至 20 字符(如果超长)
- scanID: 固定 18 字符(YYYYMMDD-HHmmss-XXXX)
- nodeHash: nodeName 的前 8 字符 + 4 字符 hash(防止冲突)
示例:
- 原始:
my-compliance-scan-20260528-143022-a7f3-k8s-worker-node-01.example.com - 截断后:
my-compliance-scan-20260528-143022-a7f3-k8s-work-a1b2(共 52 字符)
apiVersion: batch/v1
kind: Job
metadata:
name: <scanName-truncated>-<scanID>-<nodeHash>
labels:
compliance.openfuyao.cn/scan-id: "<scanID>"
compliance.openfuyao.cn/scan-name: "<scanName>"
compliance.openfuyao.cn/scanner: kube-bench
compliance.openfuyao.cn/node: "<nodeName>"
spec:
backoffLimit: 1
activeDeadlineSeconds: <spec.timeout-in-seconds> # 动态设置,等于 ComplianceScan.spec.timeout
ttlSecondsAfterFinished: 300
template:
spec:
serviceAccountName: compliance-scan-job
restartPolicy: Never
nodeName: <nodeName>
hostPID: true
priorityClassName: compliance-scan-low-priority # 低优先级,避免影响业务工作负载
tolerations:
- key: node-role.kubernetes.io/control-plane
operator: Exists
effect: NoSchedule
containers:
- name: scanner
image: hub.oepkgs.net/openfuyao/aquasec/kube-bench:v0.15.6 # 可通过 Helm values 配置
imagePullPolicy: IfNotPresent
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
command:
- /bin/sh
- -c
- |
echo "===COMPLIANCE_RESULT_START==="
kube-bench run \
--targets master,controlplane,etcd,policies \
--json \
--benchmark cis-1.8 \
| tee /results/output.json
echo "===COMPLIANCE_RESULT_END==="
volumeMounts:
- name: results
mountPath: /results
- name: etc-kubernetes
mountPath: /etc/kubernetes
readOnly: true
- name: var-lib-kubelet
mountPath: /var/lib/kubelet
readOnly: true
- name: var-lib-etcd
mountPath: /var/lib/etcd
readOnly: true
- name: etc-systemd
mountPath: /etc/systemd
readOnly: true
- name: lib-systemd
mountPath: /lib/systemd
readOnly: true
- name: usr-bin
mountPath: /usr/local/bin/host
readOnly: true
- name: srv-kubernetes
mountPath: /srv/kubernetes
readOnly: true
- name: etc-cni
mountPath: /etc/cni/net.d
readOnly: true
- name: opt-cni
mountPath: /opt/cni/bin
readOnly: true
- name: var-lib-cni
mountPath: /var/lib/cni
readOnly: true
volumes:
- name: results
emptyDir: {}
- name: etc-kubernetes
hostPath:
path: /etc/kubernetes
type: DirectoryOrCreate
- name: var-lib-kubelet
hostPath:
path: /var/lib/kubelet
type: DirectoryOrCreate
- name: var-lib-etcd
hostPath:
path: /var/lib/etcd
type: DirectoryOrCreate
- name: etc-systemd
hostPath:
path: /etc/systemd
type: DirectoryOrCreate
- name: lib-systemd
hostPath:
path: /lib/systemd
type: DirectoryOrCreate
- name: usr-bin
hostPath:
path: /usr/bin
type: Directory
- name: srv-kubernetes
hostPath:
path: /srv/kubernetes
type: DirectoryOrCreate
- name: etc-cni
hostPath:
path: /etc/cni/net.d
type: DirectoryOrCreate
- name: opt-cni
hostPath:
path: /opt/cni/bin
type: DirectoryOrCreate
- name: var-lib-cni
hostPath:
path: /var/lib/cni
type: DirectoryOrCreate
4.1.4 JSON 输出解析
kube-bench JSON 输出结构:
{
"OverallControls": {
"Controls": [
{
"id": "1",
"version": "cis-1.8",
"node_type": "master",
"tests": [
{
"section": "1.1",
"desc": "Control Plane Node Configuration Files",
"results": [
{
"test_number": "1.1.1",
"test_desc": "Ensure that the API server pod specification file permissions are set to 644 or more restrictive",
"status": "PASS",
"actual_value": "644",
"expected_result": "644",
"remediation": "No remediation required",
"scored": true
}
]
}
]
}
],
"Totals": {
"total_pass": 72,
"total_fail": 15,
"total_warn": 8,
"total_info": 5
}
}
}
解析映射规则分为两部分:存储映射(写入 etcd)和渲染映射(报告导出时从规则知识库关联)。
存储映射(写入 ComplianceScanNodeResult.spec.nonPassResults,PASS 项仅记录 passCount):
| kube-bench 字段 | UnifiedCheckResult 字段 | 映射逻辑 |
|---|---|---|
| test_number | id | 直接映射 |
| status | status | PASS->PASS(仅计数,不存储), FAIL->FAIL, WARN->WARN, INFO->INFO |
| actual_value | actualValue | 直接映射 |
| expected_result | expectedValue | 直接映射 |
渲染映射(报告导出时从规则知识库 KnowledgeBase 关联):
| 知识库字段 | 来源 | 说明 |
|---|---|---|
| description | 规则知识库 embed/cis/*/*.yaml |
根据 id 查找 test_desc |
| remediation | 规则知识库 embed/cis/*/*.yaml |
根据 id 查找 remediation |
| severity | 运行时推导 | scored+FAIL->high, scored+WARN->medium, !scored->info |
| category | 规则知识库 embed/cis/*/*.yaml |
根据 node_type 映射:master->Control Plane, node->Worker Node, etcd->etcd |
| scored | 规则知识库 embed/cis/*/*.yaml |
根据 id 查找 scored 字段 |
设计说明:
- 存储优化:仅存储 4 个核心字段(id/status/actualValue/expectedValue),单条结果约 80B
- 渲染时补全:报告导出时从内置的规则知识库(
go:embed)按 id 查找完整信息 - 知识库来源:kube-bench 的
embed/cis/*/*.yaml文件在编译时嵌入 Operator 二进制(由cis_loader.go加载)
4.1.5 所需 hostPath 挂载清单
| 主机路径 | 容器路径 | 用途 | 只读 |
|---|---|---|---|
| /etc/kubernetes | /etc/kubernetes | K8s 配置文件、manifests、证书 | 是 |
| /var/lib/kubelet | /var/lib/kubelet | kubelet 配置和数据 | 是 |
| /var/lib/etcd | /var/lib/etcd | etcd 数据目录 | 是 |
| /etc/systemd | /etc/systemd | systemd 服务配置 | 是 |
| /lib/systemd | /lib/systemd | systemd 库文件 | 是 |
| /usr/bin | /usr/local/bin/host | kubectl/kubelet 二进制(版本检测) | 是 |
| /srv/kubernetes | /srv/kubernetes | K8s 证书和配置(部分发行版) | 是 |
| /etc/cni/net.d | /etc/cni/net.d | CNI 网络配置 | 是 |
| /opt/cni/bin | /opt/cni/bin | CNI 插件二进制 | 是 |
| /var/lib/cni | /var/lib/cni | CNI 运行时数据 | 是 |
4.2 OpenSCAP 集成
4.2.1 命令行构造
OpenSCAP 扫描命令:
OSCAP_PROBE_ROOT=/host oscap xccdf eval \
--profile <profile-id> \
--results-arf /results/arf.xml \
--results /results/results.xml \
<content-file-path>
参数说明:
| 参数 | 来源 | 说明 |
|---|---|---|
| OSCAP_PROBE_ROOT | 固定 /host |
告诉 OVAL probe 在 /host 前缀下探测文件系统 |
| --profile | spec.profile | XCCDF Profile ID |
| --results-arf | 固定 | 输出 ARF 格式完整报告 |
| --results | 固定 | 输出 XCCDF Results XML |
| content-file | 固定 /scap-content/scap-rule.xml |
STIG Benchmark XML 文件路径(镜像内置) |
退出码处理:
| 退出码 | 含义 | 处理方式 |
|---|---|---|
| 0 | 所有规则合规 | 正常处理 |
| 1 | 执行错误 | 标记为 FAILED |
| 2 | 存在不合规项 | 正常处理(这是有效结果) |
4.2.2 STIG Profile 选择策略
DISA Kubernetes STIG V2R4 提供 10 个 Profile:
| Profile ID | 标题 | 适用场景 |
|---|---|---|
| xccdf_mil.disa.stig_profile_MAC-1_Classified | I - Mission Critical Classified | 最高安全级别,机密环境 |
| xccdf_mil.disa.stig_profile_MAC-1_Public | I - Mission Critical Public | 最高安全级别,公开环境 |
| xccdf_mil.disa.stig_profile_MAC-1_Sensitive | I - Mission Critical Sensitive | 最高安全级别,敏感环境 |
| xccdf_mil.disa.stig_profile_MAC-2_Classified | II - Mission Support Classified | 中等安全级别,机密环境 |
| xccdf_mil.disa.stig_profile_MAC-2_Public | II - Mission Support Public | 中等安全级别,公开环境 |
| xccdf_mil.disa.stig_profile_MAC-2_Sensitive | II - Mission Support Sensitive | 中等安全级别,敏感环境 |
| xccdf_mil.disa.stig_profile_MAC-3_Classified | III - Administrative Classified | 基础安全级别,机密环境 |
| xccdf_mil.disa.stig_profile_MAC-3_Public | III - Administrative Public | 基础安全级别,公开环境 |
| xccdf_mil.disa.stig_profile_MAC-3_Sensitive | III - Administrative Sensitive | 基础安全级别,敏感环境 |
| xccdf_mil.disa.stig_profile_CAT_I_Only | CAT I Only | 仅包含 Category I 严重规则 |
选择策略:
- 若 spec.profile 不为空,使用用户指定的 Profile。
- 若 spec.profile 为空,默认使用
xccdf_mil.disa.stig_profile_MAC-1_Classified(最严格的全量检查)。 - 所有 MAC 级别 Profile 包含相同的 55 条规则,区别仅在于元数据分类。CAT_I_Only Profile 排除了非 CAT I 规则。
4.2.3 Job Pod Spec
Job 命名策略:
与 kube-bench 相同,采用截断策略确保名称不超过 63 字符:
格式: <scanName>-<scanID>-<nodeHash>
- scanName: 截断至 20 字符(如果超长)
- scanID: 固定 18 字符(YYYYMMDD-HHmmss-XXXX)
- nodeHash: nodeName 的前 8 字符 + 4 字符 hash(防止冲突)
apiVersion: batch/v1
kind: Job
metadata:
name: <scanName-truncated>-<scanID>-<nodeHash>
labels:
compliance.openfuyao.cn/scan-id: "<scanID>"
compliance.openfuyao.cn/scan-name: "<scanName>"
compliance.openfuyao.cn/scanner: openscap
compliance.openfuyao.cn/node: "<nodeName>"
spec:
backoffLimit: 1
activeDeadlineSeconds: <spec.timeout-in-seconds> # 动态设置,等于 ComplianceScan.spec.timeout
ttlSecondsAfterFinished: 300
template:
spec:
serviceAccountName: compliance-scan-job
restartPolicy: Never
nodeName: <nodeName>
hostPID: true
priorityClassName: compliance-scan-low-priority # 低优先级,避免影响业务工作负载
tolerations:
- key: node-role.kubernetes.io/control-plane
operator: Exists
effect: NoSchedule
containers:
- name: scanner
image: cr.openfuyao.cn/openfuyao/openscap:1.4.4 # 可通过 Helm values 配置
securityContext:
privileged: true
imagePullPolicy: IfNotPresent
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
command:
- /bin/bash
- -c
- |
echo "===COMPLIANCE_RESULT_START==="
oscap xccdf eval \
--profile <spec.profile> \
--results-arf /results/arf.xml \
--results /results/results.xml \
/scap-content/scap-rule.xml
exit_code=$?
cat /results/results.xml
echo "===COMPLIANCE_RESULT_END==="
if [ $exit_code -eq 0 ] || [ $exit_code -eq 2 ]; then
exit 0
fi
exit 1
env:
- name: OSCAP_PROBE_ROOT
value: "/host"
volumeMounts:
- name: results
mountPath: /results
- name: host-root
mountPath: /host
readOnly: true
volumes:
- name: results
emptyDir: {}
- name: host-root
hostPath:
path: /
type: Directory
4.2.4 XCCDF Results XML 解析
XCCDF Results XML 结构示例:
<?xml version="1.0" encoding="UTF-8"?>
<TestResult xmlns="http://checklists.nist.gov/xccdf/1.2"
id="xccdf_mil.disa.stig_testresult_..."
test-system="cpe:/a:redhat:openscap:1.3.9"
start-time="2026-05-28T14:30:22"
end-time="2026-05-28T14:31:05">
<benchmark href="U_Kubernetes_V2R4_STIG_SCAP_1-3_Benchmark-xccdf.xml"
id="xccdf_mil.disa.stig_benchmark_Kubernetes_STIG"/>
<profile idref="xccdf_mil.disa.stig_profile_MAC-1_Classified"/>
<rule-result idref="xccdf_mil.disa.stig_rule_SV-242376r960759_rule"
time="2026-05-28T14:30:25"
severity="medium"
version="CNTR-K8-000150">
<result>pass</result>
<ident system="http://cyber.mil/cci">CCI-000068</ident>
<check system="http://oval.mitre.org/XMLSchema/oval-definitions-5">
<check-content-ref href="..." name="oval:mil.disa.stig.kubernetes:def:242376"/>
</check>
</rule-result>
<rule-result idref="xccdf_mil.disa.stig_rule_SV-242379r960759_rule"
time="2026-05-28T14:30:26"
severity="medium"
version="CNTR-K8-000180">
<result>fail</result>
</rule-result>
<score system="urn:xccdf:scoring:default" maximum="100">76.36</score>
</TestResult>
解析映射规则分为两部分:存储映射(写入 etcd)和渲染映射(报告导出时从规则知识库关联)。
存储映射(写入 ComplianceScanNodeResult.spec.nonPassResults,PASS 项仅记录 passCount):
| XCCDF 字段 | UnifiedCheckResult 字段 | 映射逻辑 |
|---|---|---|
| rule-result.idref | id | 提取 Vuln ID(如从 xccdf_mil.disa.stig_rule_SV-242376r960759_rule 提取 V-242376) |
| rule-result.result | status | pass->PASS(仅计数,不存储), fail->FAIL, error->WARN, unknown->WARN, notchecked->NOT_CHECKED, notapplicable->NOT_APPLICABLE, informational->INFO |
| rule-result.result | actualValue | 直接存储 result 元素值(如 "pass", "fail") |
| (固定) | expectedValue | "pass"(STIG 规则的期望值始终为 pass) |
渲染映射(报告导出时从规则知识库 KnowledgeBase 关联):
| 知识库字段 | 来源 | 说明 |
|---|---|---|
| description | 规则知识库 embed/stig/*/*.xml |
根据 id 查找对应 Rule 的 title |
| remediation | 规则知识库 embed/stig/*/*.xml |
根据 id 查找对应 Rule 的 fixtext |
| severity | 规则知识库 embed/stig/*/*.xml |
根据 id 查找 rule-result.severity(high/medium/low) |
| category | 规则知识库 embed/stig/*/*.xml |
根据 Rule.platform 映射:ControlPlane->Control Plane, WorkerNode->Worker Node |
| scored | 固定值 | true(STIG 规则均为计分项) |
设计说明:
- 存储优化:仅存储 4 个核心字段(id/status/actualValue/expectedValue),单条结果约 80B
- 渲染时补全:报告导出时从内置的规则知识库(
go:embed)按 id 查找完整信息 - 知识库来源:STIG 的
embed/stig/*/*.xml文件在编译时嵌入 Operator 二进制(由stig_loader.go加载)
4.2.5 ARF 与 XCCDF Results 的区别与使用场景
| 维度 | XCCDF Results XML | ARF (Asset Reporting Format) |
|---|---|---|
| 内容 | 仅包含检查结果(rule-result) | 包含检查结果 + 完整 OVAL 扫描详情 |
| 大小 | 较小(约 10-30KB) | 较大(约 100-500KB) |
| 用途 | 快速查看合规状态 | 深度审计、调试 OVAL 检查细节 |
| 存储策略 | 存入 ComplianceScanNodeResult.spec.nonPassResults | 压缩后存入 rawResult(可选) |
4.2.6 所需 hostPath 挂载清单
OpenSCAP 使用 OSCAP_PROBE_ROOT=/host 模式,需要将整个主机根文件系统挂载到容器的 /host 路径下:
| 主机路径 | 容器路径 | 用途 | 只读 |
|---|---|---|---|
| / | /host | 主机根文件系统(OVAL probe 在此前缀下探测) | 是 |
实际被 OVAL 检查访问的关键文件路径(在 /host 前缀下):
| 文件/目录 | 检查类型 | 对应规则示例 |
|---|---|---|
| /etc/kubernetes/manifests/kube-apiserver.yaml | textfilecontent54 | V-242378 (TLS), V-242382 (auth-mode), V-242390 (anonymous-auth) |
| /etc/kubernetes/manifests/kube-controller-manager.yaml | textfilecontent54 | V-242376 (TLS), V-242381 (SA credentials), V-242409 (profiling) |
| /etc/kubernetes/manifests/kube-scheduler.yaml | textfilecontent54 | V-242377 (TLS), V-242384 (bind-address) |
| /etc/kubernetes/manifests/etcd.yaml | textfilecontent54 | V-242379 (auto-tls), V-242423 (client-cert-auth) |
| /var/lib/kubelet/config.yaml | textfilecontent54 | V-242424 (TLS key), V-242434 (kernel defaults) |
| /etc/kubernetes/pki/* | file (permissions) | V-242451 (PKI file permissions) |
| /var/lib/etcd/* | file (permissions) | V-242449 (etcd data dir permissions) |
| /etc/group | textfilecontent54 | V-242445 (etcd group) |
| /etc/kubernetes/kubelet.conf | file | V-242444 (kubelet.conf permissions) |
| kubelet 进程参数 | process58 | V-242404 (hostname-override), V-242420 (client-ca-file) |
4.3 扫描引擎选择策略
4.3.1 spec.scanner 字段设计
spec.scanner 为必填字段,取值 kube-bench 或 openscap。每个 ComplianceScan CR 只能选择一种引擎。
选择建议:
| 场景 | 推荐引擎 | 理由 |
|---|---|---|
| 通用 CIS 合规检查 | kube-bench | 覆盖面广,包含 100+ 检查项 |
| DISA STIG 合规审计 | openscap | 精确匹配 STIG 规则,满足 DoD 审计要求 |
| 快速安全评估 | kube-bench | 执行速度快,结果直观 |
| 深度配置审计 | openscap | OVAL 检查更精细,支持文件权限和进程参数检查 |
4.3.2 默认值策略
不提供默认值。spec.scanner 为必填字段,用户必须显式指定。这避免了因默认值导致的意外扫描行为。
4.4 节点类型与扫描策略分析
4.4.1 kube-bench 与 OpenSCAP 的节点区分机制对比
kube-bench 和 OpenSCAP 对控制平面/工作节点的区分方式存在本质差异:

| 维度 | kube-bench | OpenSCAP (STIG) |
|---|---|---|
| 区分方式 | 显式:通过 --targets 参数指定检查哪些组件 |
隐式:OVAL CPE 自动检测平台,不匹配的规则自动跳过 |
| 控制平面检查 | --targets master,controlplane,etcd,policies |
自动检测,~47 条规则有效评估 |
| 工作节点检查 | --targets node |
自动检测,仅 1 条 + 7 条通用规则 |
| 不匹配时的行为 | 不执行(直接跳过,无输出) | 执行但返回 notapplicable(有输出但标记为不适用) |
| Operator 需要区分? | 是:必须为不同节点类型传不同 --targets |
否:oscap 自动处理,所有节点使用相同命令 |
4.4.2 STIG 规则的平台分布
基于 U_Kubernetes_V2R4_STIG_SCAP_1-3_Benchmark.xml 的分析,55 条规则的平台分布如下:
| 平台标记 | 规则数 | 占比 | 检查目标 |
|---|---|---|---|
Kubernetes_ControlPlane |
~47 条 | 85% | API Server、Controller Manager、Scheduler、etcd 的配置参数 |
Kubernetes_WorkerNode |
1 条 | 2% | V-242393: Worker 节点不得运行 sshd 服务 |
| 无平台标记(通用) | ~7 条 | 13% | kubelet 配置/进程参数、manifest 文件权限、PKI 证书权限、kubeconfig 权限 |
ControlPlane 规则示例(47 条):
- V-242376: Controller Manager 必须使用 TLS 1.2+
- V-242378: API Server 必须使用 TLS 1.2+
- V-242379: etcd 必须使用 TLS
- V-242382: API Server 必须启用 Node,RBAC 授权模式
- V-242390: API Server 必须禁用匿名认证
- V-242462: API Server 审计日志最大大小 >= 100MB
WorkerNode 规则(1 条):
- V-242393: Kubernetes Worker Nodes must not have sshd service running
通用规则(7 条,两种节点都适用):
- V-242404: Kubelet 必须禁止 hostname override
- V-242420: Kubelet 必须设置 SSL Certificate Authority
- V-242424: Kubelet 必须启用 tlsPrivateKeyFile
- V-242425: Kubelet 必须启用 tlsCertFile
- V-242434: Kubelet 必须启用 kernel protection
- V-245541: Kubelet 不得禁用超时
- V-242405/242406/242408: manifest 文件属主和权限
4.4.3 OpenSCAP 的 CPE 平台自动检测机制
STIG XML 内置了 CPE (Common Platform Enumeration) 平台检测逻辑,不需要 Operator 做节点类型区分:

<!-- 控制平面检测:检查 kube-controller-manager.yaml 是否存在且包含 controller-manager 镜像 -->
<cpe:platform id="xccdf_mil.disa.stig_platform_Kubernetes_ControlPlane">
<cpe:logical-test operator="AND" negate="false">
<cpe:check-fact-ref system="http://oval.mitre.org/XMLSchema/oval-definitions-5"
href="...-oval.xml" id-ref="oval:mil.disa.stig.ind:def:100000" />
</cpe:logical-test>
</cpe:platform>
<!-- Worker 节点检测:控制平面检测的取反 -->
<cpe:platform id="xccdf_mil.disa.stig_platform_Kubernetes_WorkerNode">
<cpe:logical-test operator="AND" negate="true">
<cpe:check-fact-ref system="http://oval.mitre.org/XMLSchema/oval-definitions-5"
href="...-oval.xml" id-ref="oval:mil.disa.stig.ind:def:100000" />
</cpe:logical-test>
</cpe:platform>
其中 oval:mil.disa.stig.ind:def:100000 的 OVAL 定义为:
<textfilecontent54_object id="oval:mil.disa.stig.ind:obj:100000">
<filepath>/etc/kubernetes/manifests/kube-controller-manager.yaml</filepath>
<pattern operation="pattern match">
^\s*image:\sregistry\.k8s\.io\/(kube-controller-manager)
</pattern>
</textfilecontent54_object>
检测逻辑:如果节点上存在 /etc/kubernetes/manifests/kube-controller-manager.yaml 且包含 kube-controller-manager 镜像引用,则判定为 ControlPlane 节点;否则判定为 WorkerNode。
当 oscap 在 Worker 节点上执行时:
- 标记为
ControlPlane的 ~47 条规则 → CPE 检测失败 → 自动返回notapplicable - 标记为
WorkerNode的 1 条规则 → CPE 检测通过 → 正常评估 - 无平台标记的 ~7 条规则 → 不受 CPE 影响 → 正常评估
4.4.4 scanType 与 Job 创建策略
核心约束:无论 kube-bench 还是 OpenSCAP,扫描都是节点级操作。
两种引擎都需要读取节点本地文件系统(manifests、kubelet config、PKI 证书、etcd 数据目录),因此必须在每个目标节点上运行一个独立的 Job Pod。不存在"一个 Job 扫描全集群"的可能。
Job 创建策略对比:
| 方案 | 描述 | 可行性 |
|---|---|---|
| A: 每节点 1 个 Job(采用) | N 个 CP 节点 + M 个 Worker 节点 = N+M 个 Job | 正确。每个 Job 通过 nodeName 绑定到指定节点 |
| B: 按节点类型 2 个 Job | 1 个 CP Job + 1 个 Worker Job | 不可行。K8s Job 只能调度到 1 个节点,无法覆盖同类型的其他节点 |
| C: 1 个 DaemonSet | 自动覆盖所有节点 | 不适合一次性扫描场景,无法控制执行时机和结果回收 |
scanType: all 时的 Job 创建逻辑:
Controller 发现 N 个 CP 节点 + M 个 Worker 节点
|
|-- kube-bench 场景:
| |-- 为每个 CP 节点创建 Job:
| | nodeName: <cpNode>
| | command: kube-bench run --targets master,controlplane,etcd,policies --json
| |
| |-- 为每个 Worker 节点创建 Job:
| nodeName: <workerNode>
| command: kube-bench run --targets node --json
|
| 总计: N + M 个 Job,但每个 Job 的 --targets 参数不同
|
|-- openscap 场景:
| |-- 为每个节点创建 Job(不区分类型):
| nodeName: <anyNode>
| command: oscap xccdf eval --profile <profile> ...
|
| 总计: N + M 个 Job,所有 Job 使用相同命令
| oscap 内部 CPE 自动检测平台,不匹配的规则返回 notapplicable
|
v
所有 Job 并行执行,Operator 等待全部完成后进入 Collecting 阶段
4.4.5 scanType 使用建议
| scanType | kube-bench | OpenSCAP (STIG) |
|---|---|---|
control-plane |
扫描 CP 组件(API Server、Scheduler、Controller Manager、etcd、Policies),~70+ 条规则 | 推荐。47 条 CP 规则全部有效评估,效率最高 |
worker |
扫描 kubelet 和 kube-proxy,~30 条规则 | 价值较低。仅 8 条规则有效(1 条 WorkerNode + 7 条通用),47 条返回 notapplicable |
all |
推荐。CP 和 Worker 都有实质性检查项 | 可行但有浪费。Worker 节点上 85% 的规则返回 notapplicable |
建议:
- kube-bench 扫描推荐使用
scanType: all,两种节点类型都有实质性检查项。 - OpenSCAP (STIG) 扫描推荐使用
scanType: control-plane,因为 STIG 规则 85% 面向控制平面。若需完整覆盖(包括 kubelet 相关的 8 条通用规则和 1 条 Worker 规则),可使用scanType: all,结果仍然正确,只是 Worker 节点的报告中会有大量NOT_APPLICABLE状态。
5. 扫描生命周期状态机
5.1 状态定义
| 状态 | 含义 | 持续时间 |
|---|---|---|
| Pending | CR 已创建或注解已触发,等待 Controller 处理 | 通常 < 5 秒 |
| Launching | Controller 正在创建 Scan Job 资源 | 通常 < 10 秒 |
| Running | Scan Job Pod 正在目标节点上执行扫描 | 数分钟至数十分钟 |
| Collecting | Job 已完成,Controller 正在回收结果并写入 ComplianceScanNodeResult + ComplianceScanResult | 通常 < 30 秒 |
| Done | 扫描完成,结果已持久化 | 持久状态 |
| Failed | 任何阶段发生不可恢复的错误 | 持久状态 |
| Ignored | 单例保护:集群中已有其他活跃的 ComplianceScan CR,本 CR 暂不处理。当活跃 CR 被删除后,最旧的 Ignored CR 自动转为 Pending | 持久状态(直到活跃 CR 删除) |
扫描结果判定规则(DetermineOverallResult):
| 条件 | 判定结果 | 说明 |
|---|---|---|
| Total == 0 | Error | 无检查结果,视为异常 |
| Error > 0 | Error | 存在检查执行错误 |
| Fail > 0 | NonCompliant | 存在不合规项 |
| Warn > 0 | NonCompliant | WARN 也视为不合规(严格模式) |
| 其他 | Compliant | 全部通过 |
注意:WARN 计为 NonCompliant 是严格模式设计。kube-bench 的 WARN 通常表示无法确认的配置状态(如需要手动检查的项),在合规审计场景下应视为未通过。
5.2 状态转换图

5.3 每个阶段的 Reconciler 逻辑
5.3.1 Pending 阶段
输入: ComplianceScan CR (phase=Pending 或 phase 为空)
处理:
1. 单例保护:检查本 CR 是否为集群中最旧的 ComplianceScan
- 若不是最旧:标记 phase = Ignored,记录 Condition 和 Event,停止处理
- 若是最旧:继续处理
2. 验证 spec 字段合法性(scanner, scanType, profile 等)
3. 生成 scanID(格式: YYYYMMDD-HHmmss-<4位随机hex>)
4. 更新 status.phase = Launching
5. 更新 status.lastScanID = scanID
6. 更新 status.lastScanTime = now
7. 快照 status.activeScanner = spec.scanner
(解耦解析器选择与 spec.scanner,防止用户在扫描进行中修改 scanner 类型)
8. 使用 Status().Update(非 Patch)确保乐观并发控制,防止重复 scanID 生成
输出: 状态转换为 Launching
5.3.2 Launching 阶段
输入: ComplianceScan CR (phase=Launching)
处理:
1. 根据 scanner 类型构建 Job Spec
- kube-bench: 按节点类型(control-plane/worker)构建不同 targets
- openscap: 每个节点一个 Job
2. 为每个目标节点创建 Job 资源
- 设置 OwnerReference 指向 ComplianceScan
- 设置 Labels 包含 scanID, scanName, scanner, nodeName
3. 更新 status.activeJobs = 创建的 Job 数量
4. 更新 status.phase = Running
输出: 状态转换为 Running,Job 资源已创建
5.3.3 Running 阶段
输入: ComplianceScan CR (phase=Running)
处理:
1. 列出所有关联的 Job(通过 Label selector)
2. 检查每个 Job 的状态:
- 若所有 Job 完成(Succeeded 或 Failed):
- 更新 status.phase = Collecting
- 若有 Job 仍在运行:
- 检查是否超时(当前时间 - lastScanTime > timeout)
- 若超时: 删除所有活跃 Job,status.phase = Failed
- 若未超时: 重新入队(RequeueAfter: 10s)
- 若有 Job 失败:
- 记录失败信息,但不立即标记 Failed(等待其他 Job 完成)
输出: 状态转换为 Collecting 或保持 Running
5.3.4 Collecting 阶段
输入: ComplianceScan CR (phase=Collecting)
处理:
1. 列出所有关联的 Job(通过 Label selector: compliance.openfuyao.cn/scan-id=<scanID>)
2. 遍历每个已完成的 Job:
- 读取 Pod 日志(kubectl logs <pod-name>)
- 提取 ===COMPLIANCE_RESULT_START=== 和 ===COMPLIANCE_RESULT_END=== 之间的内容
- 解析 JSON/XML 为 UnifiedCheckResult 数组
- 计算该节点的 Summary(pass/fail/warn/info/error/notApplicable/notChecked/total)
- 构建 CollectedNodeData 对象(内存中临时结构,含完整 results)
3. 汇总所有节点的 Summary,计算全局 Summary
4. 判断总体结果:
- 全局 Summary 的 fail == 0 -> lastScanResult = Compliant
- 全局 Summary 的 fail > 0 -> lastScanResult = NonCompliant
- 任一节点解析失败 -> lastScanResult = Error
5. 为每个节点创建 ComplianceScanNodeResult CR(并行创建):
- 名称格式: <scanName>-<scanID>-<nodeHash>
- spec.scanID = scanID
- spec.scanName = ComplianceScan 名称
- spec.nodeName = 节点名称
- spec.nodeType = 节点类型
- spec.nonPassResults = 仅非 PASS 的检查结果(FAIL/WARN/ERROR/NOT_APPLICABLE 等)
- spec.passCount = PASS 计数(不存储 PASS 详情,减少 etcd 存储)
- spec.summary = 该节点 Summary
- 设置 OwnerReference 指向 ComplianceScan
- 设置 Labels: scan-id, scan-name, scanner, node
- 构建 NodeSummary(含 resultRef 指向该 NodeResult)
6. 创建 ComplianceScanResult CR(聚合摘要):
- 名称格式: <scanName>-<scanID>
- spec.scanID = scanID
- spec.scanName = ComplianceScan 名称
- spec.scanner = status.activeScanner(使用快照值,非 spec.scanner)
- spec.benchmark = spec.benchmark
- spec.profile = spec.profile(仅 openscap)
- spec.nodeCount = 扫描节点总数
- spec.nodeSummaries = 所有节点的 NodeSummary 列表(轻量摘要 + resultRef)
- spec.summary = 全局 Summary
- spec.scanStartTime = status.lastScanTime
- spec.scanEndTime = now
- 设置 OwnerReference 指向 ComplianceScan
- 设置 Labels: compliance.openfuyao.cn/scan-id=<scanID>
- 若创建失败,回滚已创建的 ComplianceScanNodeResult
7. 追加 scanHistory 条目:
- scanID = scanID
- scanTime = status.lastScanTime
- phase = Done
- result = lastScanResult
- summary = 全局 Summary
- resultRef = ComplianceScanResult 名称
8. 若 scanHistory 长度 > maxHistory:
- 删除最旧的 ComplianceScanResult CR(通过 resultRef 查找,级联删除 NodeResult)
- 从 scanHistory 中移除对应条目
9. 更新 status.phase = Done
10. 清理已完成的 Job 资源(根据 jobTTL 延迟清理或立即删除)
输出: 状态转换为 Done
5.3.5 Done 阶段
输入: ComplianceScan CR (phase=Done)
处理:
1. 检查是否有注解触发(compliance.openfuyao.cn/trigger)
- 若有: 清除注解,status.phase = Pending,重新入队
2. 检查是否有定时调度(spec.schedule)
- 若有: 计算下次执行时间,更新 status.nextScheduleTime
- 若当前时间 >= nextScheduleTime: status.phase = Pending,重新入队
3. 无操作则不重新入队
输出: 保持 Done 或转换为 Pending
5.4 错误处理与重试策略
| 错误类型 | 处理方式 | 重试策略 |
|---|---|---|
| Job 创建失败(权限不足) | 标记 FAILED,记录 Event | 不自动重试,需人工修复 |
| Job Pod 调度失败(无可用节点) | 标记 FAILED,记录 Event | 不自动重试 |
| Job 执行超时 | 删除 Job,标记 FAILED | 可通过注解触发重新扫描 |
| Job 执行失败(非零退出码) | 记录失败信息 | Job 自身 backoffLimit=1(重试一次) |
| Result CRD 写入失败 | 重试写入 | 指数退避,最多 3 次 |
| K8s API 通信失败 | Requeue | controller-runtime 自动重试 |
5.5 超时处理
- 全局超时:由 spec.timeout 控制(默认 30 分钟)。Controller 在 Running 阶段检查是否超时。
- Job 级超时:通过 Job.spec.activeDeadlineSeconds 设置,与 spec.timeout 一致。
- 超时后处理:
- Controller 删除所有关联的活跃 Job。
- 更新 status.phase = Failed。
- 记录 Event:
ScanTimeout: scan exceeded timeout of <duration>。 - 已完成的 ComplianceScanResult 保留(部分结果仍可用于分析)。
6. 触发机制
6.1 手动触发(注解触发)
通过向 ComplianceScan CR 添加注解触发新一轮扫描:
kubectl annotate compliancescan cis-benchmark-scan \
compliance.openfuyao.cn/trigger="$(date +%s)" --overwrite
处理流程:
- Controller Watch 到 CR 的注解变更。
- 检查当前 phase 是否为 Done 或 Failed(仅在这两种状态下允许触发)。
- 清除 trigger 注解。
- 将 phase 重置为 Pending。
- 重新入队处理。
6.2 定时触发(spec.schedule)
当 spec.schedule 不为空时,Controller 内部维护 cron 调度:
- CR 创建时立即执行一次扫描。
- Controller 解析 cron 表达式,计算下次执行时间。
- 更新 status.nextScheduleTime。
- 到达执行时间时,将 phase 重置为 Pending。
- 使用 robfig/cron 库进行 cron 表达式解析和调度。
调度精度:分钟级(标准 5 字段 cron 表达式)。
6.3 CR 创建触发
首次创建 ComplianceScan CR 时自动触发扫描:
- Controller 检测到新 CR(status.phase 为空)。
- 设置 status.phase = Pending。
- 进入正常扫描流程。
7. 报告导出设计
7.1 Operator 内置子命令方案
报告生成功能集成到 Operator 二进制文件中,作为 report 子命令提供。用户通过 kubectl exec 调用,无需单独分发二进制文件。
7.1.1 命令设计
# 导出整体汇总报告(最新扫描,包含所有节点概览 + 失败项汇总)
kubectl exec -n compliance-operator-system deploy/compliance-operator -- \
/compliance-operator report <scan-name>
# 导出整体汇总报告(指定 scanID)
kubectl exec -n compliance-operator-system deploy/compliance-operator -- \
/compliance-operator report <scan-name> --scan-id <scanID>
# 导出单节点详细报告
kubectl exec -n compliance-operator-system deploy/compliance-operator -- \
/compliance-operator report <scan-name> --scan-id <scanID> --node worker-1
# 导出为 JSON 格式
kubectl exec -n compliance-operator-system deploy/compliance-operator -- \
/compliance-operator report <scan-name> --scan-id <scanID> --format json
# 导出单节点 JSON
kubectl exec -n compliance-operator-system deploy/compliance-operator -- \
/compliance-operator report <scan-name> --scan-id <scanID> --node worker-1 --format json
# 输出到本地文件
kubectl exec -n compliance-operator-system deploy/compliance-operator -- \
/compliance-operator report <scan-name> --scan-id <scanID> > report.html
report 子命令参数:
| 参数 | 必填 | 说明 |
|---|---|---|
<scan-name> |
是 | ComplianceScan 资源名称 |
--scan-id |
否 | 指定 scanID,留空使用最近一次 |
--node |
否 | 指定节点名称,导出单节点详细报告;留空导出整体汇总报告 |
--format |
否 | 输出格式:html(默认)或 json |
-o |
否 | 输出文件路径,留空输出到 stdout |
7.1.2 Operator 多子命令架构
Operator 二进制支持多个子命令:
compliance-operator
├── manager # 默认子命令, 启动 controller-manager
├── report # 报告生成子命令
└── version # 版本信息
入口代码:
// cmd/manager/main.go
func main() {
if len(os.Args) > 1 {
switch os.Args[1] {
case "report":
reportCmd.Execute()
return
case "version":
fmt.Println(version.Info())
return
}
}
// 默认启动 manager
managerCmd.Execute()
}
7.1.3 report 子命令实现逻辑

1. 解析命令行参数(scan-name, scan-id, node, format)
2. 使用 in-cluster config 连接 K8s API
3. 读取 ComplianceScan CR
- 验证 scan-name 存在
- 若指定 scan-id,验证其在 scanHistory 中
- 若未指定 scan-id,使用最新一次(scanHistory[0])
4. 通过 scanHistory[].resultRef 读取对应的 ComplianceScanResult CR
- resultRef 存储 ComplianceScanResult 的资源名称
- 该 CR 包含聚合摘要(nodeSummaries 轻量列表 + 全局 summary)
5. 根据 --node 参数决定报告类型:
A. 整体汇总报告(--node 未指定):
- 从 ComplianceScanResult.spec.summary 获取全局统计
- 从 nodeSummaries 数组构建节点概览表(每节点一行)
- 通过 nodeSummaries[].resultRef 读取各 ComplianceScanNodeResult
- 汇总所有节点的 NonPassResults(去重合并,标注受影响节点)
- 从规则知识库查找每个失败项的 description, remediation, severity
- 组装整体汇总报告数据
B. 单节点详细报告(--node <nodeName>):
- 从 nodeSummaries 数组中查找 nodeName 匹配的 NodeSummary
- 若未找到,返回错误: "node <nodeName> not found in scan <scanID>"
- 通过 NodeSummary.resultRef 读取对应的 ComplianceScanNodeResult CR
- 从 ComplianceScanNodeResult.spec.nonPassResults 获取详细结果
- 从规则知识库查找该节点所有检查项的完整信息
- 组装单节点详细报告数据
6. 根据 format 参数渲染输出
- html: 使用 Go html/template 渲染 HTML 报告
- json: 直接输出报告数据的 JSON
7. 输出到 stdout
7.1.4 部署方式
Operator Deployment 中无需额外配置,report 子命令复用 Operator 镜像内的模板和规则知识库:
# 用户操作示例(无需额外部署)
kubectl exec -n compliance-operator-system deploy/compliance-operator -- \
/compliance-operator report cis-full-scan --scan-id 20260528-143022-a7f3 > report.html
优势:
- 零额外部署:无需安装 kubectl plugin 或分发二进制文件
- 规则知识库内置:CIS/STIG 规则定义编译时嵌入 Operator 镜像
- 权限复用:使用 Operator 的 ServiceAccount,已有读取 CRD 的权限
- 版本一致:报告模板和规则知识库与 Operator 版本同步更新
7.2 HTML 报告模板设计
报告采用单文件 HTML 设计,内嵌 CSS 样式,无需外部依赖。根据 --node 参数分为两种模板。
7.2.1 整体汇总报告模板(--node 未指定)
+----------------------------------------------------------+
| Compliance Scan Report (Overall) |
| Scan Name: cis-benchmark-scan |
| Scan ID: 20260528-143022-a7f3 |
| Scanner: kube-bench | Benchmark: cis-1.8 |
| Time: 2026-05-28 14:30:22 - 14:35:10 |
| Nodes: 4 (2 control-plane, 2 worker) |
+----------------------------------------------------------+
| Global Summary |
| +--------+--------+--------+--------+--------+ |
| | PASS | FAIL | WARN | INFO | TOTAL | |
| | 149 | 11 | 8 | 5 | 173 | |
| +--------+--------+--------+--------+--------+ |
| Compliance Rate: 86.1% |
+----------------------------------------------------------+
| Node Overview |
| +------------------+----------+------+------+------+-------+
| | Node | Role | Pass | Fail | Warn | Score |
| +------------------+----------+------+------+------+-------+
| | cp-node-1 | CP | 45 | 5 | 3 | 84.9% |
| | cp-node-2 | CP | 47 | 3 | 3 | 88.7% |
| | worker-1 | Worker | 28 | 2 | 1 | 90.3% |
| | worker-2 | Worker | 29 | 1 | 1 | 93.5% |
| +------------------+----------+------+------+------+-------+
+----------------------------------------------------------+
| Failed Items Summary (deduplicated across nodes) |
| |
| [FAIL] 1.2.1 Ensure anonymous-auth is disabled |
| Severity: high | Affected nodes: cp-node-1, cp-node-2 |
| Actual Value: anonymous-auth=true |
| Expected: anonymous-auth=false |
| Remediation: Edit kube-apiserver manifest... |
| --- |
| [FAIL] V-242379 etcd must use TLS |
| Severity: medium | Affected nodes: cp-node-1 |
| Actual Value: auto-tls=true |
| Expected: auto-tls=false |
| Remediation: Edit etcd manifest... |
+----------------------------------------------------------+
7.2.2 单节点详细报告模板(--node worker-1)
+----------------------------------------------------------+
| Compliance Scan Report (Node Detail) |
| Scan Name: cis-benchmark-scan |
| Scan ID: 20260528-143022-a7f3 |
| Scanner: kube-bench | Benchmark: cis-1.8 |
| Node: worker-1 | Role: worker |
| Time: 2026-05-28 14:32:05 - 14:33:12 |
+----------------------------------------------------------+
| Node Summary |
| +--------+--------+--------+--------+--------+ |
| | PASS | FAIL | WARN | INFO | TOTAL | |
| | 28 | 2 | 1 | 0 | 31 | |
| +--------+--------+--------+--------+--------+ |
| Node Compliance Rate: 90.3% |
+----------------------------------------------------------+
| All Check Items |
| +--------+------------------------------------------+--------+
| | ID | Description | Status |
| +--------+------------------------------------------+--------+
| | 4.1.1 | Kubelet service file permissions 644 | PASS |
| | 4.1.2 | Kubelet service file ownership root:root | PASS |
| | 4.2.1 | Ensure anonymous-auth is false | FAIL |
| | 4.2.2 | Ensure authorization-mode is not AlwaysAllow | PASS |
| | 4.2.3 | Ensure --client-ca-file is set | FAIL |
| | ... | ... | ... |
| +--------+------------------------------------------+--------+
+----------------------------------------------------------+
| Failed Items Detail |
| |
| [FAIL] 4.2.1 Ensure anonymous-auth is false |
| Severity: high |
| Actual Value: anonymous-auth=true |
| Expected: anonymous-auth=false |
| Remediation: Edit kubelet config... |
| --- |
| [FAIL] 4.2.3 Ensure --client-ca-file is set |
| Severity: medium |
| Actual Value: (not set) |
| Expected: --client-ca-file=<path> |
| Remediation: Set client-ca-file in kubelet config... |
+----------------------------------------------------------+
7.3 报告内容结构
整体汇总报告
| 章节 | 内容 |
|---|---|
| Header | 扫描名称、ID、引擎、基准版本、时间范围、节点数量 |
| Global Summary | 所有节点汇总的 pass/fail/warn/info 统计,整体合规率 |
| Node Overview | 节点概览表:每个节点的 pass/fail/warn/score |
| Failed Items Summary | 所有节点的失败项去重合并,标注受影响的节点列表 |
单节点详细报告
| 章节 | 内容 |
|---|---|
| Header | 扫描名称、ID、引擎、基准版本、节点名称、节点角色、时间范围 |
| Node Summary | 该节点的 pass/fail/warn/info 统计,节点合规率 |
| All Check Items | 该节点的完整检查项列表(含通过项),按 ID 排序 |
| Failed Items Detail | 该节点所有失败项的详细信息(描述、严重级别、实际值、期望值、修复建议) |
8. RBAC 与安全设计
8.1 Operator ServiceAccount 权限
Operator 需要以下 RBAC 权限:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: compliance-operator-manager
rules:
# CRD 资源操作(合并三种 CRD)
- apiGroups: ["compliance.openfuyao.cn"]
resources: ["compliancescans", "compliancescanresults", "compliancescannoderesults"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete", "deletecollection"]
- apiGroups: ["compliance.openfuyao.cn"]
resources: ["compliancescans/status", "compliancescanresults/status", "compliancescannoderesults/status"]
verbs: ["get", "update", "patch"]
- apiGroups: ["compliance.openfuyao.cn"]
resources: ["compliancescans/finalizers", "compliancescanresults/finalizers", "compliancescannoderesults/finalizers"]
verbs: ["update"]
# Job 管理
- apiGroups: ["batch"]
resources: ["jobs"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete", "deletecollection"]
# Pod 日志读取(结果回收主通道)
- apiGroups: [""]
resources: ["pods", "pods/log"]
verbs: ["get", "list"]
# 节点查询(用于节点选择)
- apiGroups: [""]
resources: ["nodes"]
verbs: ["get", "list", "watch"]
# Event 记录
- apiGroups: [""]
resources: ["events"]
verbs: ["create", "patch"]
# ConfigMap/Lease(leader 选举)
- apiGroups: [""]
resources: ["configmaps"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: ["coordination.k8s.io"]
resources: ["leases"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
8.2 Scan Job ServiceAccount 权限
Scan Job Pod 的核心扫描逻辑(kube-bench/OpenSCAP)仅通过 hostPath 读取本地文件系统和进程信息,结果通过 stdout 输出供 Operator 读取,不直接调用 Kubernetes API。
部署方式差异:
| 部署方式 | Scan Job ClusterRole | 说明 |
|---|---|---|
config/rbac/scan_job_role.yaml(kustomize) |
rules: [](空规则) |
最小权限,kube-bench CIS Section 5(RBAC 检查项)将返回 WARN |
Helm chart templates/clusterrole.yaml |
包含 RBAC/资源读取规则 | kube-bench Section 5 检查可通过 |
Helm chart 中 Scan Job ClusterRole 包含以下额外规则(用于 kube-bench CIS Section 5 RBAC 策略检查):
# Helm chart 中的 Scan Job ClusterRole(仅 Helm 部署时生效)
rules:
- apiGroups: ["rbac.authorization.k8s.io"]
resources: ["clusterroles", "clusterrolebindings", "roles", "rolebindings"]
verbs: ["get", "list"]
- apiGroups: [""]
resources: ["pods", "serviceaccounts", "namespaces"]
verbs: ["get", "list"]
- apiGroups: ["authorization.k8s.io"]
resources: ["selfsubjectaccessreviews"]
verbs: ["create"]
建议:若使用 kustomize 部署且需要 kube-bench Section 5 检查通过,应将上述规则同步到
config/rbac/scan_job_role.yaml。
8.3 hostPath 安全策略
| 策略 | 实施方式 |
|---|---|
| 只读挂载 | 所有 hostPath volume 设置 readOnly: true |
| 最小挂载范围 | kube-bench 仅挂载必要目录;openscap 挂载根目录但只读 |
| 非特权运行 | kube-bench Pod 无容器级 SecurityContext(需要 root 访问 host 文件,仅 Pod 级 seccomp);OpenSCAP Pod 设置 privileged: true, runAsUser: 0 以访问主机文件系统进行 OVAL 探测 |
| 禁止提权 | OpenSCAP 容器设置 allowPrivilegeEscalation: false;kube-bench 未设置(需要 root) |
| 只读根文件系统 | OpenSCAP 容器设置 readOnlyRootFilesystem: true;kube-bench 未设置 |
| 最小 capabilities | OpenSCAP 容器设置 capabilities.drop: ["ALL"];kube-bench 未设置 |
| Seccomp | Pod 级 seccompProfile.type: RuntimeDefault(两种引擎均适用) |
kube-bench Pod SecurityContext(实际实现):
# Pod 级安全上下文(kube-bench 无容器级 SecurityContext)
securityContext:
seccompProfile:
type: RuntimeDefault
# 容器以 root 运行,无额外安全限制
# 原因:kube-bench 需要读取 /etc/kubernetes/pki(权限 600)等目录
OpenSCAP Pod SecurityContext(实际实现):
securityContext:
seccompProfile:
type: RuntimeDefault
containers:
- name: scanner
securityContext:
privileged: true
readOnlyRootFilesystem: true
runAsUser: 0
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
安全权衡:kube-bench 需要以 root 身份运行以读取
/etc/kubernetes/pki等权限为 600 的证书文件。OpenSCAP 需要privileged: true因为 OVAL probe 的lstat()在 mode 700 的 root 文件上会返回 EPERM。建议在 PSA(Pod Security Admission)策略中为 compliance-scan Job 创建例外。
8.4 ServiceAccount Token 策略
| 部署方式 | automountServiceAccountToken | 说明 |
|---|---|---|
config/rbac/service_account.yaml(kustomize) |
false |
SA 级别禁止自动挂载,但 Deployment PodSpec 未设置覆盖,Pod 将无法访问 K8s API |
Helm chart templates/serviceaccount.yaml |
true |
SA 级别允许自动挂载,Deployment PodSpec 也设置 true |
注意:Operator 必须能够访问 K8s API(Watch CRD、创建 Job、读取 Pod logs),因此
automountServiceAccountToken必须为true。使用 kustomize 部署时需确保 Deployment PodSpec 中显式设置automountServiceAccountToken: true以覆盖 SA 级别的false设置。
8.5 网络安全
| 通信 | 安全机制 |
|---|---|
| Controller -> API Server | 使用 ServiceAccount Token + TLS |
| Controller -> Scan Job Pod | 通过 API Server 读取 Pod logs(HTTPS) |
| Scan Job -> 节点文件系统 | hostPath 只读,无网络通信 |
| Pod 间通信 | 无(各 Job Pod 独立运行,不互相通信) |
8.6 Prometheus 指标
Operator 通过 controllers/metrics.go 定义了以下 Prometheus 指标,监听在 :8080/metrics 端点:
| 指标名称 | 类型 | 说明 |
|---|---|---|
compliance_scan_total |
Counter | 扫描执行总次数,按 scanner(kube-bench/openscap)和 result(Compliant/NonCompliant/Error)标签分类 |
compliance_scan_duration_seconds |
Histogram | 单次扫描耗时(秒),按 scanner 标签分类 |
compliance_reconcile_errors_total |
Counter | Reconcile 错误总次数,按 phase 标签分类 |
compliance_scan_failures_total |
Counter | 扫描失败总次数,按 scanner 和 reason(timeout/job_failure/parse_error)标签分类 |
指标暴露需要启用 metrics Service(Helm chart 中 metrics.enabled: true)和可选的 ServiceMonitor(serviceMonitor.enabled: true)。
8.7 敏感数据处理
| 数据类型 | 处理策略 |
|---|---|
| 证书文件内容 | 扫描引擎仅检查文件权限和存在性,不读取证书私钥内容 |
| 配置文件内容 | 检查结果仅记录配置参数值(如 --anonymous-auth=false),不记录完整文件 |
| 扫描结果 | 存储在 etcd 中,受 RBAC 保护 |
| rawResult 字段 | 可选存储,包含完整扫描输出,建议仅在需要审计时启用 |
| ServiceAccount Token | 使用 projected volume,自动过期 |
9. 部署设计
9.1 Operator Deployment YAML
apiVersion: apps/v1
kind: Deployment
metadata:
name: compliance-operator
namespace: compliance-operator-system
labels:
app.kubernetes.io/name: compliance-operator
app.kubernetes.io/component: controller-manager
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: compliance-operator
template:
metadata:
labels:
app.kubernetes.io/name: compliance-operator
spec:
serviceAccountName: compliance-operator
securityContext:
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
containers:
- name: manager
image: registry.example.com/compliance-operator:v0.1.0
command:
- /compliance-operator
args:
- manager
- --leader-elect
- --max-concurrent-reconciles=1
ports:
- containerPort: 8080
name: metrics
protocol: TCP
- containerPort: 8081
name: health
protocol: TCP
startupProbe:
httpGet:
path: /healthz
port: health
initialDelaySeconds: 5
periodSeconds: 5
failureThreshold: 30
livenessProbe:
httpGet:
path: /healthz
port: health
initialDelaySeconds: 15
periodSeconds: 20
readinessProbe:
httpGet:
path: /readyz
port: health
initialDelaySeconds: 5
periodSeconds: 10
resources:
limits:
cpu: 500m
memory: 256Mi
requests:
cpu: 100m
memory: 128Mi
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
terminationGracePeriodSeconds: 10
9.2 CRD YAML(摘要)
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: compliancescans.compliance.openfuyao.cn
labels:
app.kubernetes.io/name: compliance-operator
app.kubernetes.io/component: crd
spec:
group: compliance.openfuyao.cn
names:
kind: ComplianceScan
listKind: ComplianceScanList
plural: compliancescans
singular: compliancescan
shortNames:
- cs
scope: Cluster
versions:
- name: v1alpha1
served: true
storage: true
subresources:
status: {}
additionalPrinterColumns:
- name: Scanner
type: string
jsonPath: .spec.scanner
- name: Phase
type: string
jsonPath: .status.phase
- name: Result
type: string
jsonPath: .status.lastScanResult
- name: LastScan
type: date
jsonPath: .status.lastScanTime
- name: Age
type: date
jsonPath: .metadata.creationTimestamp
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
required: ["scanner"]
properties:
scanner:
type: string
enum: ["kube-bench", "openscap"]
description: "Scanner engine to use for compliance checks"
scanType:
type: string
enum: ["control-plane", "worker", "all"]
default: "all"
description: "Type of nodes to scan"
profile:
type: string
description: "OpenSCAP profile ID (only for openscap scanner)"
benchmark:
type: string
description: "CIS benchmark version (only for kube-bench scanner)"
schedule:
type: string
description: "Cron expression for scheduled scanning (e.g., '0 2 * * *')"
timeout:
type: string
default: "30m"
pattern: '^([0-9]+h)?([0-9]+m)?([0-9]+s)?$'
description: "Scan timeout duration (e.g., '30m', '1h')"
maxHistory:
type: integer
minimum: 1
maximum: 20
default: 5
description: "Maximum number of scan history entries to retain"
nodeSelector:
type: object
additionalProperties:
type: string
description: "Node selector for targeting specific nodes"
tolerations:
type: array
description: "Tolerations for scheduling scan jobs"
items:
type: object
properties:
key:
type: string
operator:
type: string
enum: ["Exists", "Equal"]
value:
type: string
effect:
type: string
enum: ["NoSchedule", "PreferNoSchedule", "NoExecute"]
tolerationSeconds:
type: integer
minimum: 0
imagePullPolicy:
type: string
enum: ["Always", "IfNotPresent", "Never"]
default: "IfNotPresent"
description: "Image pull policy for scanner containers"
imagePullSecrets:
type: array
description: "Image pull secrets for private registry authentication"
items:
type: object
properties:
name:
type: string
resources:
type: object
description: "Resource requirements override for scanner containers"
priorityClassName:
type: string
description: "Priority class name for scan job pods"
status:
type: object
properties:
phase:
type: string
enum: ["Pending", "Launching", "Running", "Collecting", "Done", "Failed", "Ignored"]
description: "Current phase of the scan lifecycle (Ignored = singleton protection active)"
lastScanTime:
type: string
format: date-time
description: "Timestamp of the last scan execution"
lastScanID:
type: string
description: "ID of the last scan"
lastScanResult:
type: string
enum: ["Compliant", "NonCompliant", "Error"]
description: "Result of the last scan"
activeJobs:
type: integer
minimum: 0
description: "Number of currently active scan jobs"
activeScanner:
type: string
enum: ["kube-bench", "openscap"]
description: "Scanner type actually used for the current scan cycle (snapshot at Launching)"
scanHistory:
type: array
maxItems: 20
description: "History of scan executions (most recent first)"
items:
type: object
required: ["scanID", "scanTime", "phase", "result", "resultRef", "summary"]
properties:
scanID:
type: string
pattern: '^[a-z0-9-]+$'
maxLength: 63
description: "Unique identifier for this scan"
scanTime:
type: string
format: date-time
description: "Timestamp when the scan was executed"
phase:
type: string
enum: ["Done", "Failed"]
description: "Final phase of the scan"
result:
type: string
enum: ["Compliant", "NonCompliant", "Error"]
description: "Result of the scan"
resultRef:
type: string
description: "Name of the ComplianceScanResult CR for this scan"
summary:
type: object
required: ["pass", "fail", "warn", "info", "error", "total", "notApplicable", "notChecked"]
description: "Summary of scan results across all nodes (8 fields)"
properties:
pass:
type: integer
minimum: 0
description: "Number of passed checks"
fail:
type: integer
minimum: 0
description: "Number of failed checks"
warn:
type: integer
minimum: 0
description: "Number of warnings"
info:
type: integer
minimum: 0
description: "Number of informational checks"
error:
type: integer
minimum: 0
description: "Number of checks that encountered an error"
total:
type: integer
minimum: 0
description: "Total number of checks"
notApplicable:
type: integer
minimum: 0
description: "Number of checks not applicable to this system"
notChecked:
type: integer
minimum: 0
description: "Number of checks that were not evaluated"
conditions:
type: array
description: "Standard Kubernetes conditions"
items:
type: object
properties:
type:
type: string
status:
type: string
enum: ["True", "False", "Unknown"]
lastTransitionTime:
type: string
format: date-time
reason:
type: string
message:
type: string
9.3 RBAC 资源
# ServiceAccount - Operator
apiVersion: v1
kind: ServiceAccount
metadata:
name: compliance-operator
namespace: compliance-operator-system
---
# ServiceAccount - Scan Job
apiVersion: v1
kind: ServiceAccount
metadata:
name: compliance-scan-job
namespace: compliance-operator-system
---
# ClusterRoleBinding - Operator
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: compliance-operator
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: compliance-operator-manager
subjects:
- kind: ServiceAccount
name: compliance-operator
namespace: compliance-operator-system
---
# ClusterRoleBinding - Scan Job
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: compliance-scan-job
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: compliance-scan-job
subjects:
- kind: ServiceAccount
name: compliance-scan-job
namespace: compliance-operator-system
9.4 Helm Chart 结构
charts/compliance-operator/
|-- Chart.yaml
|-- values.yaml
|-- crds/ # CRD YAML(Helm 安装时自动加载)
| |-- compliance.openfuyao.cn_compliancescans.yaml
| |-- compliance.openfuyao.cn_compliancescanresults.yaml
| |-- compliance.openfuyao.cn_compliancescannoderesults.yaml
|-- templates/
| |-- _helpers.tpl # 模板辅助函数
| |-- deployment.yaml # Operator Deployment
| |-- serviceaccount.yaml # ServiceAccount(operator + scan-job)
| |-- clusterrole.yaml # ClusterRole(operator + scan-job)
| |-- clusterrolebinding.yaml # ClusterRoleBinding
| |-- service.yaml # Metrics Service(条件启用)
| |-- servicemonitor.yaml # ServiceMonitor(条件启用)
| |-- networkpolicy.yaml # NetworkPolicy(条件启用)
| |-- pdb.yaml # PodDisruptionBudget(条件启用)
| |-- NOTES.txt # 安装后提示
| |-- tests/
| |-- test-connection.yaml # 连通性测试
| |-- test-crds.yaml # CRD 安装测试
| |-- test-configmap.yaml # 模板渲染测试
values.yaml 关键配置项:
replicaCount: 1
image:
repository: cr.openfuyao.cn/openfuyao/compliance-operator
tag: latest
pullPolicy: Always
scanJob:
images:
kubeBench: hub.oepkgs.net/openfuyao/aquasec/kube-bench:v0.15.6
openSCAP: cr.openfuyao.cn/openfuyao/openscap:1.4.4
resources:
limits:
cpu: 1000m
memory: 1024Mi
requests:
cpu: 250m
memory: 256Mi
controller:
leaderElect: true
maxConcurrentReconciles: 1
logging:
devMode: true
metrics:
enabled: false
serviceMonitor:
enabled: false
networkPolicy:
enabled: false
pdb:
minAvailable: 1
nodeSelector: {}
tolerations: []
affinity: {}
10. 风险与缓解
| 风险 | 影响 | 概率 | 缓解措施 |
|---|---|---|---|
| 扫描结果超过 etcd 1.5MB 限制 | ComplianceScanNodeResult 写入失败 | 低 | 三级 CRD 拆分设计:ComplianceScanResult 仅存轻量摘要,ComplianceScanNodeResult 仅存 NonPassResults;rawResult 字段可选且压缩;单节点非 PASS 结果通常 < 100KB |
| hostPath 挂载引发安全问题 | 容器逃逸或敏感数据泄露 | 低 | 只读挂载;最小 capabilities;非 root 运行;PSA 策略例外管理 |
| 扫描 Job 影响节点性能 | 节点 CPU/IO 负载升高 | 低 | kube-bench 和 oscap 均为轻量级工具;可通过 resources.limits 限制 |
| STIG 内容文件版本过期 | 扫描结果不符合最新标准 | 中 | 使用包含最新 SCAP 规则文件的扫描器镜像;文档提示定期更新 |
| kube-bench 版本与 K8s 版本不匹配 | 部分检查项无法执行或结果不准确 | 中 | 版本映射策略;文档说明支持的 K8s 版本范围 |
| 大量节点同时扫描导致 API Server 压力 | 大量 Job 同时创建/完成事件 | 低 | MaxConcurrentReconciles 限制并发;Job TTL 自动清理 |
| ComplianceScanResult 累积导致 etcd 空间不足 | etcd 存储压力 | 低 | maxHistory 限制(默认 5);自动淘汰最旧记录 |
| OpenSCAP 退出码 2 被误判为错误 | 扫描结果丢失 | 中 | 明确处理退出码 2 为有效结果;在 Job 脚本中特殊处理 |
| 节点标签不一致导致调度失败 | 部分节点未被扫描 | 中 | 提供 nodeSelector 覆盖选项;文档说明默认标签要求 |
| CronJob 调度与手动触发冲突 | 重复扫描 | 低 | 检查当前 phase,非 Done/Failed 状态忽略新触发 |
11. 测试计划
11.1 单元测试
| 测试项 | 覆盖范围 | 验证目标 |
|---|---|---|
| scanID 生成 | pkg/util/scanid.go | 格式正确(YYYYMMDD-HHmmss-XXXX),唯一性 |
| kube-bench JSON 解析 | pkg/scanner/kubebench/parser.go | 正确映射所有字段到 UnifiedCheckResult |
| OpenSCAP XCCDF 解析 | pkg/scanner/openscap/parser.go | 正确解析 rule-result,处理各种 result 值 |
| 状态映射 | pkg/unified/model.go | kube-bench/OpenSCAP 状态正确映射到统一状态 |
| 历史记录管理 | controllers/history_manager.go | 超过 maxHistory 时正确淘汰最旧记录 |
| Cron 表达式解析 | controllers/schedule_manager.go | 正确计算下次执行时间 |
| Job Spec 构建 | controllers/scan_job_builder.go | 根据 scanner/scanType 正确构建 Job Spec |
| 规则知识库加载 | pkg/report/cis_loader.go, stig_loader.go | CIS/STIG 规则正确加载,版本发现正常 |
11.2 集成测试
| 测试项 | 覆盖范围 | 验证目标 |
|---|---|---|
| Controller Reconcile 链路 | controllers/ | 完整状态机转换(Pending -> Done) |
| Job 创建与监控 | controllers/ | Job 创建成功,状态变化被正确捕获 |
| Result CRD 写入 | controllers/result_collector.go | ComplianceScanResult 创建成功,数据完整 |
| 历史淘汰 | controllers/history_manager.go | 超过 maxHistory 后旧 Result 被删除 |
| 注解触发 | controllers/ | 注解添加后触发新扫描 |
| 超时处理 | controllers/ | 超时后 Job 被删除,状态标记为 Failed |
| 错误恢复 | controllers/ | API 通信失败后自动重试 |
11.3 端到端测试
| 测试项 | 场景 | 验证目标 |
|---|---|---|
| kube-bench 全量扫描 | 创建 ComplianceScan (scanner=kube-bench, scanType=all) | 所有节点被扫描,结果正确汇总 |
| OpenSCAP STIG 扫描 | 创建 ComplianceScan (scanner=openscap, scanType=control-plane) | 仅控制平面节点被扫描,STIG 规则正确评估 |
| 扫描历史管理 | 连续触发 6 次扫描 | 仅保留最近 5 次,最旧记录被淘汰 |
| 报告导出 | kubectl exec deploy/compliance-operator -- /compliance-operator report --scan-id | HTML 报告正确生成,内容与 CRD 数据一致 |
| 定时扫描 | 设置 schedule="*/5 * * * *" | 每 5 分钟自动触发扫描 |
| 超时场景 | 设置 timeout=1m,扫描耗时超过 1 分钟 | 扫描被终止,状态标记为 Failed |
| 节点不存在 | scanType=control-plane 但集群无 control-plane 节点 | 状态标记为 Failed,Event 记录原因 |
| 降级测试 | 删除 Operator Deployment | CRD 数据保留,重新部署后恢复正常 |
11.4 安全测试
| 测试项 | 验证目标 |
|---|---|
| RBAC 权限验证 | Operator 和 Scan Job 的 ServiceAccount 仅拥有最小必要权限 |
| hostPath 只读验证 | 扫描 Pod 无法写入主机文件系统 |
| 非特权运行验证 | 扫描 Pod 不以 privileged 模式运行 |
| 敏感数据检查 | 扫描结果不包含证书私钥、密码等敏感数据 |
| 网络隔离验证 | 扫描 Pod 之间无法互相通信 |
11.5 性能测试
| 测试项 | 验证目标 |
|---|---|
| 大规模节点扫描 | 50 节点集群同时扫描,API Server 无明显性能下降 |
| etcd 存储压力 | 5 个 ComplianceScan x 5 条历史 x 多节点,etcd 存储占用可接受 |
| 扫描执行时间 | kube-bench 单节点 < 5 分钟,OpenSCAP 单节点 < 10 分钟 |
| Controller 内存占用 | 正常运行 < 128MB,峰值 < 256MB |
附录 A: STIG 规则分类统计
基于 DISA Kubernetes STIG V2R4 (U_Kubernetes_V2R4_STIG_SCAP_1-3_Benchmark.xml) 分析:
| 分类 | 规则数量 | 示例规则 |
|---|---|---|
| TLS/加密配置 | 8 | V-242376 (Controller Manager TLS), V-242377 (Scheduler TLS), V-242378 (API Server TLS) |
| 认证授权 | 6 | V-242382 (authorization-mode), V-242390 (anonymous-auth), V-245542 (basic-auth) |
| 审计日志 | 5 | V-242402 (audit-log-path), V-242462 (audit-log-maxsize), V-242464 (audit-log-maxage) |
| 证书管理 | 12 | V-242419 (client-ca-file), V-242422 (tls-cert-file), V-242451 (PKI permissions) |
| etcd 安全 | 8 | V-242379 (auto-tls), V-242423 (client-cert-auth), V-242449 (data dir permissions) |
| kubelet 配置 | 8 | V-242404 (hostname-override), V-242434 (kernel defaults), V-245541 (idle timeout) |
| 文件权限 | 5 | V-242444 (kubelet.conf), V-242445 (etcd user/group), V-242451 (PKI files) |
| 其他 | 3 | V-242400 (feature-gates), V-242409 (profiling), V-242438 (request-timeout) |
| 合计 | 55 |
附录 B: OVAL 检查类型分布
| OVAL 检查类型 | 数量 | 说明 |
|---|---|---|
| textfilecontent54_test | ~40 | 正则匹配配置文件内容(manifests YAML, kubelet config) |
| file_test | ~8 | 检查文件权限、属主(PKI 文件, etcd 数据目录) |
| process58_test | ~5 | 检查进程启动参数(kubelet 命令行参数) |
| password_test | 1 | 检查 etcd 用户 UID/GID |
| rpminfo_test / dpkginfo_test | 1 | CPE 平台检测(Kubernetes 是否安装) |
附录 C: 默认 Tolerations
Scan Job Pod 默认包含以下 Tolerations,以确保能调度到控制平面节点:
tolerations:
# 控制平面节点
- key: node-role.kubernetes.io/control-plane
operator: Exists
effect: NoSchedule
- key: node-role.kubernetes.io/master
operator: Exists
effect: NoSchedule
注意:若需扫描处于 NotReady/Unreachable 状态的节点,用户可通过
spec.tolerations自行添加对应的容忍规则。