Kubernetes 的证书认证

今天让我们聊聊 Kubernetes 的公私钥和证书认证。

本文内容会提及如何根据需要对 CA、公私钥进行组织并对集群进行设置。

Kubernetes 的组件中有很多不同的地方可以放置证书之类的东西。在进行集群安装的时候,我感觉有一百多亿个不同的命令参数是用来设置证书、密钥的,真不明白是怎么弄到一起工作的。

当然了,没有一百亿那么多的参数,不过的确很多的。比如 API Server 的参数吧,有大概 16 个参数是跟这些东西有关的(下面是节选):

--cert-dir string                           The directory where the TLS certs are located. If --tls-cert-file and --tls-private-key-file are provided, this flag will be ignored. (default "/var/run/kubernetes")
--client-ca-file string                     If set, any request presenting a client certificate signed by one of the authorities in the client-ca-file is authenticated with an identity corresponding to the CommonName of the client certificate.
--etcd-certfile string                      SSL certification file used to secure etcd communication.
--etcd-keyfile string                       SSL key file used to secure etcd communication.
--kubelet-certificate-authority string      Path to a cert file for the certificate authority.
--kubelet-client-certificate string         Path to a client cert file for TLS.
--kubelet-client-key string                 Path to a client key file for TLS.
--proxy-client-cert-file string             Client certificate used to prove the identity of the aggregator or kube-apiserver when it must call out during a request. This includes proxying requests to a user api-server and calling out to webhook admission plugins. It is expected that this cert includes a signature from the CA in the --requestheader-client-ca-file flag. That CA is published in the 'extension-apiserver-authentication' configmap in the kube-system namespace. Components recieving calls from kube-aggregator should use that CA to perform their half of the mutual TLS verification.
--proxy-client-key-file string              Private key for the client certificate used to prove the identity of the aggregator or kube-apiserver when it must call out during a request. This includes proxying requests to a user api-server and calling out to webhook admission plugins.
--requestheader-allowed-names stringSlice   List of client certificate common names to allow to provide usernames in headers specified by --requestheader-username-headers. If empty, any client certificate validated by the authorities in --requestheader-client-ca-file is allowed.
--requestheader-client-ca-file string       Root certificate bundle to use to verify client certificates on incoming requests before trusting usernames in headers specified by --requestheader-username-headers
--service-account-key-file stringArray      File containing PEM-encoded x509 RSA or ECDSA private or public keys, used to verify ServiceAccount tokens. If unspecified, --tls-private-key-file is used. The specified file can contain multiple keys, and the flag can be specified multiple times with different files.
--ssh-keyfile string                        If non-empty, use secure SSH proxy to the nodes, using this user keyfile
--tls-ca-file string                        If set, this certificate authority will used for secure access from Admission Controllers. This must be a valid PEM-encoded CA bundle. Alternatively, the certificate authority can be appended to the certificate provided by --tls-cert-file.
--tls-cert-file string                      File containing the default x509 Certificate for HTTPS. (CA cert, if any, concatenated after server cert). If HTTPS serving is enabled, and --tls-cert-file and --tls-private-key-file are not provided, a self-signed certificate and key are generated for the public address and saved to /var/run/kubernetes.
--tls-private-key-file string               File containing the default x509 private key matching --tls-cert-file.
--tls-sni-cert-key namedCertKey             A pair of x509 certificate and private key file paths, optionally suffixed with a list of domain patterns which are fully qualified domain names, possibly with prefixed wildcard segments. If no domain patterns are provided, the names of the certificate are extracted. Non-wildcard matches trump over wildcard matches, explicit domain patterns trump over extracted names. For multiple key/certificate pairs, use the --tls-sni-cert-key multiple times. Examples: "example.crt,example.key" or "foo.crt,foo.key:*.foo.com,foo.com". (default [])

接下来是 Controller Manager 的:

--cluster-signing-cert-file string          Filename containing a PEM-encoded X509 CA certificate used to issue cluster-scoped certificates (default "/etc/kubernetes/ca/ca.pem")
--cluster-signing-key-file string           Filename containing a PEM-encoded RSA or ECDSA private key used to sign cluster-scoped certificates (default "/etc/kubernetes/ca/ca.key")
--root-ca-file string                       If set, this root certificate authority will be included in service account's token secret. This must be a valid PEM-encoded CA bundle.
--service-account-private-key-file string   Filename containing a PEM-encoded private RSA or ECDSA key used to sign service account tokens.

再来个 Kubelet:

--client-ca-file string                   If set, any request presenting a client certificate signed by one of the authorities in the client-ca-file is authenticated with an identity corresponding to the CommonName of the client certificate.
--tls-cert-file string                    File containing x509 Certificate used for serving HTTPS (with intermediate certs, if any, concatenated after server cert). If --tls-cert-file and --tls-private-key-file are not provided, a self-signed certificate and key are generated for the public address and saved to the directory passed to --cert-dir.
--tls-private-key-file string             File containing x509 private key matching --tls-cert-file.

