Kubernetes平台由6个组件组成:apiserver、scheduler、controller-manager、kubelet、kube-proxy、etcd,其中etcd是Kubernetes数据库,在生产环境中一般不允许直接访问。平台通过apiserver组件对外提供HTTPS协议服务接口,其它scheduler、controller-manager、kubelet、kube-proxy组件不提供交互接口。运维中对平台的管理操作都需要通过apiserver提供的功能接口完成,因此Kubernetes总体的API安全防护机制是对用户访问操作HTTPS服务接口的控制。

Kubernetes只对以下的API请求属性进行检查:

  • user - username,uid
  • group - user group
  • “extra”- 额外信息
  • API - API资源的对象
  • Request path - 请求资源的路径(k8s使用resultful风格接口的API)
  • http://Node_IPaddr:6443/apis/apps/v1/namespaces/namespaces_name/resource_name/
  • HTTP 请求动作 - HTTP verbs get,post,put,和delete用于非资源请求
  • HTTP 请求动作映射到 API资源操作- get,list,create,update,patch,watch,proxy,redirect,delete,
  • 和deletecollection用于请求resource
  • Resource -被访问(仅用于resource 请求)的resource 的ID或名字- *对于使用resource 的请求get,update,
  • patch,和delete,必须提供resource 名称。
  • Subresource - 正在访问的subresource (仅用于请求resource )
  • Namespace - 正在访问对象的命名空间(仅针对命名空间的请求资源)
  • API group - 正在访问的API组(仅用于请求资源)。空字符串指定核心API组。

用户可通过客户端kubectl命令行工具或其它方式访问Kubernetes的API资源,每个访问API的请求,都要经过三个步骤校验:Authentication、Authorization、Admission Control,总体如下图所示:

image-20210122083948239

  • Authentication(认证),对用户身份进行验证。认证方式现共有8种,可以启用一种或多种认证方式,只要有一种认证方式通过,就不再进行其它方式的认证。通常启用X509 Client Certs和Service Accout Tokens两种认证方式。

  • Authorization(授权),验证用户是否拥有访问权限。授权方式现共有6种,可以启用一种或多种授权方式,启用的任一种方式依据授权策略明确允许或拒绝请求,则立即返回,不再进行其它方式的授权。通常启用RBAC和Node授权方式。

  • Admission Control(准入控制),对API资源对象修改、校验。它有一个插件列表,所有请求需要经过这个列表中的每个准入控制插件修改、校验,如果某一个准入控制插件验证失败,就拒绝请求。

用户访问 Kubernetes API时,apiserver认证授权模块从HTTPS请求中获取用户的身份信息、请求资源路径和业务对象参数。身份信息是用来确认用户的身份是否合法,资源路径是用于判定用户是否拥有访问操作的权限,业务对象参数则在准入控制中接受修改或校验参数设置是否符合业务要求。

image-20210122084021569

  • 请求到达后,apiserver从请求的证书、Header中获取用户信息:Name(用户名)、UID(用户唯一标识)、Groups(用户分组)、Extra(用户额外信息)。认证通过后,通过Context将用户信息向下传播。

  • 授权模块从Context、请求的Method及URI提取用户信息、请求资源属性。使用请求资源属性与授权策略匹配或向外部服务验证判断是否准予访问。

  • 准入控制接收到HTTPS请求提交的Body内容、用户信息、资源信息,根据不同准入控制插件的功能对上述信息做修改或校验。

认证即身份验证,认证关注的是谁发送的请求,也就是说用户必须用某种方式证明自己的身份信息。Kubernetes集群有两种类型的用户:普通用户(normal users)、服务账户(service accounts),普通用户并不被Kubernetes管理和保存;而服务账户由Kubernetes存储在etcd中,并可通过apiserver API管理。

从身份验证的方法上可分为4种类型:Token、证书、代理、密码。Kubernetes可以同时启用多个认证方式,在这种情况下,每个认证模块都按顺序尝试验证用户身份,直到其中一个成功就认证通过。如果启用匿名访问,没有被认证模块拒绝的请求将被视为匿名用户,并将该请求标记为system:anonymous 用户名和system:unauthenticated组。从1.6版本开始,ABAC和RBAC授权需要对system:anonymous用户或system:unauthenticated组进行明确授权。

kubernetes 支持很多种认证机制,包括:

  • X509 client certs
  • Static Token File
  • Bootstrap Tokens
  • Static Password File
  • Service Account Tokens
  • OpenId Connect Tokens
  • Webhook Token Authentication
  • Authticating Proxy
  • Anonymous requests
  • User impersonation
  • Client-go credential plugins

本文将对 X509 client certs、 Bootstrap Tokens、 Service Account Tokens进行详细分析

认证

Authenticating Proxy

Authenticating Proxy(认证代理),apiserver只验证代理程序身份,不校验用户身份,验证通过后,apiserver通过指定的Header提取访问的用户信息。

启用此认证方式是通过设置apiserver参数–requestheader-username-headers和–requestheader-client-ca-file。

  • –requestheader-username-headers(必需,不区分大小写)。设定Header中的用户标识,第一个包含value的Header将被作为用户名。

  • –requestheader-client-ca-file(必需)。PEM 编码的CA证书。用来校验代理端客户端证书,防止 Header 欺骗。

  • –requestheader-allowed-names(可选)。证书名称(CN)列表。如果设置了,则必须提供名称列表中的有效客户端证书。如果为空,则允许使用任何名称客户端证书。

  • –requestheader-group-headers( 1.6 以上版本支持,可选,不区分大小写)。建议为 “X-Remote-Group”,设定Header中的用户组标识。指定 Header 中的所有值都将作为用户分组。

  • –requestheader-extra-headers-prefix (1.6 以上版本支持,可选,不区分大小写)。建议为 “X-Remote-Extra-”,标题前缀可用于查找用户的扩展信息,以任何指定的前缀开头的 Header删除前缀后其余部分将成为用户扩展信息的键值,而 Header值则是扩展的值。

例如下面的配置:

1
2
3
--requestheader-username-headers=X-Remote-User
--requestheader-group-headers=X-Remote-Group
--requestheader-extra-headers-prefix=X-Remote-Extra-

代理请求:

1
2
3
4
5
6
GET / HTTP/1.1
X-Remote-User: mifpay
X-Remote-Group: admin
X-Remote-Extra-Channel: vpn
X-Remote-Extra-Scopes: openid
X-Remote-Extra-Scopes: profile

提取后将得到以下信息

image-20210122084821355

Static Password File

Static Password File(静态密码认证),启用此认证方式是通过设置apiserver参数–basic-auth-file=SOMEFILE。密码是无限期使用,更改密码后需要重启apiserver才能生效。

静态密码文件是CSV格式文件,至少包含3列:密码,用户名,用户ID。从Kubernetes 1.6版本开始,第四列(可选)为用户分组,如果有多个用户分组,则该列必须加双引号。例如:

1
password,name,uid, "group1,group2,group3"

当使用HTTPS客户端连接时,apiserver从HTTPS Header中,如Authorization: Basic BASE64ENCODED,提取出用户名和密码。BASE64ENCODED解码后格式为USER:PASSWORD。

从请求中获取到用户名和密码,与静态密码文件中的用户逐一进行比较,匹配成功则认证通过,否则认证失败。认证通过时从CSV中提取匹配的用户信息用于后续的授权等。

Static Token File

Static Token File(静态Token认证),启用此认证方式是通过设置apiserver参数–token-auth-file=SOMEFILE。Token长期有效,更改Token列表后需要重启apiserver才能生效。

SOMEFILE是CSV格式文件,至少包含3列:Token、用户名、用户的UID,其次是可选的分组。每一行表示一个用户。例如:

1
token,name,uid,"group1,group2,group3"

当使用HTTPS客户端连接时,apiserver从HTTPS Header中提取身份Token。Token必须是一个字符序列,在HTTPS Header格式如下:

1
Authorization: Bearer 31ada4fd-adec-460c-809a-9e56ceb75269

从请求中提取身份Token,与静态Token文件中的用户逐一进行比较,匹配成功则认证通过,否则认证失败。认证通过后将匹配的用户信息用于后续的授权等。

OpenID Connect Tokens

OpenID Connect Tokens是OAuth2中的一种,被许多供应商支持,尤其是Azure Active Directory、Salesforce和 Google。该协议对OAuth2的主要扩展是返回一个额外的访问Token,叫ID Token。这个ID Token是JSON Web Token(JWT)。

在请求中,使用ID Token作为bearer token(而不是 access_token)。

要启用此插件,需要设置下面两个参数:

该方式通过OAuth2服务的公有密钥对JWT中Signature校验实现身份认证。

Webhook Token Authentication

该认证方式类似于Static Token File,但它提供扩展Token身份验证的能力,具体的Token合法性验证由外部系统完成,Kubernetes平台将不再管理或配置具体的用户信息。

要启用此插件,需要设置下面两个参数:

  • –authentication-token-webhook-config-file 一个kubeconfig文件描述如何访问远程webhook服务。

  • –authentication-token-webhook-cache-ttl 用于设置缓存时间,默认为两分钟。

当客户端尝试使用承载令牌向API服务器进行身份验证时,身份验证webhook将包含令牌的JSON序列化对象POST 发送到远程服务。

image-20210122085309532

POST正文将采用以下格式:

1
2
3
4
5
6
7
{
"apiVersion": "authentication.k8s.io/v1beta1",
"kind": "TokenReview",
"spec": {
"token": "(BEARERTOKEN)"
}
}

认证成功将返回:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{
"apiVersion": "authentication.k8s.io/v1beta1",
"kind": "TokenReview",
"status": {
"authenticated": true,
"user": {
"username": "ms@mifpay.com",
"uid": "42",
"groups": [
"quantai",
"ms"
],
"extra": {
"extrafield1": [
"extravalue1",
"extravalue2"
]
}
}
}
}

认证不成功将返回:

1
2
3
4
5
6
7
{
"apiVersion": "authentication.k8s.io/v1beta1",
"kind": "TokenReview",
"status": {
"authenticated": false
}
}

X509 Client Certs

双向TLS协议

基础知识

公钥密码体制

公钥密码体制分为三个部分,公钥、私钥、加密解密算法,它的加密解密过程如下:

  • 加密:通过加密算法和公钥对内容(或者说明文)进行加密,得到密文。加密过程需要用到公钥。
  • 解密:通过解密算法和私钥对密文进行解密,得到明文。解密过程需要用到解密算法和私钥。注意,由公钥加密的内容,只能由私钥进行解密,也就是说,由公钥加密的内容,如果不知道私钥,是无法解密的。

公钥密码体制的公钥和算法都是公开的(这是为什么叫公钥密码体制的原因),私钥是保密的。大家都以使用公钥进行加密,但是只有私钥的持有者才能解密。在实际的使用中,有需要的人会生成一对公钥和私钥,把公钥发布出去给别人使用,自己保留私钥。

对称加密算法

在对称加密算法中,加密使用的密钥和解密使用的密钥是相同的。也就是说,加密和解密都是使用的同一个密钥。因此对称加密算法要保证安全性的话,密钥要做好保密,只能让使用的人知道,不能对外公开。这个和上面的公钥密码体制有所不同,公钥密码体制中加密是用公钥,解密使用私钥,而对称加密算法中,加密和解密都是使用同一个密钥,不区分公钥和私钥。

密钥,一般就是一个字符串或数字,在加密或者解密时传递给加密/解密算法。前面在公钥密码体制中说到的公钥、私钥就是密钥,公钥是加密使用的密钥,私钥是解密使用的密钥。

非对称加密算法

在非对称加密算法中,加密使用的密钥和解密使用的密钥是不相同的。前面所说的公钥密码体制就是一种非对称加密算法,他的公钥和是私钥是不能相同的,也就是说加密使用的密钥和解密使用的密钥不同,因此它是一个非对称加密算法。

RSA简介

RSA密码体制是一种公钥密码体制,公钥公开,私钥保密,它的加密解密算法是公开的。

由公钥加密的内容可以并且只能由私钥进行解密,并且由私钥加密的内容可以并且只能由公钥进行解密。也就是说,RSA的这一对公钥、私钥都可以用来加密和解密,并且一方加密的内容可以由并且只能由对方进行解密

签名和加密

我们说加密,是指对某个内容加密,加密后的内容还可以通过解密进行还原。比如我们把一封邮件进行加密,加密后的内容在网络上进行传输,接收者在收到后,通过解密可以还原邮件的真实内容。

这里主要解释一下签名,签名就是在信息的后面再加上一段内容,可以证明信息没有被修改过,怎么样可以达到这个效果呢?一般是对信息做一个hash计算得到一个hash值,注意,这个过程是不可逆的,也就是说无法通过hash值得出原来的信息内容。在把信息发送出去时,把这个hash值加密后做为一个签名和信息一起发出去。接收方在收到信息后,会重新计算信息的hash值,并和信息所附带的hash值(解密后)进行对比,如果一致,就说明信息的内容没有被修改过,因为这里hash计算可以保证不同的内容一定会得到不同的hash值,所以只要内容一被修改,根据信息内容计算的hash值就会变化。当然,不怀好意的人也可以修改信息内容的同时也修改hash值,从而让它们可以相匹配,为了防止这种情况,hash值一般都会加密后(也就是签名)再和信息一起发送,以保证这个hash值不被修改。

一个加密通信过程的演化

来看一个例子,现在假设”服务器”和”客户”要在网络上通信,并且他们打算使用RSA(参看前面的RSA简介)来对通信进行加密以保证谈话内容的安全。由于是使用RSA这种公钥密码体制,”服务器”需要对外发布公钥(算法不需要公布,RSA的算法大家都知道),自己留着私钥。”客户”通过某些途径拿到了”服务器”发布的公钥,客户并不知道私钥。

下面看一下双方如何进行保密的通信:

第一回合

“客户”->”服务器”:你好

“服务器”->”客户”:你好,我是服务器

“客户”->”服务器”:????

