Kubernetes 身份管理:身份验证

您已经部署了 Kubernetes,但是现在您将如何安全地将其交付给您的开发人员和管理员?

Kubernetes 已经风靡全球。在短短几年内,Kubernetes(又名 k8s)已从一个有趣的项目发展成为技术和创新的驱动力。说明这一点的最简单方法之一是 KubeCon 北美在西雅图举办两次的与会人数差异。两年前,它在一个酒店里举行,只有不到 20 个供应商展位。今年,它在西雅图会议中心举行,有 8,000 名与会者和 100 多个供应商!

与任何其他复杂系统一样,k8s 也有自己的安全模型,需要与用户和其他系统交互。在本文中,我将介绍各种身份验证选项,并提供关于您应该如何管理对集群的访问的示例和实施建议。

身份对 Kubernetes 意味着什么?

首先要问的问题是 k8s 中的“身份”是什么。K8s 与大多数其他系统和应用程序非常不同。它是一组 API。没有“Web 界面”(我将在本文后面讨论仪表板)。没有“登录”点。没有“会话”或“超时”。每个 API 请求都是唯一的和不同的,并且它必须包含 k8s 验证请求身份和授权请求所需的一切内容。

也就是说,关于 k8s 中用户需要记住的主要事情是他们不存在于任何持久状态中。您不会将 k8s 连接到 LDAP 目录或 Active Directory。每个请求都必须通过多种可能的方法之一向 k8s 断言身份。我将“断言”大写是因为它稍后会变得重要。关键是要记住 k8s 不验证用户身份;它验证断言。

服务帐户

服务帐户是规则稍微弯曲的地方。的确,k8s 不存储有关用户的信息。它确实存储服务帐户,这些帐户并非旨在代表人员。它们旨在代表任何非人员的事物。在 k8s 中与其他事物交互的所有事物都作为服务帐户运行。例如,如果您要提交一个非常基本的 pod


apiVersion: v1
kind: Pod
metadata:
  name: myapp-pod
  labels:
    app: myapp