本文假设读者:

  • 对 TLS 认证和 CA 有一些了解。
  • 能把这些东西跑起来,但是不知道为啥。

下面还会说明在 Kubernetes 中的不同 CA,以及不同 CA 的协同工作。

另外有些我在工作中学到的一些:

  • 不要用 CA 来检查 Service Account Key。Service Account key 有点古怪,他跟其他的 Key 不是一同处理的。
  • 如果 kubernetes 建立用户和组的方式不适合需求,可以(应该)设置一个认证代理。
  • API Server 如果设置了太多 CA,会显得有点乱。

这个题目有点复杂,如果阅读中发现任何问题请通知我。

PKI 和 Kubernetes

在阅读 Kubernetes 材料的过程中,我注意到一个词出现了很多次:“PKI”,我不很清楚这是个什么。

如果你有个在运行的 Kubernetes 集群,其中可能有几百上千个公钥私钥(客户端认证、服务认证等等)。

如果这几百个 Key 是独立的互不相关的,就会让安全性堕入泥潭。因此我们需要个 CA,CA 的职责就是签发证书,并告诉用户“这个公钥是我发的,靠谱”。

PKI 就是组织 Key 的方式 —— 什么 Key 是用什么 CA 签发的。

例如:

  • 可以为每个集群准备一个 CA,集群所有的私钥都从这个 CA 签发(Kubernetes 文档中多数是这个情况)。
  • 可以准备一个全局 CA,所有的私钥都从此而来。
  • 单独给服务使用一个 CA,对外可见;内部另外使用一个 CA 作为专门用途。
  • 还有其他。

我不是安全专家,所以不想说如何管理私钥和 CA 才更好。但是不管你用的什么样的模型,其实都可以跟 Kubernetes 协调工作。

下面根据需求来确认管理 PKI 的方式,以及如何在 Kubernetes 中实现。

Kubernetes 集群需要一个单根结构的 CA 么?不

如果你读了不少 Kubernetes 集群的安装文档,会注意到总有一步:设置一个 CA。Kelsey Hightower 的大作 “Kubernetes the hard way” 中是第二步,“在集群中信任 TLS ”中说:

每个 Kubernetes 集群都有一个集群根 CA。CA 一般用于集群中的组件验证 API Server 的合法性,在 API Serverl 来说,就是验证 kubelet 客户端的证书,诸如此类的。

基本上来说:

  1. 设置一个 CA
  2. 用这个 CA 生成不同的证书,给 Kubernetes 集群的不同组件使用。

如果不想为每个集群设置一个新的 CA 呢?可能有很多理由。但是我担心,最终还是需要提供一个根 CA。

这好像说上面的话是假的,其实可以使用很多不同的 CA 签发的证书来管理 Kubernetes。总的说来,还是要结合具体场景的需求。

接下来我们来探讨一下证书相关的参数,以及互相之间的关系。每一节都会包含一个可以定义的 CA。每一个都是独立的,并不需要一致。不过在实际操作中,可能你并不想管理 6 个不同的 CA。

API Server 的 TLS 证书(以及 CA)

--tls-cert-file string

包含缺省的 x509 https 认证的文件(可以包含 CA 证书),如果启用了 HTTPS 服务,又没有指定--tls-cert-file--tls-private-key-file参数,就会在 /var/run/kubernetes生成一个自签名证书以及 Key

--tls-private-key-file

--tls-cert-file证书的 x509 私钥。

如果用 TLS 连接 API Server,就需要这两个参数来选择 API Server 使用的证书。

证书设置了之后,还需要给各个组件的 kubeconfig 文件进行相关设置。

current-context: my-context
apiVersion: v1
clusters:
- cluster:
    certificate-authority: /path/to/my/ca.crt # 签发 API Server 证书的 CA 证书
    server: https://horse.org:4443
  name: my-cluster
kind: Config
users:
- name: green-user
  user:
    client-certificate: path/to/my/client/cert # 后面会讲
    client-key: path/to/my/client/key # 后面会讲

有个让我惊奇的事情就是——这个宇宙里面的几乎所有其他使用 TLS 的系统都会去 /etc/ssl 查找一个本机信任的 CA 列表,但是 Kubernetes 很傲娇,他不会去找,必须显式的进行告知签发 CA。

可以使用参数--kubeconfig /path/to/kubeconfig.yaml将配置文件分配给各个组件进行使用。

这样我们完成了第一个 CA 的设置:签发 API Server 证书的 CA。这个 CA 跟其他的 CA 可以不一致。

API Server 客户证书认证

--client-ca-file

如果设置了这一参数,所有的请求都应该使用该文件中所包含的 CA 签发的证书进行签署,证书中的 Common Name 会作为用户名进行使用。