因为消息是在网络上传输的,有人可以冒充自己是”服务器”来向客户发送信息。例如上面的消息可以被黑客截获如下:


“客户”->”服务器”:你好

“服务器”->”客户”:你好,我是服务器

“客户”->”黑客”:你好 // 黑客在”客户”和”服务器”之间的某个路由器上截获”客户”发给服务器的信息,然后自己冒充”服务器”

“黑客”->”客户”:你好,我是服务器

因此”客户”在接到消息后,并不能肯定这个消息就是由”服务器”发出的,某些”黑客”也可以冒充”服务器”发出这个消息。如何确定信息是由”服务器”发过来的呢?有一个解决方法,因为只有服务器有私钥,所以如果只要能够确认对方有私钥,那么对方就是”服务器”。因此通信过程可以改进为如下

第二回合

“客户”->”服务器”:你好

“服务器”->”客户”:你好,我是服务器

“客户”->”服务器”:向我证明你就是服务器

“服务器”->”客户”:你好,我是服务器 {你好,我是服务器}[私钥|RSA]

注意这里约定一下,{}表示RSA加密后的内容,[ | ]表示用什么密钥和算法进行加密,后面的示例中都用这种表示方式,例如上面的{你好,我是服务器}[私钥|RSA] 就表示用私钥对”你好,我是服务器”进行加密后的结果。

为了向”客户”证明自己是”服务器”, “服务器”把一个字符串用自己的私钥加密,把明文和加密后的密文一起发给”客户”。对于这里的例子来说,就是把字符串 “你好,我是服务器”和这个字符串用私钥加密后的内容 {你好,我是服务器}[私钥|RSA] 发给客户。

“客户”收到信息后,用自己持有的公钥解密密文,和明文进行对比,如果一致,说明信息的确是由服务器发过来的。也就是说”客户”把 {你好,我是服务器}[私钥|RSA] 这个内容用公钥进行解密,然后和”你好,我是服务器”对比。因为由”服务器”用私钥加密后的内容,由并且只能由公钥进行解密,私钥只有”服务器”持有,所以如果解密出来的内容是能够对得上的,那说明信息一定是从”服务器”发过来的。

假设”黑客”想冒充”服务器”:

“黑客”->”客户”:你好,我是服务器

“客户”->”黑客”:向我证明你就是服务器

“黑客”->”客户”:你好,我是服务器 {你好,我是服务器}[???|RSA]** //这里黑客无法冒充,因为他不知道私钥,无法用私钥加密某个字符串后发送给客户去验证。

“客户”->”黑客”:????

由于”黑客”没有”服务器”的私钥,因此它发送过去的内容,”客户”是无法通过服务器的公钥解密的,因此可以认定对方是个冒牌货!

到这里为止,”客户”就可以确认”服务器”的身份了,可以放心和”服务器”进行通信,但是这里有一个问题,通信的内容在网络上还是无法保密。为什么无法保密呢?通信过程不是可以用公钥、私钥加密吗?其实用RSA的私钥和公钥是不行的,我们来具体分析下过程,看下面的演示:

第三回合

“客户”->”服务器”:你好

“服务器”->”客户”:你好,我是服务器

“客户”->”服务器”:向我证明你就是服务器

“服务器”->”客户”:你好,我是服务器 {你好,我是服务器}[私钥|RSA]

“客户”->”服务器”:{我的帐号是aaa,密码是123,把我的余额的信息发给我看看}[公钥|RSA]

“服务器”->”客户”:{你的余额是100元}[私钥|RSA]

注意上面的的信息 {你的余额是100元}[私钥],这个是”服务器”用私钥加密后的内容,但是我们之前说了,公钥是发布出去的,因此所有的人都知道公钥,所以除了”客户”,其它的人也可以用公钥对{你的余额是100元}[私钥]进行解密。所以如果”服务器”用私钥加密发给”客户”,这个信息是无法保密的,因为只要有公钥就可以解密这内容。然而”服务器”也不能用公钥对发送的内容进行加密,因为”客户”没有私钥,发送个”客户”也解密不了。

这样问题就又来了,那又如何解决呢?在实际的应用过程,一般是通过引入对称加密来解决这个问题,看下面的演示:

第四回合

“客户”->”服务器”:你好

“服务器”->”客户”:你好,我是服务器

“客户”->”服务器”:向我证明你就是服务器

“服务器”->”客户”:你好,我是服务器 {你好,我是服务器}[私钥|RSA]

“客户”->”服务器”:{我们后面的通信过程,用对称加密来进行,这里是对称加密算法和密钥}[公钥|RSA] //蓝色字体的部分是对称加密的算法和密钥的具体内容,客户把它们发送给服务器。

“服务器”->”客户”:{OK,收到!}[密钥|对称加密算法]

“客户”->”服务器”:{我的帐号是aaa,密码是123,把我的余额的信息发给我看看}[密钥|对称加密算法]

“服务器”->”客户”:{你的余额是100元}[密钥|对称加密算法]

在上面的通信过程中,”客户”在确认了”服务器”的身份后,”客户”自己选择一个对称加密算法和一个密钥,把这个对称加密算法和密钥一起用公钥加密后发送给”服务器”。注意,由于对称加密算法和密钥是用公钥加密的,就算这个加密后的内容被”黑客”截获了,由于没有私钥,”黑客”也无从知道对称加密算法和密钥的内容。

由于是用公钥加密的,只有私钥能够解密,这样就可以保证只有服务器可以知道对称加密算法和密钥,而其它人不可能知道(这个对称加密算法和密钥是”客户”自己选择的,所以”客户”自己当然知道如何解密加密)。这样”服务器”和”客户”就可以用对称加密算法和密钥来加密通信的内容了。

总结一下,RSA加密算法在这个通信过程中所起到的作用主要有两个:

  • 因为私钥只有”服务器”拥有,因此”客户”可以通过判断对方是否有私钥来判断对方是否是”服务器”。
  • 客户端通过RSA的掩护,安全的和服务器商量好一个对称加密算法和密钥来保证后面通信过程内容的安全。

到这里,”客户”就可以确认”服务器”的身份,并且双方的通信内容可以进行加密,其他人就算截获了通信内容,也无法解密。的确,好像通信的过程是比较安全了。

但是这里还留有一个问题,在最开始我们就说过,”服务器”要对外发布公钥,那”服务器”如何把公钥发送给”客户”呢?我们第一反应可能会想到以下的两个方法:

a)把公钥放到互联网的某个地方的一个下载地址,事先给”客户”去下载。

b)每次和”客户”开始通信时,”服务器”把公钥发给”客户”。

但是这个两个方法都有一定的问题,

对于a)方法,”客户”无法确定这个下载地址是不是”服务器”发布的,你凭什么就相信这个地址下载的东西就是”服务器”发布的而不是别人伪造的呢,万一下载到一个假的怎么办?另外要所有的”客户”都在通信前事先去下载公钥也很不现实。

对于b)方法,也有问题,因为任何人都可以自己生成一对公钥和私钥,他只要向”客户”发送他自己的私钥就可以冒充”服务器”了。示意如下:

“客户”->”黑客”:你好 //黑客截获”客户”发给”服务器”的消息

“黑客”->”客户”:你好,我是服务器,这个是我的公钥 //黑客自己生成一对公钥和私钥,把公钥发给”客户”,自己保留私

“客户”->”黑客”:向我证明你就是服务器

“黑客”->”客户”:你好,我是服务器 {你好,我是服务器}[黑客自己的私钥|RSA] //客户收到”黑客”用私钥加密的信息后,是可以用”黑客”发给自己的公钥解密的,从而会误认为”黑客”是”服务器”

因此”黑客”只需要自己生成一对公钥和私钥,然后把公钥发送给”客户”,自己保留私钥,这样由于”客户”可以用黑客的公钥解密黑客的私钥加密的内容,”客户”就会相信”黑客”是”服务器”,从而导致了安全问题。这里问题的根源就在于,大家都可以生成公钥、私钥对,无法确认公钥对到底是谁的。 如果能够确定公钥到底是谁的,就不会有这个问题了。例如,如果收到”黑客”冒充”服务器”发过来的公钥,经过某种检查,如果能够发现这个公钥不是”服务器”的就好了。

为了解决这个问题,数字证书出现了,它可以解决我们上面的问题。先大概看下什么是数字证书,一个证书包含下面的具体内容:

  • 证书的发布机构
  • 证书的有效期
  • 公钥
  • 证书所有者(Subject)
  • 签名所使用的算法
  • 指纹以及指纹算法

证书的内容的详细解释会在后面详细解释,这里先只需要搞清楚一点,数字证书可以保证数字证书里的公钥确实是这个证书的所有者(Subject)的,或者证书可以用来确认对方的身份。也就是说,我们拿到一个数字证书,我们可以判断出这个数字证书到底是谁的。至于是如何判断的,后面会在详细讨论数字证书时详细解释。现在把前面的通信过程使用数字证书修改为如下:

第五回合

“客户”->”服务器”:你好

“服务器”->”客户”:你好,我是服务器,这里是我的数字证书 //这里用证书代替了公钥

“客户”->”服务器”:向我证明你就是服务器

“服务器”->”客户”:你好,我是服务器 {你好,我是服务器}[私钥|RSA]

注意,上面第二次通信,”服务器”把自己的证书发给了”客户”,而不是发送公钥。”客户”可以根据证书校验这个证书到底是不是”服务器”的,也就是能校验这个证书的所有者是不是”服务器”,从而确认这个证书中的公钥的确是”服务器”的。后面的过程和以前是一样,”客户”让”服务器”证明自己的身份,”服务器”用私钥加密一段内容连同明文一起发给”客户”,”客户”把加密内容用数字证书中的公钥解密后和明文对比,如果一致,那么对方就确实是”服务器”,然后双方协商一个对称加密来保证通信过程的安全。到这里,整个过程就完整了,我们回顾一下:

完整过程

step1: “客户”向服务端发送一个通信请求

“客户”->”服务器”:你好

step2: “服务器”向客户发送自己的数字证书。证书中有一个公钥用来加密信息,私钥由”服务器”持有

“服务器”->”客户”:你好,我是服务器,这里是我的数字证书

step3: “客户”收到”服务器”的证书后,它会去验证这个数字证书到底是不是”服务器”的,数字证书有没有什么问题,数字证书如果检查没有问题,就说明数字证书中的公钥确实是”服务器”的。检查数字证书后,”客户”会发送一个随机的字符串给”服务器”用私钥去加密,服务器把加密的结果返回给”客户”,”客户”用公钥解密这个返回结果,如果解密结果与之前生成的随机字符串一致,那说明对方确实是私钥的持有者,或者说对方确实是”服务器”。

“客户”->”服务器”:向我证明你就是服务器,这是一个随机字符串 //前面的例子中为了方便解释,用的是”你好”等内容,实际情况下一般是随机生成的一个字符串。

“服务器”->”客户”:{一个随机字符串}[私钥|RSA]

step4: 验证”服务器”的身份后,”客户”生成一个对称加密算法和密钥,用于后面的通信的加密和解密。这个对称加密算法和密钥,”客户”会用公钥加密后发送给”服务器”,别人截获了也没用,因为只有”服务器”手中有可以解密的私钥。这样,后面”服务器”和”客户”就都可以用对称加密算法来加密和解密通信内容了。

“服务器”->”客户”:{OK,已经收到你发来的对称加密算法和密钥!有什么可以帮到你的?}[密钥|对称加密算法]

“客户”->”服务器”:{我的帐号是aaa,密码是123,把我的余额的信息发给我看看}[密钥|对称加密算法]

“服务器”->”客户”:{你好,你的余额是100元}[密钥|对称加密算法]

…… //继续其它的通信

其他问题

【问题1】

上面的通信过程中说到,在检查完证书后,”客户”发送一个随机的字符串给”服务器”去用私钥加密,以便判断对方是否真的持有私钥。但是有一个问题,”黑客”也可以发送一个字符串给”服务器”去加密并且得到加密后的内容,这样对于”服务器”来说是不安全的,因为黑客可以发送一些简单的有规律的字符串给”服务器”加密,从而寻找加密的规律,有可能威胁到私钥的安全。所以说,”服务器”随随便便用私钥去加密一个来路不明的字符串并把结果发送给对方是不安全的。

〖解决方法〗

每次收到”客户”发来的要加密的的字符串时,”服务器”并不是真正的加密这个字符串本身,而是把这个字符串进行一个hash计算,加密这个字符串的hash值(不加密原来的字符串)后发送给”客户”,”客户”收到后解密这个hash值并自己计算字符串的hash值然后进行对比是否一致。也就是说,”服务器”不直接加密收到的字符串,而是加密这个字符串的一个hash值,这样就避免了加密那些有规律的字符串,从而降低被破解的机率。”客户”自己发送的字符串,因此它自己可以计算字符串的hash值,然后再把”服务器”发送过来的加密的hash值和自己计算的进行对比,同样也能确定对方是否是”服务器”。

【问题2】

在双方的通信过程中,”黑客”可以截获发送的加密了的内容,虽然他无法解密这个内容,但是他可以捣乱,例如把信息原封不动的发送多次,扰乱通信过程。

〖解决方法〗

可以给通信的内容加上一个序号或者一个随机的值,如果”客户”或者”服务器”接收到的信息中有之前出现过的序号或者随机值,那么说明有人在通信过程中重发信息内容进行捣乱,双方会立刻停止通信。有人可能会问,如果有人一直这么捣乱怎么办?那不是无法通信了? 答案是的确是这样的,例如有人控制了你连接互联网的路由器,他的确可以针对你。但是一些重要的应用,例如军队或者政府的内部网络,它们都不使用我们平时使用的公网,因此一般人不会破坏到他们的通信。

【问题3】

在双方的通信过程中,”黑客”除了简单的重复发送截获的消息之外,还可以修改截获后的密文修改后再发送,因为修改的是密文,虽然不能完全控制消息解密后的内容,但是仍然会破坏解密后的密文。因此发送过程如果黑客对密文进行了修改,”客户”和”服务器”是无法判断密文是否被修改的。虽然不一定能达到目的,但是”黑客”可以一直这样碰碰运气。

