LabHub
学习 学习路径 课程

CRD 与 Operator

安装 Operator 并验证权限边界

在 LabHub 中继续学习

目标

按照正确顺序安装一整套 Operator 清单(命名空间、RBAC、控制器 Deployment、领导者选举 Lease),并亲自查询证明其权限确实位于预期边界内。

为什么重要

Operator 可能是集群中权限最强的工作负载。由于它会创建和修改多个命名空间中的资源,一旦其服务账号被窃取,攻击者就会完整获得这些权限。因此,两点至关重要。第一,安装顺序就是依赖关系。控制器一启动就会 watch 自身类型,所以 CRD 必须先存在;它还会先查询列表,所以权限也必须先存在。顺序错误会表现为崩溃循环或 403,而要意识到原因不是代码、而是部署顺序,往往需要很长时间。第二,权限控制就是减少动词。verbs: ["*"] 虽然方便,但如果将子资源清理由所有者引用处理,很多情况下连 delete 都不需要。此外,在 RBAC 中,webserviceswebservices/status 是完全不同的资源——不了解这一点,就会困在“明明给了权限,却无法写入 status”的症状中。最后,权限不是文档,而是验证对象。使用 kubectl auth can-i --as= 同时确认允许和不应允许的操作,这张表就会成为权限边界的证据。

步骤

开始前准备:实验 Pod 每次实验都会重新创建,因此前一实验的集群状态不会保留。第 2、6 步需要 CRD webservices.apps.labhub.io,第 7、8 步的权限查询以 crd-lab 命名空间为目标。因此,如果 kubectl get crd webservices.apps.labhub.io 结果为空,请重新应用 CRD,并执行 kubectl create ns crd-lab。CRD 必须包含 v1alpha1v1 两个版本,存储版本必须是 v1(第 6 步会检查是否保留)。

  1. 创建命名空间 labhub-operator,并添加标签 app.kubernetes.io/part-of=labhub-operatorpod-security.kubernetes.io/enforce=restricted
  2. /root/op/install/out/install-order.txt 中按每个阶段一行写出安装顺序。第 1 行必须提到 CustomResourceDefinition,第 2 行必须提到 ServiceAccountClusterRole/ClusterRoleBinding,第 3 行必须提到 Deployment;不要在前面的行中提前使用 Deployment컨트롤러 一词(顺序判断以首次出现的行号为准)。集群中还必须存在 CRD webservices.apps.labhub.io
  3. labhub-operator 中创建 ServiceAccount webservice-controller,并按以下规则创建 ClusterRole webservice-controller
    • apiGroups: ["apps.labhub.io"], resources: ["webservices"], verbs: ["get","list","watch","update","patch"]
    • apiGroups: ["apps.labhub.io"], resources: ["webservices/status","webservices/finalizers"], verbs: ["get","update","patch"]
    • apiGroups: [""], resources: ["events"], verbs: ["create","patch"]
    • apiGroups: ["coordination.k8s.io"], resources: ["leases"], verbs: ["get","list","watch","create","update","patch"] 动词和资源中都不能使用 *。然后创建 ClusterRoleBinding webservice-controller,将 subject 指定为 kind: ServiceAccountname: webservice-controllernamespace: labhub-operator
  4. labhub-operator 中创建 Deployment webservice-controllerspec.replicas 为 1 或 2,spec.template.spec.serviceAccountNamewebservice-controller,容器镜像为 ghcr.io/labhub/webservice-controller:v0.1.0,在 args 中加入 --leader-elect=true,分别为容器配置 livenessProbereadinessProbe,并设置 resources.limits.memory。Pod 的 securityContext.runAsNonRoot 必须为 true
  5. labhub-operator 中创建 Lease webservice-controller.labhub.iospec.holderIdentity 是领导者的身份字符串,spec.leaseDurationSeconds 不小于 5,spec.renewTime 是精确到微秒的 RFC3339 时间(date -u +%Y-%m-%dT%H:%M:%S.%6NZ)。并在 /root/op/install/out/lease-note.txt 中说明没有领导者选举会遇到什么问题(两个实例同时协调同一个对象)。
  6. 将 Deployment 的容器镜像升级为 ghcr.io/labhub/webservice-controller:v0.2.0,并等待滚动发布完成。CRD 必须仍保留至少两个版本,且 status.storedVersions 中必须包含 v1。在 /root/op/install/out/upgrade-check.txt 中写明升级前后检查了什么,并且必须包含对 storedVersions 的检查。
  7. /opt/lab/fixtures/operator/broken-operator.yaml 复制为 /root/op/install/fixed-operator.yaml,修复三处错误并应用。然后在 /root/op/install/out/diagnosis.txt 中说明哪里错了、为什么错,要求每行一项,至少三行。必须包括绑定主体名称问题和缺少 status 子资源权限。修复后,kubectl auth can-i update webservices/status -n crd-lab --as=system:serviceaccount:labhub-operator:webservice-controllerkubectl auth can-i list webservices --all-namespaces --as=... 都必须返回 yes
  8. 创建 /root/op/install/out/permissions.json。顶层键为 checks,每个元素包含 verbresourceexpectedyes/no),并可按需包含 namespace。不写 namespace 时,按所有命名空间范围进行检查。至少要有 6 项,其中 expectedno 的项目至少 2 项,并且其中一项的 resource 必须是 secrets。例如,以下八项就足够:list webservices(yes)、watch webservices(yes)、update webservices/status in crd-lab(yes)、create events in crd-lab(yes)、update webservices/finalizers in crd-lab(yes)、get secrets in crd-lab(no)、delete webservices in crd-lab(no)、create clusterrolebindings(no)。所有项目的 expected 都必须与实际响应一致。