Kubernetes 组件获得 API Server 认证的方法之一就是使用这一参数。

所有客户端证书都应该由这一 CA 签发(不需要和 API Server 的 CA 一致)。

当使用 kubeconfig 文件的时候,可以按照如下方式设置使用证书:

kind: Config
users:
- name: green-user
  user:
    client-certificate: path/to/my/client/cert
    client-key: path/to/my/client/key

Kubernetes 做了很多用户证书方面的假设(用户名就是 Common Name,Group 就是 Organization)。如果这些假设不符合需求,那么就应该停用客户端证书认证,改用认证代理。

请求 Header 的证书认证(或者:认证代理)

API server 参数

--requestheader-allowed-names stringSlice

--requestheader-username-headers 中指定的 Header 中包含用户名,这一参数的列表确定了允许有效的 Common Name,如果这一参数的列表为空,则所有通过--requestheader-client-ca-file校验的都允许通过。

--requestheader-client-ca-file string

针对收到的请求,在信任--requestheader-username-headers中指定的 Header 里面包含的用户名之前,首先会用这一 CA 对客户证书进行验证。

另外一个设置 Kubernetes 认证的方式就是认证代理。如果你对如何向 API Server 发送用户名和组有很多想法,可以设置一个代理,这一代理会使用 HTTP Header 将用户名和组发送给 API Server。

文档中简单的解释了一下工作方式。代理使用一个客户端证书表明身份,--requestheader-client-ca-file告知 API Server,该证书所属的 CA。

我觉得——API Server 有太多认证方式了(客户端认证、认证代理、Token 等等),让人很迷惑。建议用户尽量少的同时使用认证方式,便于管理、使用和除错。

service account 私钥(不是 CA 签发的)

API Server 参数

--service-account-key-file

PEM 编码的 X509 RSA 或者 ECDSA 的私钥或者公钥,用于检验 ServiceAccount 的 token。如果没指定的话,会使用--tls-private-key-file替代。文件中可以包含多个 Key,这一参数可以重复指定多个文件。

Controller Manager 参数

--service-account-private-key-file

PEM 编码的 X509 RSA 或者 ECDSA Key,用于签署 Service Account Token。

Controller Manager 使用私钥签署 Service Account Token。跟 Kubernetes 中使用的其他私钥不同的是,这个私钥是不支持同一 CA 验证的,因此上,需要给每个 Controller Manager 指定一致的私钥文件。

这个 Key 也不需要什么 CA 来做签署,生成很容易:

openssl genrsa -out private.key 4096

然后分发给每个 Controller Manager 和 API Server 就可以了。

使用和--tls-private-key-file一致的文件是可以工作的——只要你给每个 API Server 用的都是同一个 TLS Key(一般都这么做的吧?)。(这里我假设你运行的一个有高可用支持的,多个 API Server 和多个 Controller Manager同时运行的集群)

如果两个不同的 Controller Manager 用了两个不同的 Key,那就杯具了,他们会用各自的 Key 来生成 Token,最终导致无效判定。我觉得这点不太合理,Kubernetes 应该和其他方面一样,使用 CA 进行管理。通过对源码的月度,我觉得原因可能是 jwt-go 不支持 CA。

Kubelet 证书认证

总算到了 Kubelet 环节了,下面是 API Server 和 Kubelet 相关的内容:

API Server 参数

--kubelet-certificate-authority CA 证书的路径。

--kubelet-client-certificate TLS 证书文件

--kubelet-client-key TLS Key 文件

Kubelet 参数

--client-ca-file

请求中的客户端证书如果是由文件中的 CA 签署的,那么他的 Common Name 就会被用作 ID 进行认证。

--tls-cert-file

用来提供 HTTPS 服务的 x509 证书(其中也可包含中间人证书)。如果不提供--tls-cert-file--tls-private-key-file,就会为主机地址生成一个自签名的证书和对应的 Key,并保存到--cert-dir目录里。

--tls-private-key-file

--tls-cert-file 对应的 Key

校验 kubelet 的请求是有用的,因为 Kubelet 的职责就是在主机上执行代码。

这里实际上有两个 CA,这里不准备深入描述,情况和 API Server 是一样的,Kubelet 用 TLS 来进行认证,也支持客户证书认证。

另外还要告知 API Server,用什么 CA 检查 Kubelet 的 TLS,另外用什么证书来跟 Kubelet 通信。

再说一次,这两个 CA 是可以不同的。

太多 CA 了

现在我们找到了五个不同的 CA,他们各自独立的为 Kubernetes 提供支持。

其实还有一些没讨论到的证书,不过希望本文能给你阅读官方文档提供一点帮助。

当然了,每个 CA 独立设置可能不是必要的,我是希望帮助读者理解这些东西如何设置使之符合各种需求,而不是简单的面向文档照本宣科。