〖解决方法〗

在每次发送信息时,先对信息的内容进行一个hash计算得出一个hash值,将信息的内容和这个hash值一起加密后发送。接收方在收到后进行解密得到明文的内容和hash值,然后接收方再自己对收到信息内容做一次hash计算,与收到的hash值进行对比看是否匹配,如果匹配就说明信息在传输过程中没有被修改过。如果不匹配说明中途有人故意对加密数据进行了修改,立刻中断通话过程后做其它处理。


以上是单向SSL认证的内容

双向认证 SSL 协议要求服务器和用户双方都有证书。单向认证 SSL 协议不需要客户拥有CA证书,具体的过程相对于上面的步骤,只需将服务器端验证客户证书的过程去掉,以及在协商对称密码方案,对称通话密钥时,服务器发送给客户的是没有加过密的(这并不影响 SSL 过程的安全性)密码方案。这样,双方具体的通讯内容,就是加过密的数据,如果有第三方攻击,获得的只是加密的数据,第三方要获得有用的信息,就需要对加密的数据进行解密,这时候的安全就依赖于密码方案的安全。而幸运的是,目前所用的密码方案,只要通讯密钥长度足够的长,就足够的安全。这也是我们强调要求使用128位加密通讯的原因。
一般Web应用都是采用SSL单向认证的,原因很简单,用户数目广泛,且无需在通讯层对用户身份进行验证,一般都在应用逻辑层来保证用户的合法登入。但如果是企业应用对接,情况就不一样,可能会要求对客户端(相对而言)做身份验证。这时就需要做SSL双向认证。

3437040-88fdfaf70a49c1fb

①客户端的浏览器向服务器传送客户端SSL协议的版本号,加密算法的种类,产生的随机数,以及其他服务器和客户端之间通讯所需要的各种信息。

②服务器向客户端传送SSL协议的版本号,加密算法的种类,随机数以及其他相关信息,同时服务器还将向客户端传送自己的证书。

③客户利用服务器传过来的信息验证服务器的合法性,服务器的合法性包括:证书是否过期,发行服务器证书的CA是否可靠,发行者证书的公钥能否正确解开服务器证书的”发行者的数字签名”,服务器证书上的域名是否和服务器的实际域名相匹配。如果合法性验证没有通过,通讯将断开;如果合法性验证通过,将继续进行第四步。

④用户端随机产生一个用于后面通讯的”对称密码”,然后用服务器的公钥(服务器的公钥从步骤②中的服务器的证书中获得)对其加密,然后将加密后的”预主密码”传给服务器。

⑤如果服务器要求客户的身份认证(在握手过程中为可选),用户可以建立一个随机数然后对其进行数据签名,将这个含有签名的随机数和客户自己的证书以及加密过的”预主密码”一起传给服务器。

⑥如果服务器要求客户的身份认证,服务器必须检验客户证书和签名随机数的合法性,具体的合法性验证过程包括:客户的证书使用日期是否有效,为客户提供证书的CA是否可靠,发行CA 的公钥能否正确解开客户证书的发行CA的数字签名,检查客户的证书是否在证书废止列表(CRL)中。检验如果没有通过,通讯立刻中断;如果验证通过,服务器将用自己的私钥解开加密的”预主密码 “,然后执行一系列步骤来产生主通讯密码(客户端也将通过同样的方法产生相同的主通讯密码)。

⑦服务器和客户端用相同的主密码即”通话密码”,一个对称密钥用于SSL协议的安全数据通讯的加解密通讯。同时在SSL通讯过程中还要完成数据通讯的完整性,防止数据通讯中的任何变化。

⑧客户端向服务器端发出信息,指明后面的数据通讯将使用的步骤⑦中的主密码为对称密钥,同时通知服务器客户端的握手过程结束。

⑨服务器向客户端发出信息,指明后面的数据通讯将使用的步骤⑦中的主密码为对称密钥,同时通知客户端服务器端的握手过程结束。

⑩SSL的握手部分结束,SSL安全通道的数据通讯开始,客户和服务器开始使用相同的对称密钥进行数据通讯,同时进行通讯完整性的检验。

3437040-5e8d79e001ff5118

① 浏览器发送一个连接请求给安全服务器。

② 服务器将自己的证书,以及同证书相关的信息发送给客户浏览器。

③ 客户浏览器检查服务器送过来的证书是否是由自己信赖的CA中心(如沃通CA)所签发的。如果是,就继续执行协议;如果不是,客户浏览器就给客户一个警告消息:警告客户这个证书不是可以信赖的,询问客户是否需要继续。

④ 接着客户浏览器比较证书里的消息,例如域名和公钥,与服务器刚刚发送的相关消息是否一致,如果是一致的,客户浏览器认可这个服务器的合法身份。

⑤ 服务器要求客户发送客户自己的证书。收到后,服务器验证客户的证书,如果没有通过验证,拒绝连接;如果通过验证,服务器获得用户的公钥。

⑥ 客户浏览器告诉服务器自己所能够支持的通讯对称密码方案。

⑦ 服务器从客户发送过来的密码方案中,选择一种加密程度最高的密码方案,用客户的公钥加过密后通知浏览器。

⑧ 浏览器针对这个密码方案,选择一个通话密钥,接着用服务器的公钥加过密后发送给服务器。

⑨ 服务器接收到浏览器送过来的消息,用自己的私钥解密,获得通话密钥。

⑩ 服务器、浏览器接下来的通讯都是用对称密码方案,对称密钥是加过密的。

SSL双向认证步骤:

  1. HTTPS通信双方的服务器端向CA机构申请证书,CA机构是可信的第三方机构,它可以是一个公认的权威的企业,也可以是企业自身。企业内部系统一般都使用企业自身的认证系统。CA机构下发根证书、服务端证书及私钥给申请者;
  2. HTTPS通信双方的客户端向CA机构申请证书,CA机构下发根证书、客户端证书及私钥个申请者;
  3. 客户端向服务器端发起请求,服务端下发服务端证书给客户端。客户端接收到证书后,通过私钥解密证书,并利用服务器端证书中的公钥认证证书信息比较证书里的消息,例如域名和公钥与服务器刚刚发送的相关消息是否一致,如果一致,则客户端认为这个服务器的合法身份;
  4. 客户端发送客户端证书给服务器端,服务端接收到证书后,通过私钥解密证书,获得客户端的证书公钥,并用该公钥认证证书信息,确认客户端是否合法;
  5. 客户端通过随机秘钥加密信息,并发送加密后的信息给服务端。服务器端和客户端协商好加密方案后,客户端会产生一个随机的秘钥,客户端通过协商好的加密方案,加密该随机秘钥,并发送该随机秘钥到服务器端。服务器端接收这个秘钥后,双方通信的所有内容都都通过该随机秘钥加密;

cfssl命令

cfssl 是 CloudFlare 开源的一款PKI/TLS工具。 CFSSL 包含一个命令行工具(cfssl, cfssljson)用于签名,验证并且捆绑TLS证书的 HTTP API 服务。 使用Go语言编写。与 OpenSSL 相比,cfssl 使用起来更简单。

大部分 cfssl 的输出为 JSON 格式。cfssljson 可以将输出拆分出来为独立的key,certificate,CSR 和 bundle文件。该工具需要指定参数,-f 指定输入文件,后接一个参数,指定生成的文件的基本名称。如果输入文件名是 -(默认值),则 cfssljson 从标准输入读取。它以下列方式将 JSON 文件中的键映射到文件名:

  • 如果指定了cert或certificate, 将生成basename.pem。
  • 如果指定了key 或private_key,则将生成basename-key.pem。
  • 如果指定了csr 或certificate_request,则将生成basename.csr。
  • 如果 指定了bundle, 则将生成basename-bundle.pem。
  • 如果指定了ocspResponse, 则将生成basename-response.der。
    您可以传递-stdout输出编码内容到标准输出,而不是保存到文件。

验证证书有效期

1
2
3
4
cfssl certinfo -cert /etc/kubernetes/ssl/ca.pem |grep not_after
cfssl certinfo -cert /etc/kubernetes/ssl/admin.pem |grep not_after
cfssl certinfo -cert /etc/kubernetes/ssl/kubernetes.pem |grep not_after
cfssl certinfo -cert /etc/kubernetes/ssl/kube-proxy.pem |grep not_after

生成新证书

证书分四类

  • ca.pem - 私有CA根证书
  • kubernetes.pem - 与 node 通信的,
  • kube-proxy.pem - k8s 与容器通信的
  • admin.pem - kubectl 管理用

在生成证书过程中需要有四类文件

  • *.csr - 证书请求文件,base64格式,有-----BEGIN CERTIFICATE REQUEST-----标识
  • *csr.json - 证书请求文件,是上面格式的再封装,便于传给cfssl,json格式,大括号开始
  • *-key.pem - 私匙文件,base64格式,有-----BEGIN RSA PRIVATE KEY-----标识
  • *.pem - 证书文件,base64格式,可以用cfssl certinfo -cert 文件名查看有效期,有-----BEGIN CERTIFICATE-----标识

以上四种文件,前两类是中间产物,过后可以不保留,后两个需要配置到系统中 (通常是主从结点的/etc/kubernetes/ssl目录下)。

创建CA证书

创建证书配置文件

vim ca-config.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
"signing": {
"default": {
"expiry": "87600h"
},
"profiles": {
"kubernetes": {
"usages": [
"signing",
"key encipherment",
"server auth",
"client auth"
],
"expiry": "87600h"
}
}
}
}

字段说明:

ca-config.json:可以定义多个 profiles,分别指定不同的过期时间、使用场景等参数;后续在签名证书时使用某个 profile;

signing:表示该证书可以签名其他证书;生成的ca.pem证书中 CA=TRUE;

server auth:表示client可以用该 CA 对server提供的证书进行验证;

client auth:表示server可以用该CA对client提供的证书进行验证;

expiry:过期时间

创建CA证书签名请求文件

vim ca-csr.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
"CN": "kubernetes",
"key": {
"algo": "rsa",
"size": 2048
},
"names": [
{
"C": "CN",
"ST": "Jiangsu",
"L": "Wuxi",
"O": "k8s",
"OU": "System"
}
],
"ca": {
"expiry": "87600h"
}
}

字段说明:

“CN”:Common Name,kube-apiserver 从证书中提取该字段作为请求的用户名 (User Name);浏览器使用该字段验证网站是否合法;

“O”:Organization,kube-apiserver 从证书中提取该字段作为请求用户所属的组 (Group)

生成CA证书和私钥

1
2
3
4
5
6
7
cfssl gencert -initca ca-csr.json | cfssljson -bare ca
ls | grep ca
ca-config.json
ca.csr
ca-csr.json
ca-key.pem
ca.pem

其中ca-key.pem是ca的私钥,ca.csr是一个签署请求,ca.pem是CA证书,是后面kubernetes组件会用到的RootCA。

创建kubernetes证书

在创建这个证书之前,先规划一下架构

1
2
3
4
5
6
7
k8s-master1 10.211.55.11
k8s-master2 10.211.55.12
k8s-master3 10.211.55.13
etcd01 10.211.55.11
etcd02 10.211.55.12
etcd03 10.211.55.13
VIP 10.211.55.8

创建kubernetes证书签名请求文件

vim kubernetes-csr.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
{
"CN": "kubernetes",
"hosts": [
"127.0.0.1",
"10.211.55.11",
"10.211.55.12",
"10.211.55.13",
"10.211.55.8",
"10.0.0.1",
"k8s-master1",
"k8s-master2",
"k8s-master3",
"etcd01",
"etcd02",
"etcd03",
"kubernetes",
"kubernetes.default",
"kubernetes.default.svc",
"kubernetes.default.svc.cluster",
"kubernetes.default.svc.cluster.local"
],
"key": {
"algo": "rsa",
"size": 2048
},
"names": [
{
"C": "CN",
"ST": "Jiangsu",
"L": "Wuxi",
"O": "k8s",
"OU": "System"
}
]
}

字段说明:

如果 hosts 字段不为空则需要指定授权使用该证书的 IP 或域名列表。

由于该证书后续被 etcd 集群和 kubernetes master 集群使用,将etcd、master节点的IP都填上,同时还有service网络的首IP。(一般是 kube-apiserver 指定的 service-cluster-ip-range 网段的第一个IP,如 10.0.0.1)

三个etcd,三个master,以上物理节点的IP也可以更换为主机名。

生成kubernetes证书和私钥

1
2
3
4
5
6
cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json  -profile=kubernetes kubernetes-csr.json | cfssljson -bare kubernetes
ls |grep kubernetes
kubernetes.csr
kubernetes-csr.json
kubernetes-key.pem
kubernetes.pem

创建admin证书

创建admin证书签名请求文件

vim admin-csr.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"CN": "admin",
"hosts": [],
"key": {
"algo": "rsa",
"size": 2048
},
"names": [
{
"C": "CN",
"ST": "Jiangsu",
"L": "Wuxi",
"O": "system:masters",
"OU": "System"
}
]
}

说明:

后续 kube-apiserver 使用 RBAC 对客户端(如 kubelet、kube-proxy、Pod)请求进行授权;

kube-apiserver 预定义了一些 RBAC 使用的 RoleBindings,如 cluster-admin 将 Group system:masters 与 Role cluster-admin 绑定,该 Role 授予了调用kube-apiserver 的所有 API的权限;

O指定该证书的 Group 为 system:masters,kubelet 使用该证书访问 kube-apiserver 时 ,由于证书被 CA 签名,所以认证通过,同时由于证书用户组为经过预授权的 system:masters,所以被授予访问所有 API 的权限;

注:这个admin 证书,是将来生成管理员用的kube config 配置文件用的,现在我们一般建议使用RBAC 来对kubernetes 进行角色权限控制, kubernetes 将证书中的CN 字段 作为User, O 字段作为 Group

相关权限认证可以参考下面文章

https://mp.weixin.qq.com/s/XIkQdh5gnr-KJhuFHboNag

