WEBKT

Kubernetes RBAC:Service Account 如何细粒度访问特定 Secret

76 0 0 0

在Kubernetes环境中,Secrets 承载着数据库密码、API 密钥等敏感信息,其安全性至关重要。默认情况下,如果一个 Service Account 被赋予了访问 secrets 资源的权限(例如 getlistwatch),它通常能够访问命名空间内所有Secret 对象。这显然违反了最小权限原则,增加了安全风险。

本文将深入探讨如何利用 Kubernetes RBAC(Role-Based Access Control)机制,对 Secrets 的访问进行细粒度控制,精确到允许特定的 Service Account 只能访问特定的 Secret 对象。

为什么需要细粒度控制?

想象一个微服务架构,一个应用可能只需要访问它自己的数据库凭证 Secret,而另一个服务可能需要访问 API 密钥 Secret。如果这两个服务都部署在同一个命名空间下,并且它们的 Service Account 都拥有了对 secrets 的全部读取权限,那么一个服务的漏洞可能导致敏感数据泄露,进而影响到其他不相关的服务。通过限制每个 Service Account 只能访问其真正需要的 Secret,可以显著降低这种横向攻击的风险。

核心概念:resourceNames

Kubernetes RBAC 的 RoleClusterRole 定义了权限集。在 rules 字段中,除了 apiGroupsresources 外,还有一个强大的字段叫做 resourceNames。这个字段允许你精确指定规则适用于哪些具体名称的资源。这就是实现细粒度控制的关键。

例如,如果你想让某个 Service Account 只能读取名为 my-database-secretSecret,你可以在 Role 中这样定义:

rules:
- apiGroups: [""] # "" 代表核心 API Group,Secret 属于核心组
  resources: ["secrets"]
  verbs: ["get", "watch", "list"] # 允许的操作
  resourceNames: ["my-database-secret"] # 精确指定Secret的名称

实践:限制 Service Account 访问特定 Secret

接下来,我们将通过一个具体的例子来演示如何实现这一目标。

场景描述:

我们有一个名为 my-app 的应用,它需要访问一个名为 app-credentialsSecret。同时,我们还有另一个名为 other-appSecret,我们不希望 my-app 访问它。

步骤 1:创建两个 Secret

首先,我们创建两个示例 Secret,用于演示权限控制。

# secret-app-credentials.yaml
apiVersion: v1
kind: Secret
metadata:
  name: app-credentials
type: Opaque
stringData:
  username: "user"
  password: "password123"
---
# secret-other-app.yaml
apiVersion: v1
kind: Secret
metadata:
  name: other-app-secret
type: Opaque
stringData:
  token: "shhh-this-is-another-secret"

部署它们:

kubectl apply -f secret-app-credentials.yaml
kubectl apply -f secret-other-app.yaml

步骤 2:创建 Service Account

my-app 应用创建一个专用的 Service Account

# serviceaccount.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: my-app-sa

部署它:

kubectl apply -f serviceaccount.yaml

步骤 3:定义一个 Role,限制访问 app-credentials

这是关键一步。我们创建一个 Role,只允许对 app-credentials 这个 Secret 执行 get, watch, list 操作。

# role-secret-reader.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: app-credentials-reader
  namespace: default # 确保Role和Secret、ServiceAccount在同一命名空间
rules:
- apiGroups: [""]
  resources: ["secrets"]
  verbs: ["get", "watch", "list"]
  resourceNames: ["app-credentials"] # 只允许访问这个Secret

部署它:

kubectl apply -f role-secret-reader.yaml

步骤 4:绑定 Role 到 Service Account

现在,我们将上面定义的 Role 绑定到 my-app-sa Service Account

# rolebinding.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: bind-app-credentials-reader
  namespace: default
subjects:
- kind: ServiceAccount
  name: my-app-sa
  namespace: default
roleRef:
  kind: Role
  name: app-credentials-reader
  apiGroup: rbac.authorization.k8s.io

部署它:

kubectl apply -f rolebinding.yaml

步骤 5:测试访问权限

