# Kubernetes 的证书认证

原文：[How Kubernetes certificate authorities work](https://jvns.ca/blog/2017/08/05/how-kubernetes-certificates-work/)

今天让我们聊聊 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 receiving 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 be 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`](http://foo.com)`,`[`foo.com`](http://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 文件进行相关设置。

```yaml
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 文件的时候，可以按照如下方式设置使用证书：

```yaml
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 独立设置可能不是必要的，我是希望帮助读者理解这些东西如何设置使之符合各种需求，而不是简单的面向文档照本宣科。
