亚马逊AWS官方博客

使用 Kube-OIDC-Proxy 跨多个 EKS 集群进行一致的 OIDC 身份验证

Amazon Elastic Kubernetes Service (Amazon EKS) 对 IAM 进行用户身份验证,然后再授予用户至 EKS 集群的访问权。至每个集群的访问权由 aws-auth ConfigMap 进行控制,该文件将 IAM 用户/角色映射到 Kubernetes RBAC 组。在来自 Jetstack 的 Josh Van Leeuwen 发表的客座博文中,我们将讨论如何使用多个开源项目跨多个集群对 GitHub 等 OIDC 提供商进行用户身份验证。

Kube-OIDC-ProxyJetstack 开发的开源反向代理,可以为各个后端启用 OIDC 身份验证。对于 EKS,可以使用第三方提供商提供的相同用户身份对多个 EKS 集群进行 OIDC 身份验证。此博文将探讨 Kube-OIDC-Proxy 的工作原理,如何将其部署到多个 EKS 集群中以及如何利用其它开源工具为最终用户提供无缝身份验证经验。

它的工作原理是什么?

OIDC 或 OpenID Connect 是现有 OAuth 2.0 协议的扩展协议。OIDC 流从用户从一个身份提供商处请求 JSON Web 令牌开始,其中包含范围适当的用户相关属性列表。内容包括如下属性,如电子邮件地址或名称、包含有关令牌本身的额外信息的标头,例如签名算法,以及身份提供商签署之令牌的签名。此签名将被资源服务器用于根据身份提供商提供的证书颁发机构验证令牌内容。

Kube-OIDC-Proxy 是基于 Kubernetes 内部的反向代理,它使用 OIDC 对请求进行身份验证。然后,这些经过身份验证的请求被转发至一些后端,如 Kubernetes API Server,并根据传入的 OIDC 令牌所验证的身份附加模拟标头。这与 OIDC 身份验证不可配置的 Kubernetes API 服务器非常匹配,因为请求可以由 Kube-OIDC-Proxy 使用 OIDC 令牌的身份通过 RBAC 进行模拟

部署

要创建功能完备的多集群 OIDC 身份验证基础设施,我们可以使用其他开源项目来集成外部 OIDC 提供商,并为最终用户提供友好的体验。也就是说,我们将使用以下项目:

  • Cert-Manager:一种证书管理工具,用于请求和自动更新 Kubernetes 中的 TLS 证书,包括 Let’s Encrypt 的证书。
  • Dex:一个 OIDC 提供商,为外部 OAuth 提供商提供连接器以获取身份;在本例中,将使用 GitHub 应用程序。Dex 的单个实例将被部署到主集群中,为所有集群中的所有其他组件提供服务,包括签署 OIDC 令牌。
  • Gangway:促进 OAuth 流获取必要的用户身份的一个网站,从而从提供商处形成 OIDC 令牌并根据这些令牌为最终用户自动生成 Kubeconfigs。
  • ContourEnvoy 代理支持的一个入口控制器,可使用单个公开负载均衡器将流量路由至不同组件中。TLS 透传启用,表示 TLS 连接在每个应用程序中终止,而不是在边缘,从而防止未加密的流量在集群中传递。
  • NGINX:用作 HTTP Web 服务器,以提供登陆页面,帮助最终用户跨集群轻松导航至不同的 Gangway。单个实例将部署到主集群中,从而为所有集群提供服务。
  • Kube-OIDC-Proxy:反向代理,用户将通过其进行身份验证以便访问每个集群中的 API 服务器。

部署 Kube-OIDC-Proxy

首先,从 GitHub 中克隆 kube-oidc 存储库:

git clone https://github.com/jetstack/kube-oidc-proxy.git

创建一个或多个 EKS 集群,以供 Kube-OIDC-Proxy 用于进行身份验证。使用 Kube-OIDC-Proxy 存储库,将 EKS Terraform 模块复制到所需的任何数量和名称。

注意:此步骤需要 Hashicorp 的 Terraform。您可以从 Terraform 下载页面获取 Terraform CLI。从 zip 文件中提取二进制文件,然后将二进制文件复制到您的路径中。可以通过访问安装 Terraform 获取进一步说明。