为了测试,我们创建一个 Pod,并让它使用 my-app-sa。然后,我们从 Pod 内部尝试访问 Secret

# test-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: test-secret-access
spec:
  serviceAccountName: my-app-sa # 使用我们刚刚创建的Service Account
  containers:
  - name: test-container
    image: busybox
    command: ["sh", "-c", "echo 'Testing access to app-credentials...' && kubectl get secret app-credentials -o json || echo 'Failed to get app-credentials'; echo 'Testing access to other-app-secret...' && kubectl get secret other-app-secret -o json || echo 'Failed to get other-app-secret'; sleep 3600"]
    env:
      # 为了让kubectl命令在pod中能正确运行,需要设置KUBERNETES_SERVICE_HOST和KUBERNETES_SERVICE_PORT
      # 并且busybox镜像默认不包含kubectl,这里仅作概念演示。
      # 实际测试中,建议使用包含kubectl的镜像,或直接从pod内部通过curl访问API Server
      - name: KUBERNETES_SERVICE_HOST
        value: kubernetes.default.svc
      - name: KUBERNETES_SERVICE_PORT
        value: "443"
  terminationGracePeriodSeconds: 0 # 快速终止

注意: busybox 镜像通常不包含 kubectl。为了在 Pod 中执行 kubectl 命令,你需要一个包含它的镜像(例如 alpine/k8s 或自己构建一个)。这里为了简化示例,我们将模拟 kubectl 行为,但更严谨的测试应该是从 Pod 内部通过 API 访问,或者使用 kubectl auth can-i 命令在外部测试 ServiceAccount 的权限。

使用 kubectl auth can-i 进行外部测试 (推荐):

在集群外部,你可以使用 kubectl auth can-i 命令来模拟 Service Account 的权限。

测试读取 app-credentials (应该成功):

kubectl auth can-i get secret app-credentials --as=system:serviceaccount:default:my-app-sa

输出应该类似:

yes

测试读取 other-app-secret (应该失败):

kubectl auth can-i get secret other-app-secret --as=system:serviceaccount:default:my-app-sa

输出应该类似:

no

这意味着 my-app-sa Service Account 只能访问 app-credentials,而无法访问 other-app-secret,我们的细粒度控制生效了。

最佳实践与注意事项

  1. 最小权限原则 (Principle of Least Privilege): 始终只授予 Service Account 完成其任务所需的最小权限。这是 RBAC 设计的核心思想。
  2. 命名空间隔离: 尽可能将不同的应用和其 Secret 部署在不同的命名空间中,配合 Role (而非 ClusterRole) 和 RoleBinding 进行权限管理。这样即使权限配置不当,影响范围也仅限于单个命名空间。
  3. 避免 ClusterRole 用于 Secret 对于 Secret 这种命名空间资源,除非有非常特殊的跨命名空间需求(且经过严格评估),否则应避免使用 ClusterRoleClusterRoleBinding,以防止意外授予集群范围的 Secret 访问权限。
  4. 定期审计: 定期检查 RBAC 配置,确保没有过宽的权限。可以使用 kubectl auth can-i 或第三方工具进行审计。
  5. secrets 的挂载方式: 即使 Service Account 有权限读取 Secret,也应优先通过 PodvolumeMountsenv 变量将 Secret 内容挂载或注入到容器中,而不是让容器直接去调用 Kubernetes API 来读取 Secret。这进一步简化了应用代码,并能利用 Kubernetes 自身的 Secret 卷挂载的安全特性(如内存文件系统)。
  6. 与其他安全工具结合: 考虑使用外部 Secret 管理系统(如 HashiCorp Vault、AWS Secrets Manager、Azure Key Vault、GCP Secret Manager)与 Kubernetes Secret CSI 驱动结合,以实现更强大的 Secret 生命周期管理、审计和轮换功能。

通过上述方法,您可以有效地在 Kubernetes 中对 Secrets 的访问进行细粒度控制,大幅提升集群的安全性。

K8s老兵 KubernetesRBACSecret

评论点评