参考

创建 Operator 专用命名空间

创建命名空间 labhub-operator,并添加标签 app.kubernetes.io/part-of=labhub-operatorpod-security.kubernetes.io/enforce=restricted

分别需要一个用于一次性查找所有组件的归属标签,以及一个禁止 Pod 使用特权的策略标签。控制器是完全不需要特权的工作负载。

整理安装顺序

/root/op/install/out/install-order.txt 中按每个阶段一行写出安装顺序。第 1 行必须提到 CustomResourceDefinition,第 2 行必须提到 ServiceAccountClusterRole/ClusterRoleBinding,第 3 行必须提到 Deployment;不要在前面的行中提前使用 Deployment컨트롤러 一词(顺序判断以首次出现的行号为准)。集群中还必须存在 CRD webservices.apps.labhub.io

控制器一启动就会 watch 自身类型并查询列表。这两项必须事先准备好。顺序文档每个阶段写一行,不要在前面的行中提前提及后续阶段的名称。

创建不含通配符的最小权限

labhub-operator 中创建 ServiceAccount webservice-controller,并按以下规则创建 ClusterRole webservice-controller

主资源和子资源在 RBAC 中是不同的资源。必须分别写出 status 和 finalizers,而 events 属于核心组。通配符既不能用于动词,也不能用于资源。

编写控制器 Deployment

labhub-operator 中创建 Deployment webservice-controllerspec.replicas 为 1 或 2,spec.template.spec.serviceAccountNamewebservice-controller,容器镜像为 ghcr.io/labhub/webservice-controller:v0.1.0,在 args 中加入 --leader-elect=true,分别为容器配置 livenessProbereadinessProbe,并设置 resources.limits.memory。Pod 的 securityContext.runAsNonRoot 必须为 true

如果使用默认服务账号运行,精心创建的权限就不会生效。需要一个允许多个副本的参数、分别检查是否存活和是否准备接收流量的两个探针,以及应对缓存增长的内存上限。

创建领导者选举租约

labhub-operator 中创建 Lease webservice-controller.labhub.iospec.holderIdentity 是领导者的身份字符串,spec.leaseDurationSeconds 不小于 5,spec.renewTime 是精确到微秒的 RFC3339 时间(date -u +%Y-%m-%dT%H:%M:%S.%6NZ)。并在 /root/op/install/out/lease-note.txt 中说明没有领导者选举会遇到什么问题(两个实例同时协调同一个对象)。

租约包含当前领导者是谁、有效期多长以及最后续约时间。续约时间格式精确到微秒,位数不符就会被拒绝。

升级镜像并确认保留 CRD 版本

将 Deployment 的容器镜像升级为 ghcr.io/labhub/webservice-controller:v0.2.0,并等待滚动发布完成。CRD 必须仍保留至少两个版本,且 status.storedVersions 中必须包含 v1。在 /root/op/install/out/upgrade-check.txt 中写明升级前后检查了什么,并且必须包含对 storedVersions 的检查。

控制器镜像可以通过滚动更新升级,但不能随意删除 CRD 的旧版本。请先检查曾用于存储的版本列表。还必须确认滚动发布已经结束。

修复损坏的 Operator 清单

/opt/lab/fixtures/operator/broken-operator.yaml 复制为 /root/op/install/fixed-operator.yaml,修复三处错误并应用。然后在 /root/op/install/out/diagnosis.txt 中说明哪里错了、为什么错,要求每行一项,至少三行。必须包括绑定主体名称问题和缺少 status 子资源权限。修复后,kubectl auth can-i update webservices/status -n crd-lab --as=system:serviceaccount:labhub-operator:webservice-controllerkubectl auth can-i list webservices --all-namespaces --as=... 都必须返回 yes

指向不存在主体的绑定可以在不报错的情况下创建。此外,即使拥有主资源权限,子资源仍是独立的。请使用权限查询,而不是日志进行确认。

创建权限边界验证表

创建 /root/op/install/out/permissions.json。顶层键为 checks,每个元素包含 verbresourceexpectedyes/no),并可按需包含 namespace。不写 namespace 时,按所有命名空间范围进行检查。至少要有 6 项,其中 expectedno 的项目至少 2 项,并且其中一项的 resource 必须是 secrets。例如,以下八项就足够:list webservices(yes)、watch webservices(yes)、update webservices/status in crd-lab(yes)、create events in crd-lab(yes)、update webservices/finalizers in crd-lab(yes)、get secrets in crd-lab(no)、delete webservices in crd-lab(no)、create clusterrolebindings(no)。所有项目的 expected 都必须与实际响应一致。

只检查允许的操作无法证明边界。还要加入不应允许的操作,并与实际响应对照。未写命名空间的项目按所有命名空间范围进行检查。