注意:默认情况下,EKS 集群在 eu-west-1 区域中创建。如果您想要在另一个区域中创建集群,请使用您的首选区域名称更新 kube-oidc-proxy/demo/infrastructure/amazon/providers.tf 文件,然后完成此步骤。

$ cd demo
$ cp -r infrastructure/amazon infrastructure/amazon_cluster_1
$ cp -r infrastructure/amazon infrastructure/amazon_cluster_2
$ cp -r infrastructure/amazon infrastructure/amazon_cluster_3

应用 terraform 配置以建立这些集群:

$ CLOUD=amazon_cluster_1 make terraform_apply
$ CLOUD=amazon_cluster_2 make terraform_apply
$ CLOUD=amazon_cluster_3 make terraform_apply

完成后,应创建好三个集群。同时也应该有三个 Kubeconfig 文件根据常用方法通过 AWS 对这些集群进行身份验证。

$ eksctl get clusters
NAME            REGION
cluster-2e56deb2    eu-west-1
cluster-325e7cb3    eu-west-1
cluster-b8706149    eu-west-1
$ ls -a
... .kubeconfig-amazon_cluster_1 .kubeconfig-amazon_cluster_2 .kubeconfig-amazon_cluster_3 ...

注意:如果前述步骤创建工作线程节点或 kubeconfig 文件失败,请重新运行 terraform_apply

要部署清单,我们将使用 Jsonnet 生成将部署到每个集群中的清单。生成的这些清单通过更改集群相关选项的单个 config.jsonnet 文件进行配置。完成后,此文件应存储在 kube-oidc-proxy/demo 目录中。

下面的示例显示的是 config.jsonnet 文件完成时的样子。接下来的几个步骤将指导您创建文件。

local main = import './manifests/main.jsonnet';

function(cloud='amazon_cluster_1') main {
  cloud: cloud,
  clouds+: {
    amazon_cluster_1: {
      master: true,
      domain_part: '.cluster-1',
      config: import './manifests/amazon_cluster_1-config.json',
    },
    amazon_cluster_2: {
      master: false,
      domain_part: '.cluster-2',
      config: import './manifests/amazon_cluster_2-config.json',
    },
    amazon_cluster_3: {
      master: false,
      domain_part: '.cluster-3',
      config: import './manifests/amazon_cluster_3-config.json',
    },
    google: null,
    amazon: null,
    digitalocean: null,
  },
  base_domain: '.eks.aws.joshvanl.com',
  cert_manager+: {
    letsencrypt_contact_email:: 'xxxxxx@gmail.com',
    solvers+: [
      {
        http01: {
          ingress: {},
        },
      },
    ],
  },
  dex+: if $.master then {
    connectors: [
      $.dex.Connector('github', 'GitHub', 'github', {
        clientID: 'xxx',
        clientSecret: 'xxx',
        homePage: 'eks.aws.joshvanl.com',
      }),
    ],
  } else {
  },
}

首先,我们将引用我们创建的每个集群以及生成的密码,因为它们将用于构建身份验证基础设施。我们还需要表示单个主集群,以容纳 Dex 和登陆 Web 服务器部署,以服务基础设施中的所有其他集群。

