Polaris 配置审计与持续治理
一个支付服务在预发布环境运行正常,上线后却在节点维护时出现长时间不可用。Deployment 有三个副本,却没有 readiness probe,也没有 PodDisruptionBudget;滚动更新期间,新 Pod 尚未真正接管流量,旧 Pod 又被驱逐。复盘时,团队发现这些配置问题早就在 Helm 渲染结果里,只是代码评审主要关注镜像和业务参数,没有稳定的检查证据。
另一个团队把同类问题直接交给准入 Webhook 阻断。某次证书轮换失败后,Webhook 没有可用 endpoint,而 failurePolicy 仍为 Fail,匹配范围内的新工作负载全部创建失败。安全检查没有误判业务配置,却因为自身不可用放大成发布事故。真正困难的不是“跑出一个分数”,而是让审计、告警、拒绝、例外和恢复分别拥有可验证的边界。
先分清 CLI、Dashboard 与 Webhook 的责任
Polaris 检查 Kubernetes 工作负载配置中的可靠性、效率和安全问题,但三个入口承担的风险完全不同。CLI 读取 YAML、Helm 渲染结果或集群对象,适合开发机和 CI 的一次性审计;Dashboard 读取集群当前对象并展示结果,适合发现存量问题;Webhook 参与 API Server 的 admission 请求,可拒绝危险配置,也可选用 mutation 修改部分字段。
这三者不能用同一个“已接入”概括。CLI 失败只影响当前流水线;Dashboard 不可用通常只失去观察入口;ValidatingWebhook 不可用且 fail closed 时会影响匹配资源的创建与更新;MutatingWebhook 还会改变送入 API Server 的对象。风险从离线读取、集群读取一路上升到同步写路径,因此采用顺序应当也是 CLI、Dashboard、验证型 Webhook,最后才评估 mutation。
Polaris 的检查结果不是 Kubernetes 安全的总分。它不扫描镜像 CVE,不验证节点和控制平面 CIS 配置,也不观察容器运行时行为。warning 是建议级发现,默认不会因为它拒绝 admission;danger 才能进入拒绝合同。自定义检查使用 Polaris 的配置与模板能力,也不是 Rego、Kyverno 或 ValidatingAdmissionPolicy 的通用策略替代品。
固定发行物并核对集群兼容性
下面以 Polaris 应用 v10.2.0、官方 Helm Chart 6.0.0 为可复核组合。Chart 元数据要求 Kubernetes >=1.22.0-0。v10.2.0 起镜像迁移到 us-docker.pkg.dev/fairwinds-ops/oss/polaris,旧的 Quay 镜像入口已经弃用;官方也不再提供 latest、v10 之类浮动标签。安装前先看 Polaris Releases、官方 Chart 元数据 和目标集群版本,再把 Chart 包与镜像 digest 纳入组织制品锁定。
本机 CLI 可以从 release 二进制安装,也可以使用固定镜像。下载二进制时按 release 提供的校验与 Cosign 指引验证来源;CI 中应把版本写入工具镜像或依赖锁定文件,而不是每次在线抓取最新版本。先确认版本,再对单个文件运行审计:
polaris version
polaris audit \
--audit-path deploy/rendered.yaml \
--config config/polaris.yaml \
--format json \
--output-file artifacts/polaris.json \
--set-exit-code-on-danger
status=$?
printf 'polaris_exit=%s\n' "$status"
exit "$status"预期语义比输出文字更重要:没有 danger 时退出 0;存在 danger 时,--set-exit-code-on-danger 使进程退出 3;仅有 warning 时仍可退出 0。流水线必须同时保存工具版本、配置摘要、输入清单摘要、JSON 输出与退出码,否则几周后无法解释“同一清单为什么变了分数”。完整 CLI 参数以 Polaris CLI 文档 为准。
用最小权限安装只读 Dashboard
先安装不开 Webhook 的 Dashboard,把同步 admission 风险留在集群之外。执行 Helm 安装的人需要创建 namespace、Deployment、Service、ServiceAccount、ClusterRole 和 ClusterRoleBinding 等对象的权限。Dashboard 运行身份需要读取被检查的 Kubernetes 对象;它不应因为“安全工具”标签就自动获得 Secret 内容、Pod exec、节点代理或任意写权限。
helm repo add fairwinds-stable https://charts.fairwinds.com/stable
helm repo update
helm show chart fairwinds-stable/polaris --version 6.0.0
helm show values fairwinds-stable/polaris --version 6.0.0 > polaris-values-reference.yaml
helm upgrade --install polaris fairwinds-stable/polaris \
--namespace polaris \
--create-namespace \
--version 6.0.0 \
--set dashboard.enable=true \
--set webhook.enable=false \
--wait --timeout 10m安装后先检查 Deployment、Service、EndpointSlice 和实际 RBAC,再决定是否暴露界面。可用 kubectl auth can-i --as=system:serviceaccount:polaris:<service-account> list deployments.apps --all-namespaces 验证必要读取,也要对 get secrets、create pods/exec 等非必要动作做反向验证。若 Dashboard 经 Ingress 暴露,身份认证、按角色授权、TLS、访问日志、网络策略和会话期限都要在入口层落实,不能匿名发布集群命名空间、镜像名、资源配置和检查失败详情。
Chart 6.0.0 默认 Dashboard 副本数为 2,这只是两个 Pod 的期望数。若调度器把两个副本放在同一节点、Service 没有 endpoint、入口认证故障或集群读取权限被收回,界面仍不可用。检查 readyReplicas、副本节点分布和真实查询结果,才是 Dashboard 可服务的证据。
配置字段决定告警还是阻断
Polaris 配置把每个检查映射到 severity。把某项从 warning 改成 danger,不仅改变颜色,也改变 --set-exit-code-on-danger 和 Webhook 的拒绝行为。配置变更因此必须与应用代码一样经过 review、fixture 测试和版本化发布。
可以从一个很小的组织配置开始:只把经过验证、修复路径明确的检查设为 danger,其余先观察。以下片段展示字段关系,实际检查名称应从固定版本的配置文档核对:
checks:
readinessProbeMissing: warning
livenessProbeMissing: warning
cpuRequestsMissing: warning
memoryRequestsMissing: warning
runAsRootAllowed: danger
hostNetworkSet: danger
exemptions:
- controllerNames:
- approved-system-agent
rules:
- hostNetworkSetwarning 适合先建立基线、识别误报和测量修复量;danger 适合语义稳定、对象 owner 清楚且紧急绕行已经演练的规则。exemption 会从目标对象上移除检查责任,必须把它视为有期限的风险接受,而不是消除风险。生产可用 --disallow-exemptions、--disallow-config-exemptions 或 --disallow-annotation-exemptions 禁止不同来源的绕过;具体行为应按官方例外文档测试。
正反实验要固定同一份输入上下文
先做离线实验,避免把规则调试变成集群发布事故。准备两个 Deployment:正例提供 requests/limits、readiness、liveness 和非 root 安全上下文;反例删除这些字段,并显式允许 root。两者使用相同 namespace、标签和容器镜像,减少无关变量。
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-negative
spec:
replicas: 2
selector:
matchLabels: { app: api-negative }
template:
metadata:
labels: { app: api-negative }
spec:
containers:
- name: api
image: registry.example.invalid/api@sha256:REPLACE_WITH_TEST_DIGEST
securityContext:
runAsNonRoot: false正例在固定配置下应没有 danger,命令退出 0;反例应至少产生稳定的 check ID,并在 --set-exit-code-on-danger 下退出 3。这里的“预期”是实验判据,不是声称示例镜像可以运行。若反例只有 warning 却退出 0,说明工具按配置工作,不能把退出 0 写成“清单没有问题”。反过来,若正例仍失败,应先比较 check ID、配置摘要和输入对象,不要立即新增永久例外。
第二轮再验证例外:给反例加入允许的 exemption annotation 或配置项,确认发现会消失;随后加禁止对应例外来源的 CLI 参数,确认同一发现重新出现。这个正反切换证明的是例外控制是否生效。证据包中保留原始清单,而不是只保留经过 fix 或 mutation 后的对象。
项目接入从渲染产物进入 CI
Polaris 应检查最终送往集群的对象,而不只是 Helm 模板源码。Helm values、Kustomize overlay、默认值、API 版本转换和生成器都会改变最终字段。流水线先固定 Helm/Kustomize 版本,渲染所有同一发布单元的对象,再执行审计,并把结果关联到 Git revision 和渲染摘要。
set -o pipefail
mkdir -p artifacts
helm template api ./deploy/chart \
--namespace payments \
--values deploy/values-ci.yaml > artifacts/rendered.yaml
polaris audit \
--audit-path artifacts/rendered.yaml \
--config config/polaris.yaml \
--format json \
--output-file artifacts/polaris.json \
--set-exit-code-on-danger项目接入至少需要三组 fixture:应通过的标准工作负载、必须失败的危险工作负载、带有效期例外的已知特殊对象。升级 Polaris 或修改配置时,对三组结果做结构化 diff。不要以总分作为唯一门禁,因为新增一项优秀配置可能掩盖一个 danger;应按 check ID、severity、对象身份和新旧状态判断。
polaris fix 可用于本地生成候选修改,但自动修复不能跳过代码评审。probe 路径、资源 requests、用户 ID 和容器权限依赖业务语义,工具无法仅凭 YAML 知道正确值。安全做法是把修复结果输出到新文件,review diff,再由原有 Helm/Kustomize 源码承担所有权。
Dashboard 当前态不是长期审计仓库
Dashboard 从 Kubernetes API 读取当前对象并计算结果。对象被更新或删除后,旧状态不会自然成为长期历史;多集群趋势、通知、工单和集中治理也不能从一个开源 Dashboard 自动推导出来。需要审计历史时,在外部证据存储中保存对象 UID、namespace/name、Git revision、渲染 digest、Polaris 版本、配置 digest、检查 ID、severity、首次与复核状态,并设置访问控制和保留期限。
这些数据可能暴露内部镜像名、命名空间、资源预算、端口、probe 路径、安全上下文和例外原因。JSON 报告、Dashboard 截图、Webhook 日志都按安全元数据治理:限制读者,导出前脱敏,不把完整对象或 annotation 中的 Token、证书和业务标识上传到公共工单。Polaris 本身不需要业务 Secret 内容,额外读取权限应视为错误配置处理。
发现项的 owner 应落到能够修改 Helm/Kustomize 源码的团队。平台团队维护规则和工具可用性,应用团队修复业务对象,安全团队定义 danger 与风险接受标准。三个 owner 不能合并成一个无人值守的“安全平台”队列。
Webhook 先观察,再进入拒绝路径
启用 Webhook 前,先从 Admission Controller 文档核对 TLS 和 Chart 字段。有 cert-manager 时可由 Chart 协调证书;没有时必须提供 webhook.caBundle 与对应 TLS Secret。API Server 用 CA bundle 验证服务证书,Service 通过 endpoint 找到 Webhook Pod;任一环节错误都会表现为 admission 调用失败。
上线采用逐级放大:先保持 Dashboard 审计;再启用 Webhook,但将目标规则设为 warning,确认匹配范围、延迟和日志;只把单个经过 fixture 与业务验证的规则升为 danger。对临时 namespace 创建正反对象:正例应被 API Server 接受,反例应收到明确的 admission 4xx 和规则信息。随后停止全部 Webhook endpoint,验证默认 failurePolicy: Fail 时匹配请求会失败,再恢复副本并记录恢复判据。
failurePolicy: Fail 保护安全语义,却把 Webhook 可用性放入发布路径;改成 Ignore 可减少锁死风险,却会在调用失败时放过未检查对象。选择应基于业务风险、紧急变更流程和后续补扫能力,而不是为了让安装“更稳定”随意修改。namespace selector 与 object selector 要覆盖预期对象,同时排除 Webhook 自身恢复所需的路径;break-glass 应有短时凭证、双人审批、审计和事后补扫。
Mutation 的证据是对象差异
MutatingWebhook 比验证更难回滚,因为 admission 后存入 etcd 的对象已经不同于 Git 原稿。启用某项 mutation 前,先在临时 namespace 使用 server-side dry-run,保存提交对象、admission 后对象和 diff;确认默认值不会与其他 mutating webhook、API defaulting、GitOps 控制器形成字段所有权争用。
正向实验应证明目标字段被预期补齐且工作负载行为不变;反向实验应禁用 mutation 后重新创建同一对象,证明字段不再被改写。已被 mutation 改过的存量对象不会因为关闭 Webhook 自动恢复,回滚需要由声明式源码再次施加期望状态。若 GitOps 持续删除 mutation 增加的字段,而 Webhook 每次又补回,就会形成无休止 diff;此时应停止 mutation,明确字段唯一 owner,再恢复调谐。
mutation 不适合替团队猜测资源 requests、probe 或业务用户 ID。能机械确定且不会改变业务语义的字段才可能进入自动修改;任何“看起来更安全”的默认值,都要经过应用启动、滚动发布、扩缩容和故障恢复测试。
容量与高可用看同步链路的长尾
CLI 的主要成本在 CI Runner:对象数量、模板渲染时间、检查数量和并行流水线决定 CPU、内存与制品体积。Dashboard 的成本来自 Kubernetes API list/watch、对象数量、规则计算和并发访问。Webhook 则位于同步 admission 路径,P99 延迟、超时、API Server 并发和证书轮换比平均 CPU 更关键。
Chart 默认 Dashboard 与 Webhook 各有两个副本,但完整高可用还需要 topology spread 或反亲和、PodDisruptionBudget、足够的 requests、Service endpoint 监控、TLS Secret 轮换验证以及控制面到 Pod 网络的可达性。滚动升级时至少维持一个 Ready endpoint,并观察 admission 超时与拒绝类型;两副本同时 Pending 不叫 HA。
容量测试使用代表性对象批次和真实规则配置,记录对象数、渲染大小、CLI 时长、Dashboard 查询长尾、Webhook 请求率、P95/P99、超时和失败类型。阈值来自集群发布 SLO 与基线测量,不照搬示例数字。多集群通常每集群独立部署 Webhook,故障域更清楚;集中展示需要额外平台,但不要让一个跨集群共享入口成为所有 API Server 的同步依赖。
升级、回滚与退出保持 admission 可用
升级前保存 helm get values、helm get manifest、当前 Chart 与镜像 digest、Polaris 配置和 fixture 结果。先让新旧 CLI 对同一渲染产物双跑,比较 check ID、severity、退出码和修复建议;规则变化可能让旧例外失效,也可能新增大量门禁失败。Dashboard 可先在隔离 namespace 验证;Webhook 升级还要检查 API 兼容、证书、Service endpoint 和失败策略。
回滚不是只执行 helm rollback。如果新版本已经修改配置、证书或 mutation 行为,要同时恢复与旧版本匹配的 values 和规则,并检查 admission 请求恢复。出现大面积非业务拒绝、Webhook 超时或 endpoint 全空时,先按演练流程降级/暂停匹配,再修复控制器,最后对窗口内对象补做审计。
彻底退出时先把 mutation 和 validation 从业务写路径移除,确认 API Server 不再调用 Polaris,再执行 helm uninstall polaris -n polaris。随后核对 ValidatingWebhookConfiguration、MutatingWebhookConfiguration、ClusterRole、ClusterRoleBinding、Service、证书 Secret、Ingress、namespace 和外部证据存储。删除前导出仍需保留的发现与例外,撤销入口身份和自动化凭证。最终证明应是:没有 Polaris admission 调用、没有遗留 cluster-scoped 权限、没有继续暴露的 Dashboard,项目流水线也不再引用旧二进制或浮动镜像。
