joesevv/k8s-security-lab
GitHub: joesevv/k8s-security-lab
一个以攻击→拦截演示为驱动的加固 Kubernetes 集群与安全软件供应链实验室,每项控制均附带可复现的攻击证据与运行手册。
Stars: 0 | Forks: 0
# k8s-security-lab
加固的 Kubernetes 集群 + 安全的软件供应链,旨在演示“攻击 → 控制措施拦截它”。
本仓库中的每项控制措施都包含三样东西:针对实时集群进行的攻击运行、攻击被拒绝的原始记录,以及重现这两者的运行手册。没有捕获拒绝记录的控制会被列为路线图,而不是功能——并且当某个控制的作用范围小于其名称所暗示的范围时,会明确写下其作用范围,而不是夸大其词。
## 目标
安全是本项目的重点,而不是 DevOps。Kubernetes 和 CI 只是基础;真正的交付物是一套具体、可靠的安全控制措施。技术栈的每一层都有记录在案的攻击演示,并附带前后对比证据,因此每项控制的效果是可观察的,而不是假设的。目的是证明:当缺少控制时,特定攻击会成功;而一旦控制到位,该攻击就会被拦截。请将本仓库视为一个您可以自上而下完整阅读的案例研究。
如果您只阅读一个文件,请先阅读 [`docs/threat-model.md`](docs/threat-model.md)。它将各个阶段重新构建为由对手驱动的设计,而不是工具漫游:具有目标和假定初始能力的命名对手、五个信任边界以及每个防护措施*未*覆盖的内容、标记为完整与部分的 MITRE ATT&CK 映射,以及声明本实验室刻意留出的缺口的风险残余部分。
## 架构
一个运行在具有 WSL2 后端的 Docker Desktop 上的本地 kind 集群(1个 control-plane + 2个 worker),完全以代码形式定义,因此环境可重现。Kubernetes 被固定在 v1.35.5(节点镜像由摘要固定),因为 Kyverno 1.18 准入层仅支持 Kubernetes 1.33–1.35,所以该集群位于该支持窗口的顶端。每个安全层都添加到此基线集群之上,并使用其自己的攻击演示进行了测试。
准入路径——每个工作负载在存在之前必须通过的有序关卡集合,也是每个攻击终止的地方:
```
flowchart TB
subgraph CI["GitHub Actions — the only trusted image producer"]
direction LR
BUILD["build-scan job
build · Trivy · SBOM
NO id-token"] SIGN["sign job
id-token: write
cosign keyless, by digest"] BUILD --> SIGN end GHCR[("GHCR
image + signature")] REKOR[("Rekor
transparency log")] BUILD -- "push by digest" --> GHCR SIGN -- "signature + Fulcio cert" --> GHCR SIGN --> REKOR DEV["kubectl apply"] subgraph KIND["kind cluster seclab · Kubernetes v1.35.5 · Docker Desktop / WSL2"] direction TB subgraph CPN["seclab-control-plane"] direction TB API["kube-apiserver · authz Node,RBAC"] PSA{"1 · Pod Security Admission
enforce: restricted
BUILT-IN · runs first"} VPOL{"2 · Kyverno ValidatingPolicy ×4
workload hardening
+ registry allow-list"} IVPOL{"3 · Kyverno ImageValidatingPolicy
cosign keyless identity"} DENY["REJECTED
nothing is created"] API --> PSA PSA -- "violates restricted —
SHORT-CIRCUIT:
webhooks never run" --> DENY PSA -- pass --> VPOL VPOL -- "policy failed" --> DENY VPOL -- pass --> IVPOL IVPOL -- "unsigned" --> DENY end subgraph WKN["seclab-worker + seclab-worker2 · namespace demo"] direction LR NP["NetworkPolicy
default-deny
ingress AND egress"] CLIENT["pod labelled
access=nginx"] NGINX["nginx ×2
nginx-sa · no token"] UNAUTH["pod without
access=nginx"] APP["signed-app
signed-app-sa · no token"] CLIENT -- "TCP 8080 allowed" --> NGINX UNAUTH -. "dropped · times out" .-> NGINX end IVPOL == admitted ==> WKN end DEV --> API GHCR -. "manifest + signature
pulled at admission" .-> IVPOL REKOR -. "tlog entry verified" .-> IVPOL ``` 各关卡的详细说明、sealed-secrets 路径(从 Git 交叉进入集群的唯一其他工件),以及完整的攻击 → 控制对照表都在 [`docs/architecture.md`](docs/architecture.md) 中。 ## 安全层 ### 已构建并验证 目前正在实时集群上执行,每一项都包含已提交的攻击记录。之所以列出 Enforced scope(执行范围)列,是因为这些控制被设计为 namespace(命名空间)范围,而不是 cluster(集群)范围——参见[威胁模型的 §6.7](docs/threat-model.md#6-residual-risk-and-what-is-deliberately-out-of-scope)。 | 层级 | 工具 | 其拦截的攻击 | 执行范围 | | --- | --- | --- | --- | | RBAC 加固 | Kubernetes RBAC | 通过过于宽泛的 ServiceAccount 权限进行的权限提升 | `demo` 中的 `developer` Role(无 `secrets`,无写操作 verb,无通配符);`nginx-sa` / `signed-app-sa` 完全不挂载 token | | 准入控制 | Pod Security Admission + 4个处于 Deny 模式的 Kyverno `ValidatingPolicy` | 部署特权/不合规的工作负载(privileged、`hostPath /`、能力加回、`:latest`、未审查的 registry) | 在 `demo` 上 PSA `enforce: restricted`;四个 Kyverno 策略选择 `demo` + `demo-kyverno-only`,覆盖原生 Pod、`pods/ephemeralcontainers`、`apps/v1` 控制器和 `batch/v1` jobs | | 网络策略 | Kubernetes NetworkPolicy | Pod 之间的横向移动 | Namespace `demo` —— 默认拒绝 ingress **和** egress,一个 CoreDNS 例外,以及一对基于标签的、在 TCP 8080 上的允许规则 | | 密钥管理 | sealed-secrets | 提交到 Git 的明文密钥 | 提交的密文,其严格的作用范围绑定到 `demo/demo-app-secret`;私钥永远不会离开 `kube-system` | | 供应链 | GitHub Actions + Trivy + SBOM + cosign,由 Kyverno `ImageValidatingPolicy` 强制执行 | 运行此项目从未构建过的镜像 —— 实验室自身 registry 路径下未经签名或来源不受信任的镜像 | `demo` + `demo-kyverno-only` 中匹配 `ghcr.io/joesevv/k8s-security-lab/*` 的镜像。Docker Hub 的 `nginxinc/*` 和 `curlimages/*` 故意不包含在 glob 范围内:它们没有可证明的签名,而是受到 registry 允许列表和摘要固定的限制 | 供应链这一行有两个值得在首页而不是在脚注中说明的局限性。CVE 扫描关卡是**仅报告模式**:Trivy 以 `exit-code: '0'` 运行,因此 `CRITICAL`/`HIGH` 发现将作为 SARIF 上传,但绝不会导致构建失败。而且签名证明的是**来源,而不是没有漏洞**——带有严重 CVE 的签名镜像在设计上会被允许通过。这两点都是刻意为之的,并且都在[威胁模型的 §6](docs/threat-model.md#6-residual-risk-and-what-is-deliberately-out-of-scope) 中与本实验室未防御的其他内容一起进行了说明。 ### 路线图 尚未构建。没有策略,没有证据,没有强制执行。 | 层级 | 工具 | 其将拦截的攻击 | | --- | --- | --- | | GitOps CD | ArgoCD | 未跟踪/手动的集群配置漂移 | | 运行时检测 | Falco | 恶意运行时行为(容器中的 shell 等) | | CIS 合规性 | kube-bench | 不安全的集群配置 | | 自定义控制器(延伸目标) | Go | 强制执行特定的安全不变量 | ## 攻击 → 控制映射 每个演示的攻击对应一个条目,将攻击与拦截它的控制配对,并链接到捕获的证据。 | 控制措施 | 攻击 | 证据 | | --- | --- | --- | | 阶段 2a RBAC —— 最小权限的 `developer` Role(仅对 pods/services/deployments 执行 get/list/watch)+ 无 token 的 `nginx-sa` | 携带 token 的 Pod (`developer-sa`) 使用其挂载的 ServiceAccount token 通过 API 读取 Secrets | HTTP 403 Forbidden —— [`docs/evidence/phase-2a-rbac/`](docs/evidence/phase-2a-rbac/) (attack-output.txt, can-i-matrix.txt);运行手册 [`runbooks/phase-2a-rbac.md`](runbooks/phase-2a-rbac.md)。纵深防御:读取 secret 的路径现在被 RBAC (403) 和阶段 2c NetworkPolicy egress 默认拒绝**双重**拦截——今天重放会在网络层超时,先于 RBAC 的 403,因此有关重放警告请参见 [`REPLAY-NOTE.md`](docs/evidence/phase-2a-rbac/REPLAY-NOTE.md) | | 阶段 2b 准入 —— 4个处于 Enforce 模式的 Kyverno CEL `ValidatingPolicy` 规则(禁止 privileged,禁止 `:latest`/裸标签,必须丢弃所有 capabilities 且不能添加任何 capability,registry 允许列表),范围限定在 `demo` + `demo-kyverno-only`,加上 `demo` 上的 PSA `enforce: restricted` | 九次攻击申请:四个单规则 Pod,capability 加回,一个特权 Deployment 和一个特权 CronJob(Pod 控制器路径),通过 `kubectl debug` 进行的 ephemeral-container 注入,以及一个 `hostPath /` + `runAsUser: 0` 的 Pod | 全部在准入阶段被拒绝,每次拒绝都指明了策略名称——或者对于 `hostPath`/`runAsUser`,指明了 PodSecurity,因为**这里没有任何 Kyverno 策略覆盖 `hostPath`**,PSA 填补了这一空白;合规的工作负载仍然允许通过—— [`docs/evidence/phase-2b-admission/`](docs/evidence/phase-2b-admission/) (attack-output.txt, attack-*.yaml);运行手册 [`runbooks/phase-2b-admission.md`](runbooks/phase-2b-admission.md) | | 阶段 2c 网络 —— `demo` 中默认拒绝 ingress+egress 的 NetworkPolicies,带有 CoreDNS 例外 (53/UDP+TCP) 和一对基于标签的允许规则(允许带有 `access=nginx` 标签的 Pod 在 TCP 8080 上对 nginx 进行 ingress,与客户端 egress 匹配) | 未经授权的 Pod(没有 `access=nginx` 标签,其他方面与授权客户端完全相同)向 `nginx.demo.svc.cluster.local` 发起 curl 请求 | 连接被 NetworkPolicy 拦截 —— 5s 超时后显示 HTTP:000,而带有标签的对照组 Pod 在几毫秒内获得 HTTP:200,并且两个 Pod 仍然可以解析 DNS —— [`docs/evidence/phase-2c-network/`](docs/evidence/phase-2c-network/) (attack-output.txt, attacker-unauthorized.yaml, client-authorized.yaml);运行手册 [`runbooks/phase-2c-network.md`](runbooks/phase-2c-network.md) | | 阶段 3 密钥 —— sealed-secrets 控制器 (v0.38.4, kube-system),提交到 Git 的严格范围的 SealedSecret | 仓库读者获取已提交的 SealedSecret 并尝试恢复明文(对密文进行 base64 解码 / 重新封装到攻击者的 namespace 中) | 密文解码为乱码,没有集群内的私钥则无法进行离线解封,范围窃取被拒绝,集群内往返由 SHA-256 匹配确认,并且 `git grep` 未发现明文 —— [`docs/evidence/phase-3-secrets/`](docs/evidence/phase-3-secrets/) (attack-output.txt, scope-theft-attacker-ns.yaml);运行手册 [`runbooks/phase-3-secrets.md`](runbooks/phase-3-secrets.md) | | 阶段 4 供应链 —— Kyverno `ImageValidatingPolicy` `require-keyless-signed-ghcr`(Deny + `failurePolicy: Fail`),要求提供 cosign 无密钥签名,其 Fulcio 身份必须等于一个确切的工作流 subject,并对照 Rekor 日志进行检查;匹配 `demo` + `demo-kyverno-only` 中的 `ghcr.io/joesevv/k8s-security-lab/*` | 一个**确实存在于 registry 中且确实没有签名**的镜像 —— cosign 自身的签名工件,发布在 OCI referrers-fallback 标签 `sha256-` 下,其本身从未被任何东西签署过 —— 作为裸 Pod 以及再次作为 Deployment 部署,两者在其他方面完全符合所有其他控制要求 | 在准入阶段被拒绝,并带有策略自身的判决 (`Policy require-keyless-signed-ghcr failed: Image must be cosign keyless-signed by the k8s-security-lab GitHub Actions workflow`),在 Pod 路径和 `apps/v1` 路径上均如此;真正经过签名的摘要作为对照组仍然允许通过,而不在 glob 范围内的 nginx 工作负载不受影响 —— [`docs/evidence/phase-4-supply-chain/`](docs/evidence/phase-4-supply-chain/) (attack-output.txt, attack-unsigned-existing-artifact.yaml, attack-unsigned-image.yaml);运行手册 [`runbooks/phase-4-supply-chain.md`](runbooks/phase-4-supply-chain.md)。出于特定目的,使用 CI 从未构建过的标签的第二种情况被单独分离出来:registry 返回 404 仅能证明关卡会安全关闭,而不能证明检查了签名 |
## 仓库布局
当前的目录树结构,已精简至读者所需的部分:
```
app/signed-app/ # the image this lab builds and signs (Dockerfile + static page)
clusters/ # kind cluster config; Kyverno and sealed-secrets install notes
workloads/ # workload manifests (nginx, signed-app)
policies/ # Kyverno ValidatingPolicies (phase 2b) + the warn-only demo namespace
policies/supply-chain/ # Kyverno ImageValidatingPolicy — the cosign signature gate (phase 4)
rbac/ # RBAC roles and bindings
network/ # network policies
secrets/ # sealed-secrets manifests (ciphertext only; plaintext is gitignored)
.github/workflows/ # supply-chain CI (Trivy, SBOM, cosign keyless signing)
docs/threat-model.md # adversaries, trust boundaries, ATT&CK mapping, residual risk
docs/architecture.md # admission-path and sealed-secrets diagrams
docs/evidence/ # captured attack transcripts, one directory per phase
runbooks/ # replayable command logs, one per phase
```
## 状态
集群已启动 —— Kubernetes v1.35.5 上的 3 个节点,在 `demo` 中有两个经过加固的工作负载:nginx(来自 Docker Hub,已固定摘要,非 root 用户,无 service-account token)和 `signed-app`(本仓库自身的镜像,固定为 cosign 签名的摘要)—— 通过完整的准入链重新应用其 manifest 会通过,而同一 Deployment 如果带有未签名的镜像引用则会被拒绝。目前有五个层处于实时执行状态 —— 阶段 2a RBAC、阶段 2b 准入(PSA `restricted` + 四个处于 Deny 模式的 Kyverno `ValidatingPolicy`)、阶段 2c NetworkPolicy 默认拒绝、阶段 3 sealed-secrets 以及阶段 4 cosign 无密钥签名验证 —— 每一项都在 [`docs/evidence/`](docs/evidence/) 中包含已提交的攻击记录,并在 [`runbooks/`](runbooks/) 中有可重放的运行手册。[威胁模型](docs/threat-model.md) 和 [架构图](docs/architecture.md) 现在已涵盖整个集合。
重构后的双任务 CI 工作流现在已经运行。Run `30174855073` (2026-07-25) 是 `build-scan` / `sign` 拆分后的首次执行,这两个任务在首次执行时均通过——包括 `sign` 任务自身的 `cosign verify` 自检,该检查验证了它刚生成的签名,其依据与 Kyverno `ImageValidatingPolicy` 固定的证书身份和 OIDC 发者完全一致,逐字节匹配。该运行生成了已签名的镜像标签 `b8483c5892a16afb16c1a15aaaa35b3c8436dd65` @ `sha256:7fd13d22d934f4202edc164e525436e190498590b62c41e348b1a4092eb3337b`;工作负载的 manifest 已重新固定到该摘要,集群正在运行它,并且验证记录已捕获在 [`docs/evidence/phase-4-supply-chain/cosign-verify.txt`](docs/evidence/phase-4-supply-chain/cosign-verify.txt) 中。截至 2026-07-25,这种拆分已运行了五次——`30174855073`、`30179122521`、`30179345902`、`30179460564`、`30179497251`——在每一次运行中,`build-scan` 和 `sign` 都是绿色的(成功)。这五次运行中有三次彻底闭环了 Dependabot 的固定加追踪(pin-plus-track)循环:PR #1–#3 分别提升了 SHA 固定的 action(`docker/login-action` 3.7.0→4.5.1、`actions/upload-artifact` 4.6.2→7.0.1、`actions/checkout` 4.4.0→7.0.1),重写了固定的**两**部分——40个字符的 SHA 及其尾部的 `# vX.Y.Z` 注释,使得该固定对于审查者仍然具有可读性——并且由于工作流文件是其自身的触发路径之一,每次合并都会重新构建、重新扫描、重新签名并重新验证为绿色。这些版本升级也清除了触发它们的动机:在这些合并之前的运行带有 Node.js 20 弃用警告,明确指出了这三个 action,该警告在每次合并时都会减少一个 action,截至 2026-07-25,annotations API 对 `30179497251` 的两个任务均返回零注释。还有两个局限性值得明确指出而不是隐藏:在一天之内的五次运行,且应用程序源码自流水线编写以来未曾改变,这是证明该结构有效的可重复证据,但尚未形成可靠的往绩;并且构建并非字节可重现(byte-reproducible)——未更改的 `app/signed-app` 源代码树每次重建都会产生不同的摘要(早期单任务运行时的 `sha256:b4cb133e…`,之后在随后的五次运行中每次都有不同的摘要),因此此处的摘要仅标识某一次特定的构建,而不是单独标识源码。工作负载刻意保持固定在 `sha256:7fd13d22…`,而不是追逐最新的构建:这是提交的 `cosign verify` 记录所涵盖的摘要,您固定的是您验证过的内容。下一步:使用 ArgoCD 进行 GitOps CD —— 2026-07-25。
## 许可证
Apache-2.0 —— 参见 [`LICENSE`](LICENSE)。
build · Trivy · SBOM
NO id-token"] SIGN["sign job
id-token: write
cosign keyless, by digest"] BUILD --> SIGN end GHCR[("GHCR
image + signature")] REKOR[("Rekor
transparency log")] BUILD -- "push by digest" --> GHCR SIGN -- "signature + Fulcio cert" --> GHCR SIGN --> REKOR DEV["kubectl apply"] subgraph KIND["kind cluster seclab · Kubernetes v1.35.5 · Docker Desktop / WSL2"] direction TB subgraph CPN["seclab-control-plane"] direction TB API["kube-apiserver · authz Node,RBAC"] PSA{"1 · Pod Security Admission
enforce: restricted
BUILT-IN · runs first"} VPOL{"2 · Kyverno ValidatingPolicy ×4
workload hardening
+ registry allow-list"} IVPOL{"3 · Kyverno ImageValidatingPolicy
cosign keyless identity"} DENY["REJECTED
nothing is created"] API --> PSA PSA -- "violates restricted —
SHORT-CIRCUIT:
webhooks never run" --> DENY PSA -- pass --> VPOL VPOL -- "policy failed" --> DENY VPOL -- pass --> IVPOL IVPOL -- "unsigned" --> DENY end subgraph WKN["seclab-worker + seclab-worker2 · namespace demo"] direction LR NP["NetworkPolicy
default-deny
ingress AND egress"] CLIENT["pod labelled
access=nginx"] NGINX["nginx ×2
nginx-sa · no token"] UNAUTH["pod without
access=nginx"] APP["signed-app
signed-app-sa · no token"] CLIENT -- "TCP 8080 allowed" --> NGINX UNAUTH -. "dropped · times out" .-> NGINX end IVPOL == admitted ==> WKN end DEV --> API GHCR -. "manifest + signature
pulled at admission" .-> IVPOL REKOR -. "tlog entry verified" .-> IVPOL ``` 各关卡的详细说明、sealed-secrets 路径(从 Git 交叉进入集群的唯一其他工件),以及完整的攻击 → 控制对照表都在 [`docs/architecture.md`](docs/architecture.md) 中。 ## 安全层 ### 已构建并验证 目前正在实时集群上执行,每一项都包含已提交的攻击记录。之所以列出 Enforced scope(执行范围)列,是因为这些控制被设计为 namespace(命名空间)范围,而不是 cluster(集群)范围——参见[威胁模型的 §6.7](docs/threat-model.md#6-residual-risk-and-what-is-deliberately-out-of-scope)。 | 层级 | 工具 | 其拦截的攻击 | 执行范围 | | --- | --- | --- | --- | | RBAC 加固 | Kubernetes RBAC | 通过过于宽泛的 ServiceAccount 权限进行的权限提升 | `demo` 中的 `developer` Role(无 `secrets`,无写操作 verb,无通配符);`nginx-sa` / `signed-app-sa` 完全不挂载 token | | 准入控制 | Pod Security Admission + 4个处于 Deny 模式的 Kyverno `ValidatingPolicy` | 部署特权/不合规的工作负载(privileged、`hostPath /`、能力加回、`:latest`、未审查的 registry) | 在 `demo` 上 PSA `enforce: restricted`;四个 Kyverno 策略选择 `demo` + `demo-kyverno-only`,覆盖原生 Pod、`pods/ephemeralcontainers`、`apps/v1` 控制器和 `batch/v1` jobs | | 网络策略 | Kubernetes NetworkPolicy | Pod 之间的横向移动 | Namespace `demo` —— 默认拒绝 ingress **和** egress,一个 CoreDNS 例外,以及一对基于标签的、在 TCP 8080 上的允许规则 | | 密钥管理 | sealed-secrets | 提交到 Git 的明文密钥 | 提交的密文,其严格的作用范围绑定到 `demo/demo-app-secret`;私钥永远不会离开 `kube-system` | | 供应链 | GitHub Actions + Trivy + SBOM + cosign,由 Kyverno `ImageValidatingPolicy` 强制执行 | 运行此项目从未构建过的镜像 —— 实验室自身 registry 路径下未经签名或来源不受信任的镜像 | `demo` + `demo-kyverno-only` 中匹配 `ghcr.io/joesevv/k8s-security-lab/*` 的镜像。Docker Hub 的 `nginxinc/*` 和 `curlimages/*` 故意不包含在 glob 范围内:它们没有可证明的签名,而是受到 registry 允许列表和摘要固定的限制 | 供应链这一行有两个值得在首页而不是在脚注中说明的局限性。CVE 扫描关卡是**仅报告模式**:Trivy 以 `exit-code: '0'` 运行,因此 `CRITICAL`/`HIGH` 发现将作为 SARIF 上传,但绝不会导致构建失败。而且签名证明的是**来源,而不是没有漏洞**——带有严重 CVE 的签名镜像在设计上会被允许通过。这两点都是刻意为之的,并且都在[威胁模型的 §6](docs/threat-model.md#6-residual-risk-and-what-is-deliberately-out-of-scope) 中与本实验室未防御的其他内容一起进行了说明。 ### 路线图 尚未构建。没有策略,没有证据,没有强制执行。 | 层级 | 工具 | 其将拦截的攻击 | | --- | --- | --- | | GitOps CD | ArgoCD | 未跟踪/手动的集群配置漂移 | | 运行时检测 | Falco | 恶意运行时行为(容器中的 shell 等) | | CIS 合规性 | kube-bench | 不安全的集群配置 | | 自定义控制器(延伸目标) | Go | 强制执行特定的安全不变量 | ## 攻击 → 控制映射 每个演示的攻击对应一个条目,将攻击与拦截它的控制配对,并链接到捕获的证据。 | 控制措施 | 攻击 | 证据 | | --- | --- | --- | | 阶段 2a RBAC —— 最小权限的 `developer` Role(仅对 pods/services/deployments 执行 get/list/watch)+ 无 token 的 `nginx-sa` | 携带 token 的 Pod (`developer-sa`) 使用其挂载的 ServiceAccount token 通过 API 读取 Secrets | HTTP 403 Forbidden —— [`docs/evidence/phase-2a-rbac/`](docs/evidence/phase-2a-rbac/) (attack-output.txt, can-i-matrix.txt);运行手册 [`runbooks/phase-2a-rbac.md`](runbooks/phase-2a-rbac.md)。纵深防御:读取 secret 的路径现在被 RBAC (403) 和阶段 2c NetworkPolicy egress 默认拒绝**双重**拦截——今天重放会在网络层超时,先于 RBAC 的 403,因此有关重放警告请参见 [`REPLAY-NOTE.md`](docs/evidence/phase-2a-rbac/REPLAY-NOTE.md) | | 阶段 2b 准入 —— 4个处于 Enforce 模式的 Kyverno CEL `ValidatingPolicy` 规则(禁止 privileged,禁止 `:latest`/裸标签,必须丢弃所有 capabilities 且不能添加任何 capability,registry 允许列表),范围限定在 `demo` + `demo-kyverno-only`,加上 `demo` 上的 PSA `enforce: restricted` | 九次攻击申请:四个单规则 Pod,capability 加回,一个特权 Deployment 和一个特权 CronJob(Pod 控制器路径),通过 `kubectl debug` 进行的 ephemeral-container 注入,以及一个 `hostPath /` + `runAsUser: 0` 的 Pod | 全部在准入阶段被拒绝,每次拒绝都指明了策略名称——或者对于 `hostPath`/`runAsUser`,指明了 PodSecurity,因为**这里没有任何 Kyverno 策略覆盖 `hostPath`**,PSA 填补了这一空白;合规的工作负载仍然允许通过—— [`docs/evidence/phase-2b-admission/`](docs/evidence/phase-2b-admission/) (attack-output.txt, attack-*.yaml);运行手册 [`runbooks/phase-2b-admission.md`](runbooks/phase-2b-admission.md) | | 阶段 2c 网络 —— `demo` 中默认拒绝 ingress+egress 的 NetworkPolicies,带有 CoreDNS 例外 (53/UDP+TCP) 和一对基于标签的允许规则(允许带有 `access=nginx` 标签的 Pod 在 TCP 8080 上对 nginx 进行 ingress,与客户端 egress 匹配) | 未经授权的 Pod(没有 `access=nginx` 标签,其他方面与授权客户端完全相同)向 `nginx.demo.svc.cluster.local` 发起 curl 请求 | 连接被 NetworkPolicy 拦截 —— 5s 超时后显示 HTTP:000,而带有标签的对照组 Pod 在几毫秒内获得 HTTP:200,并且两个 Pod 仍然可以解析 DNS —— [`docs/evidence/phase-2c-network/`](docs/evidence/phase-2c-network/) (attack-output.txt, attacker-unauthorized.yaml, client-authorized.yaml);运行手册 [`runbooks/phase-2c-network.md`](runbooks/phase-2c-network.md) | | 阶段 3 密钥 —— sealed-secrets 控制器 (v0.38.4, kube-system),提交到 Git 的严格范围的 SealedSecret | 仓库读者获取已提交的 SealedSecret 并尝试恢复明文(对密文进行 base64 解码 / 重新封装到攻击者的 namespace 中) | 密文解码为乱码,没有集群内的私钥则无法进行离线解封,范围窃取被拒绝,集群内往返由 SHA-256 匹配确认,并且 `git grep` 未发现明文 —— [`docs/evidence/phase-3-secrets/`](docs/evidence/phase-3-secrets/) (attack-output.txt, scope-theft-attacker-ns.yaml);运行手册 [`runbooks/phase-3-secrets.md`](runbooks/phase-3-secrets.md) | | 阶段 4 供应链 —— Kyverno `ImageValidatingPolicy` `require-keyless-signed-ghcr`(Deny + `failurePolicy: Fail`),要求提供 cosign 无密钥签名,其 Fulcio 身份必须等于一个确切的工作流 subject,并对照 Rekor 日志进行检查;匹配 `demo` + `demo-kyverno-only` 中的 `ghcr.io/joesevv/k8s-security-lab/*` | 一个**确实存在于 registry 中且确实没有签名**的镜像 —— cosign 自身的签名工件,发布在 OCI referrers-fallback 标签 `sha256-
标签:DevSecOps, PyVis, 上游代理, 子域名突变, 攻击与复现