function(cloud='amazon_cluster_1') main {
  cloud: cloud,
  clouds+: {
    amazon_cluster_1: {
      master: true,
      domain_part: '.cluster-1',
      config: import './manifests/amazon_cluster_1-config.json',
    },
    amazon_cluster_2: {
      master: false,
      domain_part: '.cluster-2',
      config: import './manifests/amazon_cluster_2-config.json',
    },
    amazon_cluster_3: {
      master: false,
      domain_part: '.cluster-3',
      config: import './manifests/amazon_cluster_3-config.json',
    },
    google: null,
    amazon: null,
    digitalocean: null,
  },

接下来,表示您拥有的基本域的域名,它将用于连接各个集群。基本域还将用于 URL,稍后,您可将它用于连接登陆页面。

将“.eks.aws.joshvanl.com”替换为您自己的基本域。

  base_domain: '.eks.aws.joshvanl.com',

配置 cert-manager,以设置账户电子邮件地址以及我们在请求证书时希望解决的问题。我们将在这里使用 HTTP01 解决问题。

将“xxxxxx@gmail.com”替换为您自己的联系电子邮件地址。

  cert_manager+: {
    letsencrypt_contact_email:: 'xxxxxx@gmail.com',
    solvers+: [
      {
        http01: {
          ingress: {},
        },
      },
    ],
  },

最后,设置您希望用于 Dex 的连接器。

  dex+: if $.master then {
    connectors: [
      $.dex.Connector('github', 'GitHub', 'github', {
        clientID: 'xxx',
        clientSecret: 'xxx',
        homePage: 'eks.aws.joshvanl.com',
      }),
    ],
  } else {
  },

这些连接器用于完成 OAuth 流,从而对新用户身份进行验证。完成 OAuth 流时,Dex 将签署包含该颁发机构接收和验证的身份的 OIDC 令牌。在本例中,我们将使用 GitHub 应用程序。OAuth 流将根据用户的 GitHub 配置文件通过其 GitHub 账户和其身份进行解决。您可以在构建 OAuth 应用程序中了解有关如何创建 GitHub OAuth 应用程序的更多信息。

使用创建的集群和编写的配置,我们可以开始安装组件。

将清单部署到主集群中:

$ CLOUD=amazon_cluster_1 make manifests_apply

此操作应该将所有组件安装到适当配置的集群中。为了解决 HTTP01 问题并使集群可以通过您选择的基本域从 Internet 进行路由,请等待 ExternalIP 公开 LoadBalancer 类型的服务。

$ export KUBECONFIG=.kubeconfig-amazon_cluster_1
$ kubectl get services --namespace auth
$ kc get svc -n auth
NAME                        TYPE           CLUSTER-IP       EXTERNAL-IP                                                               PORT(S)                      AGE
contour                     LoadBalancer   172.20.221.86    a21a97cf9d40e11e9b58302e1256987f-1040136959.eu-west-1.elb.amazonaws.com   443:31844/TCP,80:32636/TCP   105s
dex                         ClusterIP      172.20.147.161   <none>                                                                    5556/TCP                     104s
gangway                     ClusterIP      172.20.69.133    <none>                                                                    8080/TCP                     80s
kube-oidc-proxy             ClusterIP      172.20.60.178    <none>                                                                    443/TCP                      79s
landingpage                 ClusterIP      172.20.90.110    <none>                                                                    80/TCP                       79s

生成这些项目后,创建三个指向登陆页面 URL 的 CNAME 记录,Dex 服务器和一个指向该集群内的组件的通配符记录。您可以发现为此集群生成的入口记录如下所示:

$ kubectl get ingressroutes --namespace auth
NAME              FQDN                                             TLS SECRET   FIRST ROUTE   STATUS   STATUS DESCRIPTION
dex               dex.eks.aws.joshvanl.com                                      /             valid    valid IngressRoute
gangway           gangway.cluster-1.eks.aws.joshvanl.com                        /             valid    valid IngressRoute
kube-oidc-proxy   kube-oidc-proxy.cluster-1.eks.aws.joshvanl.com                /             valid    valid IngressRoute

$ kubectl get ingress --namespace auth
landingpage                 gangway.cluster-1.eks.aws.joshvanl.com,kube-oidc-proxy.cluster-1.eks.aws.joshvanl.com,dex.eks.aws.joshvanl.com + 1 more...            80, 443   14s

这需要三个如下所示的 CNAME 记录:

.cluster-1.eks.aws  CNAME 1h a21a97cf9d40e11e9b58302e1256987f-1040136959.eu-west-1.elb.amazonaws.com.
dex.eks.aws          CNAME 1h a21a97cf9d40e11e9b58302e1256987f-1040136959.eu-west-1.elb.amazonaws.com.
eks.aws              CNAME 1h a21a97cf9d40e11e9b58302e1256987f-1040136959.eu-west-1.elb.amazonaws.com.

当 DNS 记录传播到 Internet 之后,证书 HTTP01 挑战应成功,且证书应签发。您可以通过检查证书资源状态和 cert-manager 控制器中的日志输出来查看签发证书的状态:

$ kubectl get certificates --namespace auth
NAME              READY   SECRET                AGE
dex               True    dex-tls               13s
gangway           True    gangway-tls           13s
kube-oidc-proxy   True    kube-oidc-proxy-tls   12s
landingpage       True    landingpage-tls       12s

$ kubectl logs --namespace cert-manager cert-manager-xxx

注意:如果您收到证书错误,请回收 Kube-OIDC-Proxy、Dex 和 Gangway pod。

当证书签发后,您应该能够访问 Kube-OIDC-Proxy Demo 登陆页面:

由于我们只部署了一个集群,只有第一个集群才能访问实时 Gangway。通过点击 GANGWAY AMAZON_CLUSTER_1 的按钮,我们可以通过对 GitHub 进行身份验证来请求第一个集群的 OIDC 令牌。

当您下载 kubeconfig 后,您应该能够使用 Kube-OIDC-Proxy 通过我们的 OIDC 令牌连接到此集群。

$ kubectl --kubeconfig ~/Downloads/kubeconfig get nodes
服务器错误(禁止):节点被禁止:用户“xxxxxx@gmail.com”无法在集群范围内在 API 组 "" 中列出资源“节点”

由于我们尚未应用此用户的任何 RBAC 权限,此命令失败。然而,它确实表明我们正在以 GitHub 的身份连接。现在,我们可以分配此用户 cluster-admin 权限,但您很可能希望为您自己的集群的租户创建更严格、更精细化的权限。

xxxxxx@gmail.com 替换为有效的 GitHub ID。

$ kubectl create clusterrolebinding xxxxxx@gmail.com --clusterrole cluster-admin --user xxxxxx@gmail.com
clusterrolebinding.rbac.authorization.k8s.io/xxxxxx@gmail.com created
$ kubectl --kubeconfig ~/Downloads/kubeconfig get nodes
NAME                                       STATUS   ROLES    AGE     VERSION
ip-10-0-2-136.eu-west-1.compute.internal   Ready    <none>   32m   v1.14.6-eks-5047ed
ip-10-0-3-178.eu-west-1.compute.internal   Ready    <none>   32m   v1.14.6-eks-5047ed
ip-10-0-3-50.eu-west-1.compute.internal    Ready    <none>   32m   v1.14.6-eks-5047ed

现在,第一个集群已配置完成,我们准备为接下来的两个集群重复此过程。请记住创建指向新集群终端节点的新 CNAME 记录,以便它们可以通过 Internet 进行路由,并且能够解决 HTTP01 问题。

$ CLOUD=amazon_cluster_2 make manifests_apply
$ CLOUD=amazon_cluster_3 make manifests_apply

由于位于主集群中的项目将在所有集群中共享,那些非主集群都将不会部署 Dex 或登陆页面。

完成后,所有三个集群都将准备好请求令牌,并且可以使用 OIDC 进行访问。

总结

使用 Kube-OIDC-Proxy 可使具有多集群、多租户 Kubernetes 基础设施的组织促进基于第三方身份提供商的一致性 OIDC 身份验证。在此博文中,我演示了如何将其他开源项目与 Kube-OIDC-Proxy 结合使用为集群的最终用户创建简化的登录体验。

该项目的未来开发将涉及创建更多选项来处理带有和不带有令牌的代理请求,以及实施审计。您可以在此了解该项目并关注 GitHub 的进度。

本博文中的内容和意见属于第三方作者,AWS 不对本博文的内容或准确性负责。


 

Josh 是 Jetstack 的客户可靠性工程师,帮助客户实现其自己的 Kubernetes 使用。他在 cert-manager、tarmak 和 Kube-OIDC-Proxy 等开源项目上已与 Kubernetes 合作三年。工作之余,Josh 喜欢烹饪和瑜伽。

电子邮件:joshua.vanleeuwen@jetstack.io
Twitter: @JoshVanL
github.com/joshvanl