生成admin证书和私钥

1
2
3
4
5
6
cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=kubernetes admin-csr.json | cfssljson -bare admin
ls | grep admin
admin.csr
admin-csr.json
admin-key.pem
admin.pem

创建kube-proxy证书

创建 kube-proxy 证书签名请求文件

vim kube-proxy-csr.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"CN": "system:kube-proxy",
"hosts": [],
"key": {
"algo": "rsa",
"size": 2048
},
"names": [
{
"C": "CN",
"ST": "Jiangsu",
"L": "Wuxi",
"O": "k8s",
"OU": "System"
}
]
}

说明:

CN 指定该证书的 User 为 system:kube-proxy;

kube-apiserver 预定义的 RoleBinding system:node-proxier 将User system:kube-proxy 与 Role system:node-proxier 绑定,该 Role 授予了调用 kube-apiserver Proxy 相关 API 的权限;

该证书只会被 kubectl 当做 client 证书使用,所以 hosts 字段为空

生成kube-proxy证书和私钥

1
2
3
4
5
6
cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json  -profile=kubernetes kube-proxy-csr.json | cfssljson -bare kube-proxy
ls |grep kube-proxy
kube-proxy.csr
kube-proxy-csr.json
kube-proxy-key.pem
kube-proxy.pem

创建kube-controoler-manager证书

创建 kube-controoler-manager 证书签名请求文件

vim kube-controller-manager-csr.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
{
"CN": "system:kube-controller-manager",
"key": {
"algo": "rsa",
"size": 2048
},
"hosts": [
"127.0.0.1",
"10.211.55.11",
"10.211.55.12",
"10.211.55.13",
"k8s-master1",
"k8s-master2",
"k8s-master3"
],
"names": [
{
"C": "CN",
"ST": "BeiJing",
"L": "BeiJing",
"O": "system:kube-controller-manager",
"OU": "system"
}
]
}

说明:

hosts 列表包含所有 kube-controller-manager 节点 IP;

CN 为 system:kube-controller-manager、O 为 system:kube-controller-manager,kubernetes 内置的 ClusterRoleBindings system:kube-controller-manager 赋予 kube-controller-manager 工作所需的权限

生成kube-controoller-manager证书和私钥

1
cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json  -profile=kubernetes kube-controller-manager-csr.json | cfssljson -bare  kube-controller-manager 

创建kube-scheduler证书

创建 kube-scheduler 证书签名请求文件

vim kube-scheduler-csr.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
{
"CN": "system:kube-scheduler",
"hosts": [
"127.0.0.1",
"10.211.55.11",
"10.211.55.12",
"10.211.55.13",
"k8s-master1",
"k8s-master2",
"k8s-master3",
],
"key": {
"algo": "rsa",
"size": 2048
},
"names": [
{
"C": "CN",
"ST": "Jiangsu",
"L": "Wuxi",
"O": "system:kube-scheduler",
"OU": "4Paradigm"
}
]
}

说明:

hosts 列表包含所有 kube-scheduler 节点 IP;

CN 为 system:kube-scheduler、O 为 system:kube-scheduler,kubernetes 内置的 ClusterRoleBindings system:kube-scheduler 将赋予 kube-scheduler 工作所需的权限。

经过上述操作,我们会用到如下文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json  -profile=kubernetes kube-scheduler-csr.json| cfssljson -bare  kube-scheduler
ls | grep pem
admin-key.pem
admin.pem
ca-key.pem
ca.pem
kube-proxy-key.pem
kube-proxy.pem
kubernetes-key.pem
kubernetes.pem
kube-controller-manager-key.pem
kube-controller-manager.pem
kube-scheduler-key.pem
kube-scheduler.pem

查看证书信息:

1
cfssl-certinfo -cert kubernetes.pem

k8s的认证

在 Kubernetes 中,各个组件提供的接口中包含了集群的内部信息。如果这些接口被非法访问,将影响集群的安全,因此组件之间的通信需要采用双向 TLS 认证。即客户端和服务器端都需要验证对方的身份信息。在两个组件进行双向认证时,会涉及到下面这些证书相关的文件:

  • 服务器端证书:服务器用于证明自身身份的数字证书,里面主要包含了服务器端的公钥以及服务器的身份信息。
  • 服务器端私钥:服务器端证书中包含的公钥所对应的私钥。公钥和私钥是成对使用的,在进行 TLS 验证时,服务器使用该私钥来向客户端证明自己是服务器端证书的拥有者。
  • 客户端证书:客户端用于证明自身身份的数字证书,里面主要包含了客户端的公钥以及客户端的身份信息。
  • 客户端私钥:客户端证书中包含的公钥所对应的私钥,同理,客户端使用该私钥来向服务器端证明自己是客户端证书的拥有者。
  • 服务器端 CA 根证书:签发服务器端证书的 CA 根证书,客户端使用该 CA 根证书来验证服务器端证书的合法性。
  • 客户端端 CA 根证书:签发客户端证书的 CA 根证书,服务器端使用该 CA 根证书来验证客户端证书的合法性。

Kubernetes 中使用到的CA和证书

Kubernetes 中使用了大量的证书,本文不会试图覆盖到所有可能使用到的证书,但会讨论到主要的证书。理解了这些证书的使用方法和原理后,也能很快理解其他可能遇到的证书文件。下图标识出了在 kubernetes 中主要使用到的证书和其使用的位置:

kubernetes-certificate-usage

上图中使用序号对证书进行了标注。图中的箭头表明了组件的调用方向,箭头所指方向为服务提供方,另一头为服务调用方。为了实现 TLS 双向认证,服务提供方需要使用一个服务器证书,服务调用方则需要提供一个客户端证书,并且双方都需要使用一个 CA 证书来验证对方提供的证书。为了简明起见,上图中只标注了证书使用方提供的证书,并没有标注证书的验证方验证使用的 CA 证书。图中标注的这些证书的作用分别如下:

  1. etcd 集群中各个节点之间相互通信使用的证书。由于一个 etctd 节点既为其他节点提供服务,又需要作为客户端访问其他节点,因此该证书同时用作服务器证书和客户端证书。
  2. etcd 集群向外提供服务使用的证书。该证书是服务器证书。
  3. kube-apiserver 作为客户端访问 etcd 使用的证书。该证书是客户端证书。
  4. kube-apiserver 对外提供服务使用的证书。该证书是服务器证书。
  5. kube-controller-manager 作为客户端访问 kube-apiserver 使用的证书,该证书是客户端证书。
  6. kube-scheduler 作为客户端访问 kube-apiserver 使用的证书,该证书是客户端证书。
  7. kube-proxy 作为客户端访问 kube-apiserver 使用的证书,该证书是客户端证书。
  8. kubelet 作为客户端访问 kube-apiserver 使用的证书,该证书是客户端证书。
  9. 管理员用户通过 kubectl 访问 kube-apiserver 使用的证书,该证书是客户端证书。
  10. kubelet 对外提供服务使用的证书。该证书是服务器证书。
  11. kube-apiserver 作为客户端访问 kubelet 采用的证书。该证书是客户端证书。
  12. kube-controller-manager 用于生成和验证 service-account token 的证书。该证书并不会像其他证书一样用于身份认证,而是将证书中的公钥/私钥对用于 service account token 的生成和验证。kube-controller-manager 会用该证书的私钥来生成 service account token,然后以 secret 的方式加载到 pod 中。pod 中的应用可以使用该 token 来访问 kube-apiserver, kube-apiserver 会使用该证书中的公钥来验证请求中的 token。

通过这张图,对证书机制比较了解的读者可能已经看出,我们其实可以使用多个不同的 CA 来颁发这些证书。只要在通信的组件中正确配置用于验证对方证书的 CA 根证书,就可以使用不同的 CA 来颁发不同用途的证书。但我们一般建议采用统一的 CA 来颁发 kubernetes 集群中的所有证书,这是因为采用一个集群根 CA 的方式比采用多个 CA 的方式更容易管理,可以避免多个CA 导致的复杂的证书配置、更新等问题,减少由于证书配置错误导致的集群故障。

k8s中的证书配置

所有客户端的证书首先要经过集群CA的签署,否则不会被集群认可

etcd

需要在 etcd 的启动命令行中配置以下证书相关参数:

  • etcd 对外提供服务的服务器证书及私钥。
  • etcd 节点之间相互进行认证的 peer 证书、私钥以及验证 peer 的 CA。
  • etcd 验证访问其服务的客户端的 CA。
1
2
3
4
5
6
7
8
/usr/local/bin/etcd \\
--cert-file=/etc/etcd/kube-etcd.pem \\ # 对外提供服务的服务器证书
--key-file=/etc/etcd/kube-etcd-key.pem \\ # 服务器证书对应的私钥
--peer-cert-file=/etc/etcd/kube-etcd-peer.pem \\ # peer 证书,用于 etcd 节点之间的相互访问
--peer-key-file=/etc/etcd/kube-etcd-peer-key.pem \\ # peer 证书对应的私钥
--trusted-ca-file=/etc/etcd/cluster-root-ca.pem \\ # 用于验证访问 etcd 服务器的客户端证书的 CA 根证书
--peer-trusted-ca-file=/etc/etcd/cluster-root-ca.pem\\ # 用于验证 peer 证书的 CA 根证书
...
kubectl

kubectl只是个go编写的可执行程序,只要为kubectl配置合适的kubeconfig,就可以在集群中的任意节点使用 。kubectl的权限为admin,具有访问kubernetes所有api的权限。

证书名称 作用
ca.pem CA根证书
admin.pem kubectl的TLS认证证书,具有admin权限
admin-key.pem kubectl的TLS认证私钥
  • --certificate-authority=/etc/kubernetes/ssl/ca.pem 设置了该集群的根证书路径, --embed-certs为true表示将--certificate-authority证书写入到kubeconfig中
  • --client-certificate=/etc/kubernetes/ssl/admin.pem 指定kubectl证书
  • --client-key=/etc/kubernetes/ssl/admin-key.pem 指定kubectl私钥
kubelet
证书名称 作用
ca.pem CA根证书
kubelet-client.crt kubectl的TLS认证证书
kubelet-client.key kubectl的TLS认证私钥
kubelet.crt 独立于 apiserver CA 的自签 CA
kubelet.key 独立于 apiserver CA 的私钥

当成功签发证书后,目标节点的 kubelet 会将证书写入到 –cert-dir= 选项指定的目录中;此时如果不做其他设置应当生成上述除ca.pem以外的4个文件

  • kubelet-client.crt 该文件在 kubelet 完成 TLS bootstrapping 后生成,此证书是由 controller manager 签署的,此后 kubelet 将会加载该证书,用于与 apiserver 建立 TLS 通讯,同时使用该证书的 CN 字段作为用户名,O 字段作为用户组向 apiserver 发起其他请求
  • kubelet.crt 该文件在 kubelet 完成 TLS bootstrapping 后并且没有配置 –feature-gates=RotateKubeletServerCertificate=true 时才会生成;这种情况下该文件为一个独立于 apiserver CA 的自签 CA 证书,有效期为 1 年;被用作 kubelet 10250 api 端口
kube-apiserver

kube-apiserver是我们在部署kubernetes集群是最需要先启动的组件,也是我们和集群交互的核心组件。

需要在 kube-apiserver 中配置以下证书相关参数:

  • kube-apiserver 对外提供服务的服务器证书及私钥。
  • kube-apiserver 访问 etcd 所需的客户端证书及私钥。
  • kube-apiserver 访问 kubelet 所需的客户端证书及私钥。
  • 验证访问其服务的客户端的 CA。
  • 验证 etcd 服务器证书的 CA 根证书。
  • 验证 service account token 的公钥。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/usr/local/bin/kube-apiserver \\ 
--tls-cert-file=/var/lib/kubernetes/kube-apiserver.pem \\ # 用于对外提供服务的服务器证书
--tls-private-key-file=/var/lib/kubernetes/kube-apiserver-key.pem \\ # 服务器证书对应的私钥
--etcd-certfile=/var/lib/kubernetes/kube-apiserver-etcd-client.pem \\ # 用于访问 etcd 的客户端证书
--etcd-keyfile=/var/lib/kubernetes/kube-apiserver-etcd-client-key.pem \\ # 用于访问 etcd 的客户端证书的私钥
--kubelet-client-certificate=/var/lib/kubernetes/kube-apiserver-kubelet-client.pem \\ # 用于访问 kubelet 的客户端证书
--kubelet-client-key=/var/lib/kubernetes/kube-apiserver-kubelet-client-key.pem \\ # 用于访问 kubelet 的客户端证书的私钥
--client-ca-file=/var/lib/kubernetes/cluster-root-ca.pem \\ # 用于验证访问 kube-apiserver 的客户端的证书的 CA 根证书
--etcd-cafile=/var/lib/kubernetes/cluster-root-ca.pem \\ # 用于验证 etcd 服务器证书的 CA 根证书
--kubelet-certificate-authority=/var/lib/kubernetes/cluster-root-ca.pem \\ # 用于验证 kubelet 服务器证书的 CA 根证书
--service-account-key-file=/var/lib/kubernetes/service-account.pem \\ # 用于验证 service account token 的公钥
--token-auth-file=/etc/kubernetes/token.csv \\ # 用于kubelet组件第一次启动时没有证书如何连接 apiserver,
Token 和 apiserver 的 CA 证书被写入了 kubelet 所使用的 bootstrap.kubeconfig 配置文件中;这样在首次请求时,kubelet 使用 bootstrap.kubeconfig 中的 apiserver CA 证书来与 apiserver 建立 TLS 通讯,使用 bootstrap.kubeconfig 中的用户 Token 来向 apiserver 声明自己的 RBAC 授权身份
...

采用 kubeconfig 访问 kube-apiserver

Kubernetes 中的各个组件,包括kube-controller-mananger、kube-scheduler、kube-proxy、kubelet等,采用一个kubeconfig 文件中配置的信息来访问 kube-apiserver。该文件中包含了 kube-apiserver 的地址,验证 kube-apiserver 服务器证书的 CA 证书,自己的客户端证书和私钥等访问信息。