spec:
  containers:
  - name: myapp-container
    image: busybox
    command: ['sh', '-c', 'echo Hello Kubernetes!
     ↪&& sleep 3600']

然后通过运行 kubectl get pod myapp-pod -o yaml 在部署后在 k8s 中查看它


apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: 2018-12-25T19:17:53Z
  labels:
    app: myapp
  name: myapp-pod
  namespace: default
  resourceVersion: "12499217"
  selfLink: /api/v1/namespaces/default/pods/myapp-pod
  uid: c6dd5181-0879-11e9-a289-525400616039
spec:
  containers:
  - command:
    - sh
    - -c
    - echo Hello Kubernetes! && sleep 3600
    image: busybox
    imagePullPolicy: Always
    name: myapp-container
.
.
.
    volumeMounts:
    - mountPath: /var/run/secrets/kubernetes.io/serviceaccount
      name: default-token-bjzd4
      readOnly: true
.
.
.
  serviceAccount: default
  serviceAccountName: default
  .
  .
  .

您会注意到有一个 serviceAccountserviceAccountName 属性,两者都是 default。此服务帐户由准入控制器链为您注入。您可以在 pod 上设置自己的服务帐户,但这将在以后的关于 k8s 中授权的文章中介绍。现在,我想介绍一下服务帐户是什么,以将其与用户帐户区分开来。

使用服务帐户来代表人员是很诱人的。它们创建简单且易于使用。但是,它们有多个缺点

  1. 服务帐户的令牌是一个人类无法记住的长字符串,因此很可能会被写下来,如果操作不当,可能会被利用。
  2. 授权服务帐户的唯一方法是通过直接的 RBAC 绑定。(我计划在以后的文章中详细介绍这一点,但想象一下有 2,000 名开发人员需要在数十个命名空间中跟踪,所有命名空间都有自己的策略。审计将是一场噩梦。)
  3. 服务帐户没有过期时间,因此如果泄露了一个服务帐户而无人知晓,则可能会一直被滥用,直到被发现。

如果您的应用程序在 pod 中运行并且需要与 API 服务器通信,则可以通过挂载到您的 pod 的 secret 检索 pod 的服务帐户。如果您查看上面的 yaml,您会看到已将卷挂载添加到 /var/run/secrets/kubernetes.io/serviceaccount,其中有一个令牌文件包含 pod 的服务帐户令牌。不要将服务帐户令牌作为 secret 或 pod 配置嵌入到集群中运行,因为这会使轮换令牌的使用更加困难,并且通常更难管理。

用户帐户

我之前提到过,k8s 不连接到任何类型的用户存储(至少不直接连接)。这意味着在每个请求中,您都必须提供足够的信息供 k8s 验证调用者。K8s 不关心您如何建立身份,它只关心如何证明身份有效。存在多种机制来执行此操作;我在这里介绍最流行的机制。

Kubernetes 如何知道你是谁

OpenID Connect

您应该使用此选项(除了托管发行版的云提供商解决方案之外)来验证用户身份。

  1. OpenID Connect 令牌的有效期可能很短,因此如果被拦截和泄露,到攻击者知道他们拥有什么时,令牌就没用了。
  2. 使用 OpenID Connect,k8s 永远 不会拥有用户的凭据,因此不可能泄露它没有的东西。
  3. OpenID Connect 提供的用户身份不仅可以提供用户名信息,还可以提供组信息。这使得通过 LDAP 目录或外部数据库管理访问权限变得更加容易,而无需为单个用户创建 RBAC 绑定。
  4. 通过在 k8s 和身份层之间添加“代理”,可以更轻松地添加多种类型的身份验证,例如多因素身份验证。
  5. 大量的开源 OpenID Connect 实现将与 k8s 一起工作。
OpenID Connect 入门

在深入研究如何使用 OpenID Connect 之前,让我解释一下该协议。OpenID Connect 有两个核心概念需要理解

  1. OpenID Connect 是构建在 OAuth2 之上的断言生成协议。
  2. OAuth2 是用于传输持有者令牌的授权协议。

在这两点中有一个词似乎缺失了:身份验证!那是因为 OpenID Connect 不是 身份验证协议。它不关心您如何进行身份验证。用户是通过用户名和密码、智能卡登录还是仅仅看起来非常值得信赖,这都无关紧要。OpenID Connect 是一种用于生成、检索和刷新关于用户的断言的协议。关于断言的外观也有一些标准,但用户如何进行身份验证最终取决于 OpenID Connect 的实现。

关于 OAuth2 的第二点很重要,因为这两个协议经常被彼此混淆或被误传。OAuth2 是用于传输令牌的协议。它没有定义令牌是什么或应该如何使用。它只是定义了如何在持有者和依赖方之间传递令牌。

Kubernetes 如何与 OpenID Connect 一起工作?

图 1 显示了来自 k8s 身份验证页面 的图形。

""

图 1. k8s OpenID Connect 流程

我不会重复该网站上的确切文字,但以下是基本知识

  1. 用户登录到用户的身份提供商。
  2. 身份提供商生成 id_tokenrefresh_token
  3. id_token 用于向 k8s 断言用户身份。
  4. id_token 过期时,refresh_token 用于生成新的 id_token

id_token 是一个 JSON Web Token (JWT),它表示

  1. 用户是谁。
  2. 用户是哪些组的成员(可选)。
  3. 令牌的有效期。
  4. 并且,它包含一个数字签名,用于验证 JWT 是否已被篡改。

用户的 id 属性 sub 通常是用户的唯一标识符。通常使用 Active Directory 的登录 ID(又名 samAccountName),或者许多实施者更喜欢使用电子邮件地址。总的来说,这不是最佳实践。用户的 ID 应该是唯一的和不可变的。虽然电子邮件地址是唯一的,但它并不总是不可变的(例如,有时名称会更改)。

JWT 在每次从 kubectl 到 k8s 的请求中传递。id_token 被称为“Bearer Token”,因为它授予持有者访问权限而无需任何额外的检查。这意味着,如果 API 调用流程中的系统(例如服务网格代理、验证 webhook 或变异 webhook)泄露了此令牌,则可能会被攻击者滥用。由于这些令牌很容易被滥用,因此它们的生命周期应该非常短。我建议一分钟。这样,如果令牌被泄露,当有人看到它,知道它是什么并且能够使用它时,令牌已经过期,因此没用了。当使用如此短期的令牌时,重要的是配置 refresh_token 以在 id_token 过期后更新您的 id_token

kubectl 知道如何通过使用 refresh_token 调用身份提供商的授权服务 URL 来刷新 id_token 令牌。refresh_token 是 k8s 的 API 服务器永远不会使用的令牌,应被用户视为 secret。此令牌用于获取新的 JWT,此时可以使用新的 refresh_tokenid_token 应该具有非常短的生命周期,而 refresh_token 超时时间应该类似于不活动超时时间,通常为 15-20 分钟。这样,您的 k8s 实施将符合您企业中关注不活动超时的策略。使用 refresh_token 获取新的 id_token 比使用有效期更长的 id_token 更安全,因为 refresh_token 意味着以下几点

  1. 它只能使用一次;一旦使用,就会生成一个新的令牌。
  2. 它只在用户和身份提供商之间传递,因此可能泄露它的参与者要少得多。
  3. 它不会识别您的身份;如果单独泄露,则无法用于识别您的身份,因为它是不透明的,因此攻击者不知道在没有其他信息的情况下如何处理它。

Kubernetes 仪表板

仪表板没有自己的登录系统。它所能做的只是使用代表用户操作的现有令牌。这通常意味着在仪表板前面放置一个反向代理,该代理将在每个请求中注入 id_token。然后,反向代理负责在需要时刷新令牌。

我应该使用哪个身份提供商?

在选择身份提供商时,k8s 实际上只有两个要求

  1. 必须 支持 OpenID Connect 发现。
  2. 它提供了一种机制来生成令牌并将其注入到您的 ~/.kube/config 中。

差不多就是这样!发现很重要,因为它可以避免您必须手动告诉 k8s 不同 URL 的位置、用于签名的密钥等等。将 k8s 指向包含所有信息的发现 URL 要容易得多。这是一个通用标准,大多数身份提供商都开箱即用地支持它。

第 2 点是事情变得有趣的地方。关于如何从您的登录点(通常是 Web 浏览器)获取令牌信息到您的 ~/.kube/config 中,有不同的思想流派。

Web 浏览器注入

在此模型中,一切都集中在您的 Web 浏览器上。您通过浏览器进行身份验证,然后获得命令以正确设置您的 kubectl 客户端。例如,OpenUnison(我们自己的项目)为您提供了一个命令,用于在身份验证后设置您的集群配置(图 2)。

""

图 2. 浏览器令牌

您使用 kubectl 的内置功能从命令行配置配置文件以完成设置。

此方法有几个优点

  1. 浏览器具有最多的身份验证选项。除了用户名和密码外,您还可以集成 Kerberos、多因素身份验证等等。
  2. 您无需管理复杂的 k8s 配置;它们为您管理。
  3. 这适用于标准的 kubectl 命令,因此无需在工作站上部署更多内容。

kubectl 插件

您可以使用 插件 扩展 kubectl 命令。使用插件,您可以收集用户的凭据,然后生成令牌。我见过一些插件会从 CLI 收集您的凭据,还有一些插件会启动浏览器提示您登录。从 CLI 的角度来看,此方法很好,因为它允许您的 CLI 驱动您的用户体验。此方法的主要缺点是需要在每个工作站上安装插件。

下载配置

使用此方法,身份提供商(或自定义构建的应用程序)为您提供了一个完全生成的配置文件,您可以下载该文件。如果某些内容未保存到正确的位置,则可能会产生支持问题。

选择身份提供商后,请按照其集成说明进行操作。重要的关键项是发现 URL、标识符“声明”和组“声明”。

X509 证书

证书身份验证利用客户端(通常是 kubectl 命令)和 k8s API 服务器之间的 TLS 握手,通过向 API 服务器出示证书来断言身份。除了一个用例之外,此方法不是“最佳实践”,并且应不鼓励使用,原因如下

  1. 证书无法在 k8s 中吊销。您要么需要等到证书过期,要么重新密钥整个集群。
  2. 证书的私钥永远 不应离开生成它的安全介质。通常,您会被“给予”一个密钥对和证书来使用。
  3. 很难将组与证书一起使用。您需要将它们嵌入到主题中,如果这些组需要更改,那么,请参阅上面的第 1 条。

您应该将 X509 证书用于身份验证的唯一情况是当您引导集群或在紧急情况下且您的身份提供商不可用时。大多数发行版都会为每个 master 部署一个密钥对,因此如果您 ssh 进入该 master,则可以使用 kubectl 管理集群。这意味着您需要锁定对 master 的访问权限(我计划在以后的文章中介绍这一点)。

Webhook

此方法允许您通过 webhook 集成第三方登录或令牌系统。k8s 不会告诉您如何验证身份,而是调用 webhook 并询问“这是谁?”

除非您是云提供商并且拥有自己的身份解决方案,否则不要这样做。我见过的几乎所有此方法的实现都变成了“让我们传递密码”或考虑不周的 OpenID Connect。

使用模拟的反向代理

在这里,客户端(kubectl 或其他)不直接与 API 服务器通信。而是与反向代理通信,反向代理然后将标头注入到请求中以表示用户。这通常被认为是处理高级身份验证场景的一种方法,因为它从 API 服务器的角度来看需要最少的工作量。实施步骤如下

  1. 创建一个服务帐户。
  2. 授权服务帐户执行模拟。
  3. 配置反向代理以将服务帐户和模拟标头注入到每个请求中。

此解决方案提供了这些问题以及与 Webhook 相同的陷阱。现有标准很可能适合您的需求,并且更易于管理和维护。

总结

要将身份集成到 k8s 中,请遵循此基本清单

  1. 仅将服务帐户用于系统,而不是人员。
  2. 对人员使用 OpenID Connect;它经过良好验证,并受到多个系统(包括开源和专有系统)的支持。
  3. 仅在“紧急情况下打破玻璃”的情况下使用证书身份验证。

遵循这些规则,您会发现您的开发人员很高兴少记住一个密码,并且您的安全团队会很高兴您遵循最佳实践和合规性要求。

资源

Marc Boorshtein 是 Tremolo Security 的首席技术官,该公司构建开源身份管理软件。Marc 在开源社区工作了 15 年。近年来,Marc 专注于云原生身份,包括重写了大部分 Kubernetes 关于 OpenID Connect 的文档。您可以在 Twitter 上通过 @mlbiam 联系 Marc。

加载 Disqus 评论