作者:Tarun Pothulapati
安全是 Linkerd 最关心的问题。它在增强系统的整体安全性方面发挥着关键作用,而这只有在 Linkerd 本身是安全的情况下才可能实现。我们最近在 Linkerd 上增加了对 Kubernetes 的新绑定服务账户令牌的支持。这是迈向安全的一大步。但是为什么呢?为了理解这一点,首先我们需要了解 Linkerd 是如何使用服务帐户的。
Linkerd 提供双向 TLS(mTLS),以确保工作负载之间的通信安全。任何类型的通信安全的核心都是身份的概念——正如Kubernetes 工程师 mTLS 指南[1]中所讨论的,没有身份就没有真实性,没有真实性就没有安全通信。Linkerd 的所有 mTLS 魔术是可能的,是因为控制平面(特别是身份组件)发出一个证书,代理使用该证书向其他服务进行身份验证。
但是这个 TLS 证书中包含的身份是什么?Linkerd 的身份组件如何确保它向集群中的代理颁发证书,而不是一些入侵者试图与集群中的其他服务通信?控制平面如何确保代理本身的身份?我们将在这篇博文中回答这些问题。就让我们一探究竟吧!
Kubernetes 服务帐户
这不仅仅是 Linkerd 的问题。许多组件或 K8s 控制器在为它们提供服务之前都希望验证它们的客户机的身份(如果它们在集群中运行的话)。因此,Kubernetes 提供了默认情况下连接到 pod 的服务帐户,内部的应用程序可以使用这些帐户向其他组件证明它是 Kubernetes 集群的一部分。它们以卷的形式被附加到 pod 中,并被加载到/var/run/secrets/kubernetes.io/serviceaccount 的文件路径。默认情况下,Kubernetes 会附加 pod 命名空间的默认服务帐户。
代码语言:javascript复制spec:
containers:
...
volumeMounts:
- mountPath: /var/run/secrets/kubernetes.io/serviceaccount
name: kube-api-access-tsbwl
readOnly: true
...
volumes:
- name: kube-api-access-tsbwl
projected:
defaultMode: 420
sources:
- serviceAccountToken:
expirationSeconds: 3607
path: token
- configMap:
items:
- key: ca.crt
path: ca.crt
name: kube-root-ca.crt
- downwardAPI:
items:
- fieldRef:
apiVersion: v1
fieldPath: metadata.namespace
path: namespace
Kubernetes RBAC 还经常使用服务帐户来授予 pod 对 Kubernetes API 服务的访问权。这是通过使用 ClusterRoleBinding 将一个 ClusterRole(带有必要的权限)附加到一个服务帐户(通过创建一个 ServiceAccount 对象)来实现的。然后,我们可以在工作负载的 serviceAccountName 中指定相同的服务帐户。这将覆盖每个名称空间提供的默认服务帐户。默认的服务帐户令牌没有查看、列出或修改集群中的任何资源的权限。
当 Kubernetes 附加默认的服务帐户令牌时,它还附加一个 kube-root-ca.crt 的 configmap(如上面的 YAML 所示),包含 API 服务器受信任的根证书。当应用程序与 API 服务器通信时,这用于与 API 服务器的 TLS 身份验证[2]。
Linkerd 不需要任何这些额外的文件,除了令牌,因为它从不与 Kubernetes API 交互(稍后我们将看到绑定的服务帐户令牌如何修复这个问题)。
那么 Linkerd 如何验证它的代理呢?
为了让代理获得它的证书,它需要用身份组件验证自己。这是通过将服务帐户令牌嵌入到每次需要新证书时(默认 24 小时)调用的 Certify 请求中来实现的。身份组件通过与TokenReview[3] Kubernetes API 对话来验证令牌[4],并在此之后返回一个带有证书的 CertifyResponse。身份组件不仅验证令牌是否有效,而且还验证令牌是否与请求证书的同一个 pod 相关联。这可以通过查看 TokenReview 响应中的 Status.User.Username 来验证。Kubernetes API 将用户名设置为该令牌所附加的 pod 名称。
只有 Linkerd 中的身份组件有必要的 API 访问来验证令牌。一旦验证了令牌,身份组件就会发出一个证书,让代理使用该证书与其他服务进行通信。
Linkerd 如何提供工作负载身份?
Linkerd 在这里采取了一个(在我看来)漂亮的简化步骤:服务帐户不仅仅用来验证代理是否像它们所说的那样,它们还被用作工作负载身份本身的基础。这为我们提供了一个工作负载身份,该身份已经绑定到授予该 pod 的功能,这意味着我们可以在不需要任何额外配置的情况下提供 mTLS!这是 Linkerd 为所有网格 pod 提供默认 mTLS 的能力背后的秘密。
每当 Linkerd 在两个端点之间建立一个相互的 TLS 连接时,交换的身份就是服务账户的身份。这个身份甚至被连接到 Linkerd 的指标中:每当一个网格化的请求被接收或发送时,相关的指标也包括与该对等体相关的服务帐户。
下面是一个来自emojivoto[5]的例子:
代码语言:javascript复制request_total{..., client_id="web.emojivoto.serviceaccount.identity.linkerd.cluster.local", authority="emoji-svc.emojivoto.svc.cluster.local:8080", namespace="emojivoto", pod="emoji-696d9d8f95-5sj4j"} 14532
正如你在上面的度量中看到的 client_id 标签是附加到从那里接收请求的客户端 pod 的服务帐户。
授权策略
Linkerd 的新授权策略特性允许用户指定一组只能访问一组资源的客户端。这是通过使用相同的身份来实现的,用户可以指定应该允许与他们的 ServerAuthorization 资源中的一组工作负载(按 Server 资源分组)进行通信的客户机的服务帐户。
代码语言:javascript复制apiVersion: policy.linkerd.io/v1beta1
kind: ServerAuthorization
metadata:
namespace: emojivoto
name: internal-grpc
labels:
app.kubernetes.io/part-of: emojivoto
app.kubernetes.io/version: v11
spec:
server:
selector:
matchLabels:
emojivoto/api: internal-grpc
client:
meshTLS:
serviceAccounts:
- name: web
在上面的例子中,我们允许使用 web 服务帐户的工作负载与 internal-grpc 服务器通信。
绑定服务账户令牌
虽然所有这些都很棒,但还有一个问题。这个令牌是针对与 Kubernetes API 通信的应用程序的,而不是专门针对 Linkerd 的。Linkerd 也不需要那些作为默认卷挂载的一部分的额外证书。这不是安全最佳实践。Linkerd 使用默认的服务账户令牌实际上获得了比实际需要更多的权限。这是一个潜在的弱点。这也意味着在Linkerd 之外有一些控件[6]来管理用户可能想要使用的服务令牌,这导致了Linkerd 的问题[7],因为 Linkerd 可能希望它存在以进行验证。用户还可以显式地禁用令牌自动挂载到他们的 pod 上,从而导致 Linkerd 出现问题。从 Linkerd 2.11 开始,如果令牌自动挂载被禁用,我们将跳过 pod 注入。
为了解决这些挑战,从edge-21.11.1[8]开始,我们添加了对自动挂载绑定服务账户令牌的支持。Linkerd 将使用绑定服务账户令牌(Bound Service Account Tokens[9])特性来请求自己的令牌集,而不是使用默认挂载的令牌。绑定服务帐户令牌(在 Kubernetes v1.20 中 GA 了)特性允许组件根据需求从 API 服务器请求特定服务帐户的令牌,这些令牌被绑定到特定的目的(而不是默认的,用于访问 API 服务器)。
使用这个,Linkerd 注入器将请求一个专门为 Linkerd 绑定的令牌,以及一个 24 小时的过期(就像身份过期一样)。这个令牌是为 Kubernetes 挂载到 pod 上的相同服务帐户生成的,因此不会影响 Linkerd 现有的关于身份和策略的任何功能。
代码语言:javascript复制spec:
containers:
...
volumeMounts:
- mountPath: /var/run/linkerd/identity/end-entity
name: linkerd-identity-end-entity
...
volumes:
- name: linkerd-identity-token
projected:
defaultMode: 420
sources:
- serviceAccountToken:
audience: identity.l5d.io
expirationSeconds: 86400
path: linkerd-identity-token
这个令牌是专门为 Linkerd 生成的,用于代理验证自己的身份,不能与 Kubernetes API 对话,这给了我们一个很好的关注点分离。
结论
在这篇文章中,我们描述了迁移到 Kubernetes 新的绑定服务账户令牌的动机,它将 Linkerd 访问 Kubernetes API 的范围减少到支持其安全特性所必需的最低限度。我们还揭示了控制平面在颁发证书之前如何验证代理的一些内部工作原理,并了解了 Linkerd 如何使用 Kubernetes 的服务帐户作为原语来构建授权策略等特性。
Linkerd 的目标是为 Kubernetes 用户提供世界级的安全,而不会给他们带来负担。通过依赖服务帐户,我们可以在你安装 Linkerd 的瞬间,为所有网格 pod 提供默认的相互 TLS 零配置。通过绑定服务帐户,实现比以前更加安全。
Linkerd 适用于所有人
Linkerd 是云原生计算基金会的一个毕业项目。Linkerd 致力于开放治理。如果你有功能要求、问题或评论,我们希望你加入我们快速增长的社区!Linkerd 托管在 GitHub 上,我们在 Slack、Twitter 和邮件列表上有一个蓬勃发展的社区。快来加入乐趣吧!
参考资料
[1]Kubernetes 工程师 mTLS 指南: https://buoyant.io/mtls-guide/
[2]与 API 服务器的 TLS 身份验证: https://github.com/kubernetes/client-go/blob/master/rest/config.go#L514
[3]TokenReview: https://github.com/linkerd/linkerd2/blob/main/controller/identity/validator.go#L51
[4]验证令牌: https://github.com/linkerd/linkerd2/blob/main/controller/identity/validator.go#L51
[5]emojivoto: https://linkerd.io/2/getting-started/
[6]Linkerd 之外有一些控件: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/#use-the-default-service-account-to-access-the-api-server
[7]Linkerd 的问题: https://github.com/linkerd/linkerd2/issues/3183
[8]edge-21.11.1: https://github.com/linkerd/linkerd2/releases/tag/edge-21.11.1
[9]Bound Service Account Tokens: https://github.com/kubernetes/enhancements/tree/master/keps/sig-auth/1205-bound-service-account-tokens