在一个使用 minikube 安装的集群中,生成的 kubeconfig 配置文件如下所示,这四个文件分别为 admin 用户, kube-controller-mananger、kubelet 和 kube-scheduler 的kubeconfig配置文件。

1
2
$ ls /etc/kubernetes/
admin.conf controller-manager.conf kubelet.conf scheduler.conf

打开controller-manager.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
apiVersion: v1
clusters:
- cluster:
# 用于验证 kube-apiserver 服务器证书的 CA 根证书
certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS…………………………
server: https://localhost:8443
name: kubernetes
contexts:
- context:
cluster: kubernetes
user: system:kube-controller-manager
name: system:kube-controller-manager@kubernetes
current-context: system:kube-controller-manager@kubernetes
kind: Config
preferences: {}
users:
- name: system:kube-controller-manager
user:
# 用于访问 kube-apiserver 的客户端证书
client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FU…………………………
# 客户端证书对应的私钥
client-key-data: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0t…………………………

可以看到,访问 kube-apiserver 所需要的相关证书内容已经被采用 base64 编码写入了文件中。其他几个文件中的内容也是类似的,只是配置的用户名和客户端证书有所不同。

在启动这些组件时,需要在参数中指出 kubeconfig 文件的路径,例如 kube-controller-manager 的启动命令如下。

1
2
3
4
5
6
7
/usr/local/bin/kube-controller-manager \\
--kubeconfig=/etc/kubernetes/controller-manager.conf
# 下面几个证书和访问 kube-apiserver 无关,我们会在后面介绍到
--cluster-signing-cert-file=/var/lib/kubernetes/cluster-root-ca.pem # 用于签发证书的 CA 根证书
--cluster-signing-key-file=/var/lib/kubernetes/cluster-root-ca-key.pem # 用于签发证书的 CA 根证书的私钥
--service-account-private-key-file=/var/lib/kubernetes/service-account-key.pem # 用于对 service account token 进行签名的私钥
...

Service Account证书

Kubernetes 中有两类用户,一类为 user account,一类为 service account。 service account 主要被 pod 用于访问 kube-apiserver。 在为一个 pod 指定了 service account 后,kubernetes 会为该 service account 生成一个 JWT token,并使用 secret 将该 service account token 加载到 pod 上。pod 中的应用可以使用 service account token 来访问 api server。service account 证书被用于生成和验证 service account token。该证书的用法和前面介绍的其他证书不同,因为实际上使用的是其公钥和私钥,而并不需要对证书进行验证。

可以看到 service account 证书的公钥和私钥分别被配置到了 kube-apiserver 和 kube-controller-manager 的命令行参数中,如下所示:

1
2
3
4
5
6
7
/usr/local/bin/kube-apiserver \\ 
--service-account-key-file=/var/lib/kubernetes/service-account.pem \\ # 用于验证 service account token 的公钥
...

/usr/local/bin/kube-controller-manager \\
--service-account-private-key-file=/var/lib/kubernetes/service-account-key.pem # 用于对 service account token 进行签名的私钥
...

下图展示了kubernetes中生成、使用和验证service account token的过程

service-account-token

Kubernetes 证书签发

Kubernetes 提供了一个 certificates.k8s.io API,可以使用配置的 CA 根证书来签发用户证书。该 API 由 kube-controller-manager 实现,其签发证书使用的根证书在下面的命令行中进行配置。我们希望 Kubernetes 采用集群根 CA 来签发用户证书,因此在 kube-controller-manager 的命令行参数中将相关参数配置为了集群根 CA。

1
2
3
4
5
/usr/local/bin/kube-controller-manager \\
--cluster-signing-cert-file=/var/lib/kubernetes/cluster-root-ca.pem # 用于签发证书的 CA 根证书
--cluster-signing-key-file=/var/lib/kubernetes/cluster-root-ca-key.pem # 用于签发证书的 CA 根证书的私钥
...
多是用来签发TLS-Bootstrapping的

TLS bootstrapping

引导流程

当集群开启了 TLS 认证后,每个节点的 kubelet 组件都要使用由 apiserver 使用的 CA 签发的有效证书才能与 apiserver 通讯;此时如果节点多起来,为每个节点单独签署证书将是一件非常繁琐的事情;TLS bootstrapping 功能就是让 kubelet 先使用一个预定的低权限用户连接到 apiserver,然后向 apiserver 申请证书,kubelet 的证书由 apiserver 动态签署;在配合 RBAC 授权模型下的工作流程大致如下所示(不完整,下面细说)

ixtwd

kubelet启动时候向kube-apiserver进行认证过程:

  1. kubelet启动时查找配置的kubeconfig文件

  2. 从kubeconfig文件中得到apiserver的URL和认证信息(一般是TLS key和CA签发的证书)

  3. 使用认证信息向apiserver进行认证

集群管理需要为kubelet做的事

  1. 为kubernetes集群创建CA和CA-KEY

  2. 将CA和CA-KEY给kube-apiserver使用

  3. 为每个node的kubelet创建证书和key(强烈建议每个node使用唯一证书,并设置唯一CN)

  4. 使用CA-KEY签发kubelet的证书

  5. 将kubelet的证书给kubelet使用

TLS Bootstrapping旨在简化从第三步开始的步骤,因为这是每个kubelet都需要的。

TLS Bootstrap初始化过程:

  1. kubelet进程启动

  2. kubelet查找kubeconfig,没找到(因为没配置)

  3. kubelet查找到bootstrap-kubeconfig文件

  4. kubelet通过bootstrap-kubeconfig文件,查找到apiserver的URL和有限制权限的”token”,该”token”可以通过apiserver的认证,且只能向apiserver发送CSR请求

  5. kubelet使用”token”向apiserver认证

  6. kubelet现在有权限创建一个CSR并发给apiserver

  7. kubelet创建一个key和cert,并使用cert创建一个带signerName=kubernetes.io/kube-apiserver-client-kubelet的CSR,并发送给apiserver

  8. apiserver签发该CSR:

    如果配置了自动签发,kube-controller-manager将会自动签发该CSR

    可以使用集群外的程序或人使用kubectl或Kubernetes API进行签发

  9. 为kubelet签发证书(一般证书有效期为1年)

  10. 将签发好的证书分发给kubelet

  11. kubelet得到签发的证书

  12. kubelet使用key和签发的证书生成kubeconfig

  13. kubelet现在可以正常与apiserver交互了

  14. 可选:如果配置了,kubelet将会在证书快过期的时候,自动发起新的CSR请求更新自己的证书

  15. apiserver或手动或自动签发kubelet发送的更新证书的CSR请求

相关术语

kubelet server

在官方 TLS bootstrapping 文档中多次提到过 kubelet server 这个东西;在经过翻阅大量文档以及 TLS bootstrapping 设计文档后得出,**kubelet server 指的应该是 kubelet 的 10250 端口;**

kubelet 组件在工作时,采用主动的查询机制,即定期请求 apiserver 获取自己所应当处理的任务,如哪些 pod 分配到了自己身上,从而去处理这些任务;同时 kubelet 自己还会暴露出两个本身 api 的端口,用于将自己本身的私有 api 暴露出去,这两个端口分别是 10250 与 10255;对于 10250 端口,kubelet 会在其上采用 TLS 加密以提供适当的鉴权功能;对于 10255 端口,kubelet 会以只读形式暴露组件本身的私有 api,并且不做鉴权处理

总结一下,就是说 kubelet 上实际上有两个地方用到证书,一个是用于与 API server 通讯所用到的证书,另一个是 kubelet 的 10250 私有 api 端口需要用到的证书

注意:默认情况下,TLS bootstrap签发的证书仅用于client auth,出于安全考虑,不会用于server auth。如果想要自动签发server证书,打开RotateKubeletServerCertificate特性,并且自己实现一个自动签发server证书的controller。但是,你也可以启动服务器证书,至少支持kubelet发送证书轮询请求,但是kube-controller-manager不能自动签发。

CSR 请求类型

kubelet 发起的 CSR 请求都是由 controller manager 来做实际签署的,对于 controller manager 来说,TLS bootstrapping 下 kubelet 发起的 CSR 请求大致分为以下三种

  • nodeclient: kubelet 以 O=system:nodesCN=system:node:(node name) 形式发起的 CSR 请求
  • selfnodeclient: kubelet client renew 自己的证书发起的 CSR 请求(与上一个证书就有相同的 O 和 CN)
  • selfnodeserver: kubelet server renew 自己的证书发起的 CSR 请求

大白话加自己测试得出的结果: nodeclient 类型的 CSR 仅在第一次启动时会产生,selfnodeclient 类型的 CSR 请求实际上就是 kubelet renew 自己作为 client 跟 apiserver 通讯时使用的证书产生的,selfnodeserver 类型的 CSR 请求则是 kubelet 首次申请或后续 renew 自己的 10250 api 端口证书时产生的

Token

Token格式:[a-z0-9]{6}.[a-z0-9]{16}

点号之前的是token ID,之后的是token Secret

如:abcdef.0123456789abcdef

权限

token认证的所使用的用户名是system:bootstrap:<token id>,属于system:bootstrappers组,可以在secret中的auth-extra-groups字段添加额外组

生成bootstrap-kubeconfig

1
2
3
4
5
6
7
8
kubectl config --embed-certs=true --kubeconfig=/var/lib/kubelet/bootstrap-kubeconfig set-cluster bootstrap --server='https://192.168.81.10:6443' --certificate-authority=/var/lib/kubernetes/ca.pem

kubectl config --kubeconfig=/var/lib/kubelet/bootstrap-kubeconfig set-credentials kubelet-bootstrap --token=10001.be31df5b85f4f55404b96bffe768562a

kubectl config --kubeconfig=/var/lib/kubelet/bootstrap-kubeconfig set-context bootstrap --user=kubelet-bootstrap --cluster=bootstrap

kubectl config --kubeconfig=/var/lib/kubelet/bootstrap-kubeconfig use-context bootstrap
# 将bootstrap-kubeconfig文件分发到kubelet上

配置

生成token

token由token-id和token-secret组成,id相当于用户名,secret相当于密码。

token-id由自己取一个,密码可以通过以下命令生成。

生成token密码:

1
head -c 16 /dev/urandom | od -An -t x | tr -d ' '

kube-apiserver配置

配置kube-apiserver参数

1
--client-ca-file=FILENAME

该CA用于验证客户端证书的有效性。

  • 用于认证bootstrapping kubelet的组system:bootstrappers

  • 批准CSR

kube-apiserver启用token认证

1
--enable-bootstrap-token-auth

方法一:Bootstrap Tokens Kubernetes v1.18 [stable]

这是一种更加简单方便的认证方式。

创建包含token ID,token密码,作用范围的secret

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
apiVersion: v1
kind: Secret
metadata:
# Name MUST be of form "bootstrap-token-<token id>"
name: bootstrap-token-07401b
namespace: kube-system
# Type MUST be 'bootstrap.kubernetes.io/token'
type: bootstrap.kubernetes.io/token
stringData:
# Human readable description. Optional.
description: "The default bootstrap token generated by 'kubeadm init'."
# Token ID and secret. Required.
token-id: "07401b"
token-secret: f395accd246ae52d
# Expiration. Optional.
#expiration: 2017-03-10T03:22:11Z
# Allowed usages.
usage-bootstrap-authentication: "true"
usage-bootstrap-signing: "true"
# Extra groups to authenticate the token as. Must start with "system:bootstrappers:" auth-extra-groups: system:bootstrappers:worker,system:bootstrappers:ingress

Secret说明

https://github.com/kubernetes/community/blob/master/contributors/design-proposals/cluster-lifecycle/bootstrap-discovery.md

  • type: bootstrap.kubernetes.io/token
  • name: bootstrap-token-<token id>
  • token-id: 自己定义的
  • token-secret: 自己生成
  • expiration: 过期时间
  • usage-bootstrap-signing: 为true时,表示允许这个token的请求签发bootstrap配置
  • usage-bootstrap-authentication: 这个token是否允许用于apiserver的认证
  • description(optional): 描述信息
  • auth-groups(or auth-extra-groups?): 逗号分隔的认证组列表,必须以system:bootstrappers:开头
  • ConfigMap Signing

方法二:Token authentication file

https://kubernetes.io/docs/reference/access-authn-authz/authentication/#static-token-file

生成token文件

如:02b50b05283e98dd0fd71db496ef01e8,kubelet-bootstrap,10001,"system:bootstrappers" token,user,uid,”group1,group2,group3”

kube-apiserver添加参数

1
--token-auth-file=FILENAME

配置bootstrapper有生成csr的权限

1
2
3
kubectl create clusterrolebinding kubelet-bootstrap \
--clusterrole=system:node-bootstrapper \
--user=kubelet-bootstrap

kube-controller-manager配置

签发kubelet证书所用的ca和ca-key,这个ca应用和kube-apiserver中的–client-ca-file一致

1
2
--cluster-signing-cert-file="/var/lib/kubernetes/ca.pem" 
--cluster-signing-key-file="/var/lib/kubernetes/ca-key.pem"

在kubernetes 1.19中提供了更加细致的CA配置,仅供参考

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
Csrsigning controller flags:

