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 命令,收集分散的结果文件,再人工汇总为报告。这种方式存在以下问题:

  1. 操作复杂度高:需要 SSH 登录每个节点,手动执行扫描命令,收集结果文件。
  2. 缺乏统一视图:不同节点的扫描结果格式各异,无法在集群层面统一查看合规状态。
  3. 无法追溯历史:扫描结果散落在各节点本地,无法方便地对比不同时间点的合规变化趋势。
  4. 缺乏自动化:无法与 CI/CD 流水线或定时任务集成,难以实现持续合规监控。

compliance-operator 旨在解决上述问题,通过 Kubernetes Operator 模式提供声明式的合规扫描能力。

1.2 设计目标

  1. 声明式扫描:用户通过创建/更新 CRD 即可触发扫描,无需 SSH 登录节点。
  2. 双引擎支持:同时支持 kube-bench(CIS 基准)和 OpenSCAP(STIG 规则)两种扫描引擎。
  3. 统一结果模型:将两种引擎的不同输出格式统一为通用的检查结果模型。
  4. 历史可追溯:保留最近 5 次扫描历史,支持按 scanID 精准导出历史报告。
  5. 最小权限:扫描 Pod 以最小权限运行,hostPath 仅只读挂载。
  6. etcd 友好:结果存储策略充分考虑 etcd 1.5MB 对象大小限制。

1.3 非目标

  1. 不定义 CIS/STIG 规则内容本身,仅定义运行框架与数据模型。
  2. 不支持无限历史保留,固定为最近 5 次。
  3. 不提供 Web UI,全命令行操作。
  4. 不支持自动修复(remediation),仅提供修复建议。
  5. 不支持跨集群扫描。

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

系统行为

  1. Controller 检测到新 CR 创建,进入 PENDING 状态。
  2. Controller 查询集群节点,识别 control-plane 和 worker 节点。
  3. 为每个 control-plane 节点创建一个 Job(targets: master,controlplane,etcd,policies)。
  4. 为每个 worker 节点创建一个 Job(targets: node)。
  5. Job Pod 在目标节点上执行 kube-bench run --targets <targets> --json --benchmark cis-1.8
  6. Controller 在 Collecting 阶段读取 Pod logs,解析 JSON 输出,创建 ComplianceScanResult CRD。
  7. 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

系统行为

  1. Controller 检测到新 CR,进入 PENDING。
  2. 仅选择 control-plane 节点(scanType: control-plane)。
  3. 为每个 control-plane 节点创建一个 Job。
  4. 主容器执行 OSCAP_PROBE_ROOT=/host oscap xccdf eval --profile <profile> --results-arf /results/arf.xml --results /results/results.xml /scap-content/scap-rule.xml
  5. Controller 在 Collecting 阶段读取 Pod logs,解析 XCCDF Results XML,创建 ComplianceScanResult。
  6. 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

系统行为

  1. Controller 创建 CR 后立即执行一次扫描。
  2. Controller 内部维护一个 cron 调度器,根据 spec.schedule 字段注册定时任务。
  3. 每次定时触发时,Controller 将 ComplianceScan 状态重置为 PENDING 并启动新一轮扫描。
  4. 扫描完成后自动追加历史记录,超过 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)。

三级 CRD 关系图

ComplianceScanResult 通过以下机制与 ComplianceScan 关联:

  1. OwnerReference:ComplianceScanResult 和 ComplianceScanNodeResult 的 metadata.ownerReferences 均指向 ComplianceScan,确保 ComplianceScan 被删除时级联删除所有关联的 Result。
  2. scanHistory 引用:ComplianceScan.status.scanHistory[].resultRef 存储 ComplianceScanResult 的资源名称(如 cis-scan-20260528-143022-a7f3)。
  3. 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 工作流程(已实现):

  1. 创建时:Controller 在 ComplianceScan CR 上添加 scan-cleanup finalizer
  2. 删除时
    • Kubernetes 设置 metadata.deletionTimestamp,但不立即删除
    • Controller 检测到 deletionTimestamp,通过 LabelScanName 标签删除所有关联的 Job 资源
    • 清理完成后,Controller 移除 finalizer
    • Kubernetes 完成删除