--cluster-signing-cert-file string Filename containing a PEM-encoded X509 CA certificate used to issue
cluster-scoped certificates. If specified, no more specific --cluster-signing-* flag may be specified.
--cluster-signing-duration duration The length of duration signed certificates will be given. (default 8
760h0m0s)
--cluster-signing-key-file string Filename containing a PEM-encoded RSA or ECDSA private key used to s
ign cluster-scoped certificates. If specified, no more specific --cluster-signing-* flag may be specified.
--cluster-signing-kube-apiserver-client-cert-file string Filename containing a PEM-encoded X509 CA certificate used to issue
certificates for the kubernetes.io/kube-apiserver-client signer. If specified, --cluster-signing-{cert,key}-file must not be set.
--cluster-signing-kube-apiserver-client-key-file string Filename containing a PEM-encoded RSA or ECDSA private key used to s
ign certificates for the kubernetes.io/kube-apiserver-client signer. If specified, --cluster-signing-{cert,key}-file must not be set
.
--cluster-signing-kubelet-client-cert-file string Filename containing a PEM-encoded X509 CA certificate used to issue
certificates for the kubernetes.io/kube-apiserver-client-kubelet signer. If specified, --cluster-signing-{cert,key}-file must not be
set.
--cluster-signing-kubelet-client-key-file string Filename containing a PEM-encoded RSA or ECDSA private key used to s
ign certificates for the kubernetes.io/kube-apiserver-client-kubelet signer. If specified, --cluster-signing-{cert,key}-file must no
t be set.
--cluster-signing-kubelet-serving-cert-file string Filename containing a PEM-encoded X509 CA certificate used to issue
certificates for the kubernetes.io/kubelet-serving signer. If specified, --cluster-signing-{cert,key}-file must not be set.
--cluster-signing-kubelet-serving-key-file string Filename containing a PEM-encoded RSA or ECDSA private key used to s
ign certificates for the kubernetes.io/kubelet-serving signer. If specified, --cluster-signing-{cert,key}-file must not be set.
--cluster-signing-legacy-unknown-cert-file string Filename containing a PEM-encoded X509 CA certificate used to issue
certificates for the kubernetes.io/legacy-unknown signer. If specified, --cluster-signing-{cert,key}-file must not be set.
--cluster-signing-legacy-unknown-key-file string Filename containing a PEM-encoded RSA or ECDSA private key used to s
ign certificates for the kubernetes.io/legacy-unknown signer. If specified, --cluster-signing-{cert,key}-file must not be set.

看个实践的例子

1
2
3
4
5
6
7
8
9
10
11
$ kubectl versionClient Version: version.Info{Major:"1", Minor:"19", GitVersion:"v1.19.0", GitCommit:"e19964183377d0ec2052d1f1fa930c4d7575bd50", GitTreeState:"clean", BuildDate:"2020-09-04T02:07:01Z", GoVersion:"go1.15", Compiler:"gc", Platform:"linux/amd64"}Server Version: version.Info{Major:"1", Minor:"19", GitVersion:"v1.19.0", GitCommit:"e19964183377d0ec2052d1f1fa930c4d7575bd50", GitTreeState:"clean", BuildDate:"2020-09-04T02:07:01Z", GoVersion:"go1.15", Compiler:"gc", Platform:"linux/amd64"}
$ kubectl get csr
NAME AGE SIGNERNAME REQUESTOR CONDITION
csr-9d7qd 0s kubernetes.io/kubelet-serving system:node:k8s00.99bill.com Pending #没有手工签发前的server证书csr
csr-qzbmz 8s kubernetes.io/kube-apiserver-client-kubelet system:bootstrap:07401b Approved,Issued # client证书csr
$ kubectl certificate approve csr-9d7qd
certificatesigningrequest.certificates.k8s.io/csr-9d7qd approved
$ kubectl get csr
NAME AGE SIGNERNAME REQUESTOR CONDITION
csr-9d7qd 43s kubernetes.io/kubelet-serving system:node:k8s00.99bill.com Approved,Issued
csr-qzbmz 51s kubernetes.io/kube-apiserver-client-kubelet system:bootstrap:07401b Approved,Issued

证书有效期参数

1
--cluster-signing-duration

自动续签证书请求参数(默认开启)

1
--feature-gates=RotateKubeletServerCertificate=true

(可选)自动删除过期token,在controller-manager添加参数,tokencleaner控制器负责这个工作。

1
--controllers=*,tokencleaner(默认开启)

签发证书配置:

为了批准csr,您需要告诉controller-manager批准它们是可以接受的。这是通过将RBAC权限授予正确的组来实现的。

配置证书自动签发权限

证书签发有CSR approving controllers实现,自动签发只针对client证书。

创建clusterrole

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# A ClusterRole which instructs the CSR approver to approve a user requesting
# node client credentials.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: system:certificates.k8s.io:certificatesigningrequests:nodeclient
rules:
- apiGroups: ["certificates.k8s.io"]
resources: ["certificatesigningrequests/nodeclient"]
verbs: ["create"]
---
# A ClusterRole which instructs the CSR approver to approve a node renewing its
# own client credentials.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: system:certificates.k8s.io:certificatesigningrequests:selfnodeclient
rules:
- apiGroups: ["certificates.k8s.io"]
resources: ["certificatesigningrequests/selfnodeclient"]
verbs: ["create"]

创建clusterrolebinding

自动批准 kubelet 的首次 CSR 请求(用于与 apiserver 通讯的证书)

1
kubectl create clusterrolebinding auto-approve-csrs-for-group --clusterrole=system:certificates.k8s.io:certificatesigningrequests:nodeclient --group=system:bootstrappers

自动批准 kubelet 后续 renew 用于与 apiserver 通讯证书的 CSR 请求

1
kubectl create clusterrolebinding auto-approve-renewals-for-nodes --clusterrole=system:certificates.k8s.io:certificatesigningrequests:selfnodeclient --group=system:nodes

自动批准 kubelet 发起的用于 10250 端口鉴权证书的 CSR 请求(包括后续 renew)(下面用到的clusterrole是不会默认创建的,需要手工创建一下)

1
2
3
4
5
6
7
8
9
10
# A ClusterRole which instructs the CSR approver to approve a node requesting a
# serving cert matching its client cert.
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: approve-node-server-renewal-csr
rules:
- apiGroups: ["certificates.k8s.io"]
resources: ["certificatesigningrequests/selfnodeserver"]
verbs: ["create"]
1
kubectl create clusterrolebinding node-server-auto-renew-crt --clusterrole=approve-node-server-renewal-csr --group=system:nodes

kubelet配置

kubelet加参数启动

1
2
3
4
5
--feature-gates=RotateKubeletClientCertificate=true,RotateKubeletServerCertificate=true(1.12后默认开启)
--rotate-certificates
--rotate-server-certificates
--bootstrap-kubeconfig="/var/lib/kubelet/bootstrap-kubeconfig"
--kubeconfig="/var/lib/kubelet/kubeconfig"

kubelet配置bootstrap需要以下东西:

存储签发好的证书的路径(可以使用默认/var/lib/kubelet/pki)

存放kubeconfig文件的路径(kubeconfig还不存在)

bootstrap kubeconfig文件

(可选)rotate证书

当kubelet启动时,如果kubeconfig文件不存在,将使用bootstrap-kubeconfig文件

获取到的证书将放在--cert-dir路径下

证书自动续期

默认情况下kube-apiserver签发的证书有效期1年,当然可以调整kube-controller-manager参数--cluster-signing-duration(早期版本是--experimental-cluster-signing-duration)修改有效期,但是生产环境不建议将时间修改得太长。

kubelet开启client证书轮询:--rotate-certificates

kubelet开启server证书轮询:--rotate-server-certificates

开启了参数之后,kubelet在证书将要到期的时候会向kube-apiserver发送续签证书请求。

该功能需要kubelet开启特性RotateKubeletClientCertificate(beta默认开始) 和 RotateKubeletServerCertificate(beta默认开启)

https://kubernetes.io/docs/tasks/tls/certificate-rotation/

注意:由于安全原因,在Kubernetes核心中实现的CSR审批控制器不审批节点serving证书。要使用RotateKubeletServerCertificate,需要运行自定义的审批控制器,或手动审批提供服务的证书请求。

RBAC 中 ClusterRole 只是描述或者说定义一种集群范围内的能力,这三个 ClusterRole 在 1.7 之前需要自己手动创建,在 1.8 后 apiserver 会自动创建前两个(1.8 以后名称有改变,自己查看文档);以上三个 ClusterRole 含义如下

  • approve-node-client-csr: 具有自动批准 nodeclient 类型 CSR 请求的能力
  • approve-node-client-renewal-csr: 具有自动批准 selfnodeclient 类型 CSR 请求的能力
  • approve-node-server-renewal-csr: 具有自动批准 selfnodeserver 类型 CSR 请求的能力

所以,如果想要 kubelet 能够自动续期,那么就应当将适当的 ClusterRole 绑定到 kubelet 自动续期时所采用的用户或者用户组身上

在自动续期下引导过程与单纯的手动批准 CSR 有点差异,具体的引导流程地址如下

  • kubelet 读取 bootstrap.kubeconfig,使用其 CA 与 Token 向 apiserver 发起第一次 CSR 请求(nodeclient)
  • apiserver 根据 RBAC 规则自动批准首次 CSR 请求(approve-node-client-csr),并下发证书(kubelet-client.crt)
  • kubelet **使用刚刚签发的证书(O=system:nodes, CN=system:node:NODE_NAME)**与 apiserver 通讯,并发起申请 10250 server 所使用证书的 CSR 请求
  • apiserver 根据 RBAC 规则自动批准 kubelet 为其 10250 端口申请的证书(kubelet-server-current.crt)
  • 证书即将到期时,kubelet 自动向 apiserver 发起用于与 apiserver 通讯所用证书的 renew CSR 请求和 renew 本身 10250 端口所用证书的 CSR 请求
  • apiserver 根据 RBAC 规则自动批准两个证书
  • kubelet 拿到新证书后关闭所有连接,reload 新证书,以后便一直如此

从以上流程我们可以看出,我们如果要创建 RBAC 规则,则至少能满足四种情况:

  • 自动批准 kubelet 首次用于与 apiserver 通讯证书的 CSR 请求(nodeclient)
  • 自动批准 kubelet 首次用于 10250 端口鉴权的 CSR 请求(实际上这个请求走的也是 selfnodeserver 类型 CSR)
  • 自动批准 kubelet 后续 renew 用于与 apiserver 通讯证书的 CSR 请求(selfnodeclient)
  • 自动批准 kubelet 后续 renew 用于 10250 端口鉴权的 CSR 请求(selfnodeserver)

证书及配置文件作用

  • token.csv

该文件为一个用户的描述文件,基本格式为 Token,用户名,UID,用户组;这个文件在 apiserver 启动时被 apiserver 加载,然后就相当于在集群内创建了一个这个用户;接下来就可以用 RBAC 给他授权;持有这个用户 Token 的组件访问 apiserver 的时候,apiserver 根据 RBAC 定义的该用户应当具有的权限来处理相应请求

  • bootstarp.kubeconfig

该文件中内置了 token.csv 中用户的 Token,以及 apiserver CA 证书;kubelet 首次启动会加载此文件,使用 apiserver CA 证书建立与 apiserver 的 TLS 通讯,使用其中的用户 Token 作为身份标识像 apiserver 发起 CSR 请求

  • kubelet-client.crt

该文件在 kubelet 完成 TLS bootstrapping 后生成,此证书是由 controller manager 签署的,此后 kubelet 将会加载该证书,用于与 apiserver 建立 TLS 通讯,同时使用该证书的 CN 字段作为用户名,O 字段作为用户组向 apiserver 发起其他请求

  • kubelet.crt

该文件在 kubelet 完成 TLS bootstrapping 后并且没有配置 --feature-gates=RotateKubeletServerCertificate=true 时才会生成;这种情况下该文件为一个独立于 apiserver CA 的自签 CA 证书,有效期为 1 年;被用作 kubelet 10250 api 端口,删除后 kubelet 组件会重新生成它

  • kubelet-server.crt

该文件在 kubelet 完成 TLS bootstrapping 后并且配置了 --feature-gates=RotateKubeletServerCertificate=true 时才会生成;这种情况下该证书由 apiserver CA 签署,默认有效期同样是 1 年,被用作 kubelet 10250 api 端口鉴权

  • kubelet-client-current.pem

这是一个软连接文件,当 kubelet 配置了 --feature-gates=RotateKubeletClientCertificate=true 选项后,会在证书总有效期的 70%~90% 的时间内发起续期请求,请求被批准后会生成一个 kubelet-client-时间戳.pemkubelet-client-current.pem 文件则始终软连接到最新的真实证书文件,除首次启动外,kubelet 一直会使用这个证书同 apiserver 通讯

  • kubelet-server-current.pem

同样是一个软连接文件,当 kubelet 配置了 --feature-gates=RotateKubeletServerCertificate=true 选项后,会在证书总有效期的 70%~90% 的时间内发起续期请求,请求被批准后会生成一个 kubelet-server-时间戳.pemkubelet-server-current.pem 文件则始终软连接到最新的真实证书文件,该文件将会一直被用于 kubelet 10250 api 端口鉴权

kubeconfig

kubeconfig 是用来访问 k8s 集群的凭证,生成 kubeconfig 的步骤很简单但参数很多,这里以生成 admin 的 kubeconfig 为例,解释各参数的含义。

生成最高权限的 kubeconfig

一般情况下集群创建之后,会先生成一份最高权限的 kubeconfig,即管理员角色,可以操作集群的所有资源,并为其他用户创建或删除权限,可以称之为 admin 证书,生成方式是:

admin-csr.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{
"CN": "kubernetes-admin",
"hosts": [
"172.18.0.1",
"100.64.230.122",
"100.75.187.77"
],
"key": {
"algo": "rsa",
"size": 2048
},
"names": [
{
"C": "CN",
"ST": "Jiangsu",
"L": "Wuxi",
"O": "system:masters",
"OU": "cloudnative"
}
]
}

admin 证书

1
cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=demo admin-csr.json | cfssljson -bare admin

生成 admin.conf,即最高权限的 kubeconfig

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# 配置kubernetes集群参数

kubectl config set-cluster kubernetes \
--certificate-authority=/etc/kubernetes/pki/ca.pem \
--embed-certs=true \
--server=https://vip:6443 \
--kubeconfig=admin.conf


# 配置客户端认证参数
kubectl config set-credentials kubernetes-admin \
--client-certificate=/etc/kubernetes/pki/admin.pem \
--embed-certs=true \
--client-key=/etc/kubernetes/pki/admin-key.pem \
--kubeconfig=admin.conf


# 设置上下文参数
kubectl config set-context kubernetes-admin@kubernetes \
--cluster=kubernetes \
--user=kubernetes-admin \
--kubeconfig=admin.conf


# 设置默认上下文
kubectl config use-context kubernetes-admin@kubernetes --kubeconfig=admin.conf


# 将 kubeconfig 拷贝到默认路径~/.kube/下,这是 kubectl 命令寻找 kubeconfig 时的默认路径
# 也可以在 kubectl 中手动指定 kubeconfig 文件。如 kubectl --kubeconfig=zhangsan.conf get cs
mkdir -p ~/.kube; cp admin.conf ~/.kube/config`

集群参数

本段设置了所需要访问的集群的信息。

  • 使用 set-cluster 设置了需要访问的集群,如上为 kubernetes 这只是个名称,实际为 –server 指向的 apiserver 所在的集群
  • –certificate-authority 设置了该集群的公钥
  • –embed-certs 为 true 表示将 –certificate-authority 证书写入到 kubeconfig 中
  • –server 则表示该集群的 apiserver 地址

用户参数

本段主要设置用户的相关信息,主要是用户证书。

  • 用户名: zhangsan
  • 证书: /etc/kubernetes/ssl/zhangsan.pem
  • 私钥: /etc/kubernetes/ssl/zhangsan-key.pem
1
2
3
4
5
6
cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=demo zhangsan.json | cfssljson -bare zhangsan

得到:

* zhangsan.pem
* zhangsan-key.pem

这一步操作是指客户端的证书首先要经过集群 CA 的签署,否则不会被集群认可,认证就失败。

此处使用的是 客户端 x509 认证方式,也可以使用token认证,如kubelet的 TLS Boostrap机制下的bootstrapping 使用的就是 token 认证方式

上下文参数

集群参数和用户参数可以同时设置多对,而上下文参数就是集群参数和用户参数关联起来。

上面的上下文名称为 kubenetes,集群为 kubenetes(apiserver 地址对应的集群),用户为zhangsan,表示使用 zhangsan 的用户凭证来访问 kubenetes 集群

最后使用 kubectl config use-context kubernetes 来使用名为 kubenetes 的环境项来作为配置。

如果配置了多个环境项,可以通过切换不同的环境项名字来访问到不同的集群环境。

kubeconfig 的认证过程

正向生成 kubeconfig 我们已经做完了,apiserver 认证请求时,如何解析 kubeconfig 文件的内容呢?

我们可以看下 kubeconfig 的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: REDACTED
server: https://xx:6443
name: kubernetes
contexts:
- context:
cluster: kubernetes
user: kubernetes-admin
name: kubernetes-admin@kubernetes
current-context: kubernetes-admin@kubernetes
kind: Config
preferences: {}
users:
- name: kubernetes-admin
user:
client-certificate-data: REDACTED
client-key-data: REDACTED

除了 context,里面有三个证书字段,都是 base64 编码后的内容

  • certificate-authority-data: server 端的证书,用于验证 apiserver 的合法性
  • client-certificate-data: 客户端证书
  • client-key-data: 客户端私钥

可以提取出来 certificate-authority-data 的内容放到一个文件cert.txt,然后base64解码

1
cat cert.txt | base64 -d 

certificate-authority-data:

得到的内容其实就是 ca.pem 即服务端证书,apiserver 的证书也是基于ca.pem签发,因为 TLS 是双向认证,apiserver 在认证 kubectl请求时,kubectl 也需要验证 apiserver 的证书,防止中间人攻击,验证的字段就是certificate-authority-data

client-certificate:

因为 k8s 没有 user 这种资源,因此在使用 kubeconfig 访问时,身份信息就“隐藏”在client-certificate的数据中,我们来查看一下。

将 kubeconfig 中的client-certificate-data的内容放在一个文件 client.txt 中,然后解码:

1
cat client.txt | base64 -d > admin.pem

查看证书内容:

1
cfssl certinfo -cert admin.pem
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
{
"subject": {
"common_name": "kubernetes-admin",
"country": "CN",
"organization": "system:masters",
"organizational_unit": "cloudnative",
"locality": "Jiangsu",
"province": "Wuxi",
"names": [
"CN",
"Jiangsu",
"Wuxi",
"system:masters",
"cloudnative",
"kubernetes-admin"
]
},
"issuer": {
"common_name": "kubernetes",
"country": "CN",
"organization": "k8s",
"organizational_unit": "cloudnative",
"locality": "Jiangsu",
"province": "Wuxi",
"names": [
"CN",
"Jiangsu",
"Wuxi",
"k8s",
"cloudnative",
"kubernetes"
]
},
"serial_number": "566012679603493454812450131987428233530903130206",
"sans": [
"172.18.0.1"
],
"not_before": "2020-06-25T01:50:00Z",
"not_after": "2030-06-23T01:50:00Z",
"sigalg": "SHA256WithRSA",
"authority_key_id": "DA:2B:A9:AE:AA:89:19:B7:0D:5F:FA:8B:1C:2D:EE:5D:EB:6E:D5:CB",
"subject_key_id": "FC:38:3A:C0:A4:E9:A6:41:16:24:AA:E6:1C:9C:7F:46:EF:42:61:08",
"pem": xxx

从输出内容可以看到Subject: organization=system:masters, common_name=kubernetes-admin

apiserver 验证、解析请求,得到 system:masters 的http上下文信息,并传给后续的authorizers来做权限校验。

O 和 CN 的含义

“O”:Organization, apiserver接到请求后从证书中提取该字段作为请求用户所属的组 (Group)
“CN”:Common Name,apiserver从证书中提取该字段作为请求的用户名 (User Name)

在admin-csr.json中, admin使用了system:masters作为组 (Group)

k8s 预定义了 RoleBinding:cluster-admin 将 Group system:masters 与 Role cluster-admin 绑定,该 Role 授予了调用 k8s 相关 API 的权限,权限极高。

即:

  • Group: system:masters
  • ClusterRole: cluster-admin
  • ClusterRoleBinding: cluster-admin

k8s 核心组件的默认权限

  • admin权限: system:masters组,clusterrole 和 rolebinding 都叫 cluster-admin
  • kubelet: system:nodes组,clusterrole 和 rolebinding 都叫system:nodes,下同
  • kube-proxy: system:kube-proxy组
  • scheduler: system:kube-scheduler组
  • controller-manager: system:kube-controller-manager组

授权

在Kubernetes中,授权作为一个独立的步骤。根据所有授权策略匹配请求资源属性,决定允许或拒绝请求。

授权包括六种方式:AlwaysDeny、AlwaysAllow、ABAC、RBAC、Webhook、Node。配置多个授权时,将按顺序检查每个授权,任何一个匹配的授权策略允许或拒绝请求时,则立即返回该决定,并且不会再检查其他授权策略;所有授权策略都没有允许或拒绝时,最终则拒绝该请求。

通过设置apiserver配置参数(–authorization-mode)启用授权插件。启用多个时,以逗号分隔。例如:

1
--authorization-mode=AlwaysAllow,AlwaysDeny,ABAC,Webhook,RBAC,Node

启用多个授权时,授权验证顺序由配置参数中的顺序决定。

本文仅对ServiceAccount做详细分析

在讲ServiceAccount之前,首先需要了解一下Secret

Secret

Secret有三种类型

  • Opaque:base64编码格式的Secret,用来存储密码、密钥等;但数据也通过base64 –decode解码得到原始数据,所以加密性很弱。
  • kubernetes.io/dockerconfigjson:用来存储私有docker registry的认证信息。
  • kubernetes.io/service-account-token: 用于被serviceaccount引用。serviceaccout创建时Kubernetes会默认创建对应的secret。Pod如果使用了serviceaccount,对应的secret会自动挂载到Pod目录/run/secrets/ kubernetes.io/serviceaccount中。

Opaque Secret

Opaque类型的数据是一个map类型,要求value是base64编码格式:

1
2
3
4
$ echo -n "admin" | base64
YWRtaW4=
$ echo -n "1f2d1e2e67df" | base64
MWYyZDFlMmU2N2Rm
1
2
3
4
5
6
7
8
9
10
secrets.yml
-----------
apiVersion: v1
kind: Secret
metadata:
name: mysecret
type: Opaque
data:
password: MWYyZDFlMmU2N2Rm
username: YWRtaW4=

创建secret:kubectl create -f secrets.yml

1
2
3
4
# kubectl get secret
NAME TYPE DATA AGE
default-token-cty7p kubernetes.io/service-account-token 3 45d
mysecret Opaque 2 7s

注意:其中default-token-cty7p为创建集群时默认创建的secret,被serviceacount/default引用。

如果是从文件创建secret,则可以用更简单的kubectl命令,比如创建tls的secret:

1
2
3
$ kubectl create secret generic helloworld-tls \
--from-file=key.pem \
--from-file=cert.pem

Opaque Secret的使用

创建好secret之后,有以下方式来使用它:

  • 以Volume方式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    apiVersion: v1
    kind: Pod
    metadata:
    labels:
    name: db
    name: db
    spec:
    volumes:
    - name: secrets
    secret:
    secretName: mysecret
    containers:
    - image: gcr.io/my_project_id/pg:v1
    name: db
    volumeMounts:
    - name: secrets
    mountPath: "/etc/secrets"
    readOnly: true
    ports:
    - name: cp
    containerPort: 5432
    hostPort: 5432

    查看pod中对应的信息

    1
    2
    3
    4
    5
    6
    # ls /etc/secrets
    password username
    # cat /etc/secrets/username
    admin
    # cat /etc/secrets/password
    1f2d1e2e67df
  • 以环境变量方式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    apiVersion: extensions/v1beta1
    kind: Deployment
    metadata:
    name: wordpress-deployment
    spec:
    replicas: 2
    strategy:
    type: RollingUpdate
    template:
    metadata:
    labels:
    app: wordpress
    visualize: "true"
    spec:
    containers:
    - name: "wordpress"
    image: "wordpress"
    ports:
    - containerPort: 80
    env:
    - name: WORDPRESS_DB_USER
    valueFrom:
    secretKeyRef:
    name: mysecret
    key: username
    - name: WORDPRESS_DB_PASSWORD
    valueFrom:
    secretKeyRef:
    name: mysecret
    key: password

    将Secret挂载指定的key

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    apiVersion: v1
    kind: Pod
    metadata:
    labels:
    name: db
    name: db
    spec:
    volumes:
    - name: secrets
    secret:
    secretName: mysecret
    items:
    - key: password
    mode: 511
    path: tst/psd
    - key: username
    mode: 511
    path: tst/usr
    containers:
    containers:
    - image: nginx
    name: db
    volumeMounts:
    - name: secrets
    mountPath: "/etc/secrets"
    readOnly: true
    ports:
    - name: cp
    containerPort: 80
    hostPort: 5432

    创建Pod成功后,可以在对应的目录看到:

    1
    2
    3
    # kubectl exec db ls /etc/secrets/tst 
    psd
    usr

kubernetes.io/dockerconfigjson

可以直接用kubectl命令来创建用于docker registry认证的secret:

1
2
3
4
5
6
7
$ kubectl create secret docker-registry myregistrykey 
--docker-server=DOCKER_REGISTRY_SERVER
--docker-username=DOCKER_USER
--docker-password=DOCKER_PASSWORD
--docker-email=DOCKER_EMAIL

secret "myregistrykey" created.

查看secret的内容

1
2
3
4
5
6
7
8
9
10
11
12
13
# kubectl get secret myregistrykey  -o yaml
apiVersion: v1
data:
.dockercfg: eyJjY3IuY2NzLnRlbmNlbnR5dW4uY29tL3RlbmNlbnR5dW4iOnsidXNlcm5hbWUiOiIzMzIxMzM3OTk0IiwicGFzc3dvcmQiOiIxMjM0NTYuY29tIiwiZW1haWwiOiIzMzIxMzM3OTk0QHFxLmNvbSIsImF1dGgiOiJNek15TVRNek56azVORG94TWpNME5UWXVZMjl0In19
kind: Secret
metadata:
creationTimestamp: 2017-08-04T02:06:05Z
name: myregistrykey
namespace: default
resourceVersion: "1374279324"
selfLink: /api/v1/namespaces/default/secrets/myregistrykey
uid: 78f6a423-78b9-11e7-a70a-525400bc11f0
type: kubernetes.io/dockercfg

也可以直接读取~/.dockercfg的内容来创建:

1
2
$ kubectl create secret docker-registry myregistrykey \
--from-file="~/.dockercfg"

在创建Pod的时候,通过imagePullSecrets来引用刚创建的myregistrykey:

1
2
3
4
5
6
7
8
9
10
apiVersion: v1
kind: Pod
metadata:
name: foo
spec:
containers:
- name: foo
image: janedoe/awesomeapp:v1
imagePullSecrets:
- name: myregistrykey

kubernetes.io/service-account-token

用于被serviceaccount引用。serviceaccout创建时Kubernetes会默认创建对应的secret。Pod如果使用了serviceaccount,对应的secret会自动挂载到Pod的/run/secrets/kubernetes.io/serviceaccount目录中。

1
2
3
4
5
6
7
8
9
$ kubectl run nginx --image nginx
deployment "nginx" created
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
nginx-3137573019-md1u2 1/1 Running 0 13s
$ kubectl exec nginx-3137573019-md1u2 ls /run/secrets/kubernetes.io/serviceaccount
ca.crt
namespace
token

Secret与ConfigMap对比

相同点:

  • key/value的形式
  • 属于某个特定的namespace
  • 可以导出到环境变量
  • 可以通过目录/文件形式挂载(支持挂载所有key和部分key)

不同点:

  • Secret可以被ServerAccount关联(使用)
  • Secret可以存储register的鉴权信息,用在ImagePullSecret参数中,用于拉取私有仓库的镜像
  • Secret支持Base64加密
  • Secret分为kubernetes.io/Service Account,kubernetes.io/dockerconfigjson,Opaque三种类型,Configmap不区分类型
  • Secret文件存储在tmpfs文件系统中,Pod删除后Secret文件也会对应的删除。

ServiceAccount

首先Kubernetes中账户区分为:User Accounts(用户账户) 和 Service Accounts(服务账户) 两种,它们的设计及用途如下:

  • UserAccount是给kubernetes集群外部用户使用的,例如运维或者集群管理人员,使用kubectl命令时用的就是UserAccount账户;UserAccount是全局性。在集群所有namespaces中,名称具有唯一性,默认情况下用户为admin;

    用户名称可以在kubeconfig中查看

1
2
3
4
[root@Centos8 ~]# cd ~/.kube/
[root@Centos8 .kube]# cat config
users:
- name: kubernetes-admin
  • ServiceAccount是给运行在Pod的程序使用的身份认证,Pod容器的进程需要访问API Server时用的就是ServiceAccount账户;ServiceAccount仅局限它所在的namespace,每个namespace都会自动创建一个default service account;创建Pod时,如果没有指定Service Account,Pod则会使用default Service Account。

Kubernetes区分普通帐户(user accounts)和服务帐户(service accounts)的原因

  • 普通帐户是针对(人)用户的,服务账户针对Pod进程。
  • 普通帐户是全局性。在集群所有namespaces中,名称具有唯一性。
  • 通常,群集的普通帐户可以与企业数据库同步,新的普通帐户创建需要特殊权限。服务账户创建目的是更轻量化,允许集群用户为特定任务创建服务账户。
  • 普通帐户和服务账户的审核注意事项不同。
  • 对于复杂系统的配置包,可以包括对该系统的各种组件的服务账户的定义。

Token Controller

TokenController作为controller-manager的一部分运行。异步行为:

  • 观察serviceAccount的创建,并创建一个相应的Secret 来允许API访问。
  • 观察serviceAccount的删除,并删除所有相应的ServiceAccountToken Secret
  • 观察secret 添加,并确保关联的ServiceAccount存在,并在需要时向secret 中添加一个Token。
  • 观察secret 删除,并在需要时对应 ServiceAccount 的关联

需要使用–service-account-private-key-file参数选项将Service Account 密匙(key)文件传递给controller-manager中的Token controller。key用于 Service Account Token签名。同样,也需要使用–service-account-key-file 参数选项将相应的(public key)公匙传递给kube-apiserver ,公钥用于在认证期间验证Token。

Service Account Controller

Service Account Controller在namespaces里管理ServiceAccount,并确保每个有效的namespaces中都存在一个名为“default”的ServiceAccount。

Service Account 用来访问 kubernetes API,通过 kubernetes API 创建和管理,每个 account 只能在一个 namespace 上生效,存储在 kubernetes API 中的 Secrets 资源。kubernetes 会默认创建,并且会自动挂载到 Pod 中的 /run/secrets/kubernetes.io/serviceaccount 目录下。
Service account是为了方便Pod里面的进程调用Kubernetes API或其他外部服务而设计的。它与User account不同:

  • User account是为人设计的,而service account则是为Pod中的进程调用Kubernetes API而设计;
  • User account是跨namespace的,而service account则是仅局限它所在的namespace;
  • 每个namespace都会自动创建一个default service account
  • Token controller检测service account的创建,并为它们创建secret
  • 开启ServiceAccount Admission Controller后
    • 每个Pod在创建后都会自动设置spec.serviceAccount为default(除非指定了其他ServiceAccout)
    • 验证Pod引用的service account已经存在,否则拒绝创建
    • 如果Pod没有指定ImagePullSecrets,则把service account的ImagePullSecrets加到Pod中
    • 每个container启动后都会挂载该service account的token和ca.crt到/var/run/secrets/kubernetes.io/serviceaccount/  

当创建 pod 的时候,如果没有指定一个 service account,系统会自动在与该pod 相同的 namespace 下为其指派一个default service account。而pod和apiserver之间进行通信的账号,称为serviceAccountName。如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[root@k8s-master ~]# kubectl get pods
NAME READY STATUS RESTARTS AGE
filebeat-ds-hxgdx 1/1 Running 1 34d
filebeat-ds-s466l 1/1 Running 2 34d
myapp-0 1/1 Running 0 3h
myapp-1 1/1 Running 0 3h
myapp-2 1/1 Running 0 4h
myapp-3 1/1 Running 0 4h
pod-vol-demo 2/2 Running 0 2d
redis-5b5d6fbbbd-q8ppz 1/1 Running 1 2d
[root@k8s-master ~]# kubectl get pods/myapp-0 -o yaml |grep "serviceAccountName"
serviceAccountName: default
[root@k8s-master ~]# kubectl describe pods myapp-0
Name: myapp-0
Namespace: default
......
Volumes:
......
default-token-j5pf5:
Type: Secret (a volume populated by a Secret)
SecretName: default-token-j5pf5
Optional: false

从上面可以看到每个Pod无论定义与否都会有个存储卷,这个存储卷为default-token-*** token令牌,这就是pod和serviceaccount认证信息。通过secret进行定义,由于认证信息属于敏感信息,所以需要保存在secret资源当中,并以存储卷的方式挂载到Pod当中。从而让Pod内运行的应用通过对应的secret中的信息来连接apiserver,并完成认证。每个 namespace 中都有一个默认的叫做 default 的 service account 资源。进行查看名称空间内的secret,也可以看到对应的default-token。让当前名称空间中所有的pod在连接apiserver时可以使用的预制认证信息,从而保证pod之间的通信。

1
2
3
4
5
6
7
8
[root@k8s-master01 ~]# kubectl create namespace kinmfer  #创建一个名称空间
namespace "kinmfer" created
[root@k8s-master01 ~]# kubectl get sa -n kinmfer #名称空间创建完成后会自动创建一个sa
NAME SECRETS AGE
default 1 11s
[root@k8s-master01 ~]# kubectl get secret -n kinmfer #同时也会自动创建一个secret
NAME TYPE DATA AGE
default-token-5jtz2 kubernetes.io/service-account-token 3 19s

在创建的名称空间中新建一个pod

1
2
3
4
5
6
7
8
9
10
11
12
13
[root@k8s-master01 pod-example]# cat pod_demo.yaml 
kind: Pod
apiVersion: v1
metadata:
name: task-pv-pod
namespace: kinmfer
spec:
containers:
- name: nginx
image: ikubernetes/myapp:v1
ports:
- containerPort: 80
name: www

查看pod信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[root@k8s-master01 pod-example]# kubectl apply -f  pod_demo.yaml 
pod "task-pv-pod" created
[root@k8s-master01 pod-example]# kubectl get pod -n kinmfer
NAME READY STATUS RESTARTS AGE
task-pv-pod 1/1 Running 0 13s
[root@k8s-master01 pod-example]# kubectl get pod task-pv-pod -o yaml -n kinmfer
......
volumeMounts:
- mountPath: /var/run/secrets/kubernetes.io/serviceaccount
name: default-token-5jtz2
......
volumes: #挂载sa的secret
- name: default-token-5jtz2
secret:
defaultMode: 420
secretName: default-token-5jtz2
......

一个ServiceAccount可包含多个Secret

其中

  • 名为Tokens的Secret用于访问API Server的Secret,也被称为Service Account Secret。
  • 名为imagePullSecrets的Secret用于下载容器镜像时的认证过程,通常镜像库运行在Insecure模式下,所以这个为空
  • 用户自定义的其他Secret,用于用户的进程。

名称空间新建的pod如果不指定sa,会自动挂载当前名称空间中默认的sa(default)。而默认的service account仅仅只能获取当前Pod自身的相关属性,无法观察到其他名称空间Pod的相关属性信息。如果想要扩展Pod,假设有一个Pod需要用于管理其他Pod或者是其他资源对象,是无法通过自身的名称空间的serviceaccount进行获取其他Pod的相关属性信息的,此时就需要进行手动创建一个serviceaccount,并在创建Pod时进行定义。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
[root@k8s-master ~]# kubectl explain sa
KIND: ServiceAccount
VERSION: v1

DESCRIPTION:
ServiceAccount binds together: * a name, understood by users, and perhaps
by peripheral systems, for an identity * a principal that can be
authenticated and authorized * a set of secrets

FIELDS:
apiVersion <string>
APIVersion defines the versioned schema of this representation of an
object. Servers should convert recognized schemas to the latest internal
value, and may reject unrecognized values. More info:
https://git.k8s.io/community/contributors/devel/api-conventions.md#resources

automountServiceAccountToken <boolean>
AutomountServiceAccountToken indicates whether pods running as this service
account should have an API token automatically mounted. Can be overridden
at the pod level.

imagePullSecrets <[]Object>
ImagePullSecrets is a list of references to secrets in the same namespace
to use for pulling any images in pods that reference this ServiceAccount.
ImagePullSecrets are distinct from Secrets because Secrets can be mounted
in the pod, but ImagePullSecrets are only accessed by the kubelet. More
info:
https://kubernetes.io/docs/concepts/containers/images/#specifying-imagepullsecrets-on-a-pod

kind <string>
Kind is a string value representing the REST resource this object
represents. Servers may infer this from the endpoint the client submits
requests to. Cannot be updated. In CamelCase. More info:
https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds

metadata <Object>
Standard object's metadata. More info:
https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata

secrets <[]Object>
Secrets is the list of secrets allowed to be used by pods running using
this ServiceAccount. More info:
https://kubernetes.io/docs/concepts/configuration/secret

创建serviceaccount(以下简称sa)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[root@k8s-master01 ~]#  kubectl create  serviceaccount admin   #创建一个sa 名称为admin
serviceaccount "admin" created
[root@k8s-master01 ~]# kubectl get sa
NAME SECRETS AGE
admin 1 6s
default 1 28d
[root@k8s-master01 ~]# kubectl describe sa admin #查看名称为admin的sa的信息,系统会自动创建一个token信息
Name: admin
Namespace: default
Labels: <none>
Annotations: <none>
Image pull secrets: <none>
Mountable secrets: admin-token-rxtrc
Tokens: admin-token-rxtrc
Events: <none>s

看到有一个 token 已经被自动创建,并被 service account 引用。设置非默认的 service account,只需要在 pod 的spec.serviceAccountName 字段中将name设置为您想要用的 service account 名字即可。在 pod 创建之初 service account 就必须已经存在,否则创建将被拒绝。需`要注意的是不能更新已创建的 pod 的 service account。