ComplianceScanResult 清理机制

ComplianceScanResult 和 ComplianceScanNodeResult 的清理依赖 OwnerReference 级联删除而非独立 finalizer。当 ComplianceScan 被删除时,Kubernetes 自动级联删除所有 OwnerReference 指向它的 Result 和 NodeResult CR。ComplianceScanResultReconciler 当前为被动 Watch 实现(仅日志记录),不包含 finalizer 管理逻辑。

设计决策说明

选择"聚合摘要 CR + 单节点详细 CR"的三级模型的原因:

  1. etcd 友好:ComplianceScanResult 仅存储轻量 NodeSummaries(每项约 200 字节),即使 50 节点也只有 ~10KB。详细结果分散在独立的 ComplianceScanNodeResult 中,每个 CR < 100KB,远低于 etcd 1.5MB 限制。
  2. 按需加载:整体报告只需读取 ComplianceScanResult(一次 API 调用)。单节点详细报告通过 NodeSummary.resultRef 精准读取对应的 ComplianceScanNodeResult。
  3. 运维简单:5 次历史 = 5 个 ComplianceScanResult CR + N 个 ComplianceScanNodeResult CR。淘汰时通过 OwnerReference 级联删除。
  4. 引用简洁: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 计数。这是因为:

  1. PASS 项占大多数:典型扫描中 PASS 项占 80-90%,但信息量低(仅表示"配置正确")。
  2. 减少 etcd 存储:不存储 PASS 详情可将单节点 CR 大小从 ~100KB 降至 ~10KB。
  3. 报告仍可完整:报告导出时,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/v1 CustomResourceDefinition 定义。
  • 通过 served: true, storage: true 标记当前版本。

3.5.2 升级路径

当 API 从 v1alpha1 升级到 v1beta1 或 v1 时:

  1. 在 CRD 中添加新版本,同时保留旧版本(served: true, storage: false)。
  2. 实现 Conversion Webhook 处理版本间转换。
  3. 使用 kubectl convert 验证转换逻辑。
  4. 将所有存储对象迁移到新版本后,移除旧版本。

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-benchopenscap。每个 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 做节点类型区分

CPE 平台自动检测流程

<!-- 控制平面检测:检查 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 一致。
  • 超时后处理
    1. Controller 删除所有关联的活跃 Job。
    2. 更新 status.phase = Failed。
    3. 记录 Event: ScanTimeout: scan exceeded timeout of <duration>
    4. 已完成的 ComplianceScanResult 保留(部分结果仍可用于分析)。

6. 触发机制

6.1 手动触发(注解触发)

通过向 ComplianceScan CR 添加注解触发新一轮扫描:

kubectl annotate compliancescan cis-benchmark-scan \
  compliance.openfuyao.cn/trigger="$(date +%s)" --overwrite

处理流程:

  1. Controller Watch 到 CR 的注解变更。
  2. 检查当前 phase 是否为 Done 或 Failed(仅在这两种状态下允许触发)。
  3. 清除 trigger 注解。
  4. 将 phase 重置为 Pending。
  5. 重新入队处理。

6.2 定时触发(spec.schedule)

当 spec.schedule 不为空时,Controller 内部维护 cron 调度:

  1. CR 创建时立即执行一次扫描。
  2. Controller 解析 cron 表达式,计算下次执行时间。
  3. 更新 status.nextScheduleTime。
  4. 到达执行时间时,将 phase 重置为 Pending。
  5. 使用 robfig/cron 库进行 cron 表达式解析和调度。

调度精度:分钟级(标准 5 字段 cron 表达式)。

6.3 CR 创建触发

首次创建 ComplianceScan CR 时自动触发扫描:

  1. Controller 检测到新 CR(status.phase 为空)。
  2. 设置 status.phase = Pending。
  3. 进入正常扫描流程。

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 子命令实现逻辑

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 扫描失败总次数,按 scannerreason(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 自行添加对应的容忍规则。