1
2
3
4
5
6
7
[root@k8s-master01 ~]# kubectl get secret  #会自动创建一个secret(admin-token-rxtrc),用于当前sa连接至当前API server时使用的认证信息
NAME TYPE DATA AGE
admin-token-rxtrc kubernetes.io/service-account-token 3 1m
default-token-tcwjz kubernetes.io/service-account-token 3 28d
myapp-ingress-secret kubernetes.io/tls 2 6h
mysql-passwd Opaque 1 17d
tomcat-ingress-secret kubernetes.io/tls 2 7h

创建一个pod应用刚刚创建的sa

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
 
[root@k8s-master mainfests]# kubectl create serviceaccount admin
serviceaccount/admin created
[root@k8s-master mainfests]# kubectl get sa
NAME SECRETS AGE
admin 1 3s
default 1 50d
[root@k8s-master mainfests]# kubectl describe sa/admin
Name: admin
Namespace: default
Labels: <none>
Annotations: <none>
Image pull secrets: <none>
Mountable secrets: admin-token-7k5nr
Tokens: admin-token-7k5nr
Events: <none>
[root@k8s-master mainfests]# kubectl get secret
NAME TYPE DATA AGE
admin-token-7k5nr kubernetes.io/service-account-token 3 31s
default-token-j5pf5 kubernetes.io/service-account-token 3 50d
mysecret Opaque 2 1d
tomcat-ingress-secret kubernetes.io/tls 2 10d
[root@k8s-master mainfests]# vim pod-sa-demo.yaml  #Pod中引用新建的serviceaccount
apiVersion: v1
kind: Pod
metadata:
name: pod-sa-demo
namespace: default
labels:
app: myapp
tier: frontend
spec:
containers:
- name: myapp
image: ikubernetes/myapp:v1
ports:
- name: http
containerPort: 80
serviceAccountName: admin
[root@k8s-master mainfests]# kubectl apply -f pod-sa-demo.yaml
pod/pod-sa-demo created
[root@k8s-master mainfests]# kubectl describe pods pod-sa-demo
......
Volumes:
admin-token-7k5nr:
Type: Secret (a volume populated by a Secret)
SecretName: admin-token-7k5nr
Optional: false
......

ServiceAccount中添加Image pull secrets

创建docker-registry的Secret

1
2
3
4
5
6
7
8
[root@Centos8 rbac]# kubectl create secret docker-registry myregistrykey --docker-server=hub.vfancloud.com --docker-username=admin --docker-password=admin@123 --docker-email=xxx@163.com -n kinmfer
secret/myregistrykey created

[root@Centos8 rbac]# kubectl get secret -n kinmfer
NAME TYPE DATA AGE
default-token-wwbc8 kubernetes.io/service-account-token 3 62m
myregistrykey kubernetes.io/dockerconfigjson 1 7s
kinmfer-token-9s8f7 kubernetes.io/service-account-token 3 43m

将docker-registry的Secret添加到SA

1
2
3
4
5
6
7
8
9
apiVersion: v1
kind: ServiceAccount
metadata:
name: kinmfersa
namespace: kinmfer
secrets:
- name: kinmfer-token-9s8f7
imagePullSecrets:
- name: myregistrykey

查看SA的Image pull secrets

1
2
3
4
5
6
7
8
9
[root@Centos8 rbac]# kubectl describe sa vfansa -n vfan 
Name: kinmfer
Namespace: vfan
Labels: <none>
Annotations: <none>
Image pull secrets: myregistrykey
Mountable secrets: kinmfer-token-9s8f7
Tokens: kinmfer-token-9s8f7
Events: <none>

这个时候,只要是使用此sa的pod,都可以在docker-registry中拉取镜像了,同样,可以把此secret添加到default的SA中。达到相同的效果。

可以为serviceaccount创建多个token

准入控制