序言
一直以来,对kubernetes的身份验证、授权、准入控制似懂非懂。抄起键盘胡乱打,似懂非懂最可怕。所以,利用起周末时间,好好地学习一下。
pem证书和相关命令
pem是什么
PEM: Privacy Enhanced Mail的缩写,以文本的方式进行存储。
- 以pem格式存储的证书结构
1
2
3-----BEGIN CERTIFICATE-----
Base64编码过的证书数据
-----END CERTIFICATE-----
kubernetes使用的是.crt和.key后缀类型的证书和密钥。在生成证书的过程中,可以用cfssl生成.pem证书,然后直接命名为.crt。
证书查看命令
1 | openssl x509 -in xxx.crt -text -noout |
证书标准
kubernetes使用X.509数字证书标准。使用kubeadm部署集群的话,可以自动生成证书,但证书有效期只有一到两年。推荐使用kubeadm生成证书用于参考,然后按照kubernetes的证书标准自制证书。
kubeadm和证书类型
生成证书命令:1
kubeadm init phase certs all --cert-dir=绝对路径
查看生成的证书:
kubeadm主要是生成了etcd和kubernetes本身的证书。使用kubeadm安装集群的过程中,如果我们按照kubernetes标准自制证书,且跳过kubeadm init phase certs
步骤,是不是所有场景都会使用到自制证书了呢?
答案是否定的,至少/etc/kubernetes/admin.conf、kubelet.conf、controller-manager.conf、scheduler.conf里面仍会默认生成认证证书和密钥。这些配置主要与user accounts有关,因此我们还要针对这些场景,自制证书,同时对上述文件进行扩展,这是后话。
完整的证书:
可以看出,主要是根证书、证书、密钥。
下面对根证书和证书的内容进行抽象说明,主要参考SSL 数字证书的标准、编码以及文件扩展名。
根证书主要内容
使用openssl x509 -in xxx.crt -text -noout
查看。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23Certificate:
Data:
Version: 3 (0x2)
Serial Number: 0 (0x0) 说明:CA机构给该证书的唯一序列号,根证书为0
Signature Algorithm: sha256WithRSAEncryption 说明:签名算法为SHA-256
Issuer: CN=xxx 说明:证书颁发者的相关信息
Validity 说明:证书生效日期和失效日期
Not Before: xxx
Not After : xxx
Subject: CN=xxx 说明:证书持有者的相关信息
Subject Public Key Info: 说明:服务端公开的密钥
Public Key Algorithm: rsaEncryption 说明:RSA公钥
Public-Key: (2048 bit)
Modulus:
xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Key Usage: critical
xxx, xxx, xxx
X509v3 Basic Constraints: critical
CA:TRUE
Signature Algorithm: sha256WithRSAEncryption 说明:数字签名
xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:
证书主要内容
1 | Certificate: |
各个证书的区别
注:证书的O、CN字段用于提供RBAC所需的用户组和用户。
kubernetest证书制作标准参考PKI certificates and requirements。
另外,参考Key usage extensions and extended key usage,简单说明一下证书中用到的Key Usage和Extended Key Usage。
Key Usage | 说明 |
---|---|
Digital Signature | 当公钥与数字签名机制一起使用时使用,以支持除不可否认、证书签名或CRL签名之外的其他安全服务。数字签名通常用于具有完整性的实体身份验证和数据源身份验证。 |
Key Encipherment | 当证书与用于加密密钥的协议一起使用时使用。一个例子是S/MIME信封,其中使用证书中的公钥加密快速(对称)密钥。SSL协议还执行密钥加密。 |
Certificate Sign | 当subject公钥用于验证证书上的签名时使用。此扩展只能在CA证书中使用。 |
Extended Key Usage | 说明 |
---|---|
TLS Web Client Authentication | 数字签名和/或密钥协议 |
TLS Web Server Authentication | 数字签名、密钥加密或密钥协议 |
- etcd
注:kubeadm默认生成的证书包含的信息
证书 | 颁发者信息 | 持有者信息 |
---|---|---|
ca.crt | CN=etcd-ca | CN=etcd-ca |
healthcheck-client.crt | CN=etcd-ca | O=system:masters, CN=kube-etcd-healthcheck-client |
peer.crt | CN=etcd-ca | CN=<hostname> |
server.crt | CN=etcd-ca | CN=<hostname> |
证书 | Key Usage | Basic Constraints | Extended Key Usage | Subject Alternative Name |
---|---|---|---|---|
ca.crt | Digital Signature, Key Encipherment, Certificate Sign | CA:TRUE | - | - |
healthcheck-client.crt | Digital Signature, Key Encipherment | - | TLS Web Client Authentication | - |
peer.crt | Digital Signature, Key Encipherment | - | TLS Web Server Authentication, TLS Web Client Authentication | DNS:<hostname> , DNS:localhost, IP Address:<Host_IP> , IP Address:127.0.0.1, IP Address:0:0:0:0:0:0:0:1 |
server.crt | Digital Signature, Key Encipherment | - | TLS Web Server Authentication, TLS Web Client Authentication | DNS:<hostname> , DNS:localhost, IP Address:<Host_IP> , IP Address:127.0.0.1, IP Address:0:0:0:0:0:0:0:1 |
- kubernetes
注:kubeadm默认生成的证书包含的信息
证书 | 颁发者信息 | 持有者信息 |
---|---|---|
ca.crt | CN=kubernetes | CN=kubernetes |
apiserver.crt | CN=kubernetes | CN=kube-apiserver |
apiserver-kubelet-client.crt | CN=kubernetes | O=system:masters, CN=kube-apiserver-kubelet-client |
apiserver-etcd-client.crt | CN=etcd-ca | O=system:masters, CN=kube-apiserver-etcd-client |
front-proxy-ca.crt | CN=front-proxy-ca | CN=front-proxy-ca |
front-proxy-client.crt | CN=front-proxy-ca | CN=front-proxy-client |
sa.pub | - | - |
证书 | Key Usage | Basic Constraints | Extended Key Usage | Subject Alternative Name |
---|---|---|---|---|
ca.crt | Digital Signature, Key Encipherment, Certificate Sign | CA:TRUE | - | - |
apiserver.crt | Digital Signature, Key Encipherment | - | TLS Web Server Authentication | DNS:<hostname> , DNS:kubernetes, DNS:kubernetes.default, DNS:kubernetes.default.svc, DNS:kubernetes.default.svc.cluster.local, IP Address:集群serviveIP, IP Address:<Host_IP> |
apiserver-kubelet-client.crt | Digital Signature, Key Encipherment | - | TLS Web Client Authentication | - |
apiserver-etcd-client.crt | Digital Signature, Key Encipherment | - | TLS Web Client Authentication | - |
front-proxy-ca.crt | Digital Signature, Key Encipherment, Certificate Sign | CA:TRUE | - | - |
front-proxy-client.crt | Digital Signature, Key Encipherment | - | TLS Web Client Authentication | - |
sa.pub | - | - | - | - |
- user accounts
注:自制证书包含的信息
证书 | 颁发者信息 | 持有者信息 |
---|---|---|
admin.crt | CN=kubernetes | O=system:masters, CN=kubernetes-admin |
kubelet.crt | CN=kubernetes | O=system:nodes, CN=system:node:<nodeName> |
scheduler.crt | CN=kubernetes | CN=system:kube-scheduler |
controller-manager.crt | CN=kubernetes | CN=system:kube-controller-manager |
证书 | Key Usage | Basic Constraints | Extended Key Usage | Subject Alternative Name | Subject Key Identifier | Authority Key Identifier |
---|---|---|---|---|---|---|
admin.crt | Digital Signature, Key Encipherment | CA:FALSE | TLS Web Client Authentication | - | xx:xx | xx:xx |
kubelet.crt | Digital Signature, Key Encipherment | CA:FALSE | TLS Web Client Authentication, TLS Web Server Authentication | DNS:localhost, IP Address:127.0.0.1, IP Address:<Host_IP> | xx:xx | xx:xx |
scheduler.crt | Digital Signature, Key Encipherment | CA:FALSE | TLS Web Client Authentication | - | xx:xx | xx:xx |
controller-manager.crt | Digital Signature, Key Encipherment | CA:FALSE | TLS Web Client Authentication | - | xx:xx | xx:xx |
注意:kubelet的Key Usage需要同时包含TLS Web Client Authentication和TLS Web Server Authentication。具体原因后面介绍。
自制证书
参考PKI certificates and requirements、CFSSL as an external CA for non-ha kubeadm intialized clusters。
ectd
- ca-config.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23{
"signing": {
"profiles": {
"server": {
"expiry": "876000h",
"usages": [
"digital signature",
"key encipherment",
"server auth",
"client auth"
]
},
"client": {
"expiry": "876000h",
"usages": [
"digital signature",
"key encipherment",
"client auth"
]
}
}
}
}
ca
ca-csr.json
1
2
3
4
5
6
7
8{
"CN": "etcd-ca",
"hosts": [],
"key": {
"algo": "rsa",
"size": 2048
}
}命令
1
cfssl gencert -initca ca-csr.json | cfssljson -bare ca
healthcheck-client
healthcheck-client-csr.json
1
2
3
4
5
6
7
8{
"CN": "kube-etcd-healthcheck-client",
"hosts": [],
"key": {
"algo": "rsa",
"size": 2048
}
}命令
1
cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=client healthcheck-client-csr.json | cfssljson -bare healthcheck-client
peer
peer-csr.json.sed
1
2
3
4
5
6
7
8{
"CN": "kube-etcd-peer",
"hosts": ["localhost","127.0.0.1",<ETCD_NODE_IPS>],
"key": {
"algo": "rsa",
"size": 2048
}
}命令
1
cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=server peer-csr.json | cfssljson -bare peer
server
server-csr.json.sed
1
2
3
4
5
6
7
8{
"CN": "kube-etcd",
"hosts": ["localhost","127.0.0.1",<ETCD_NODE_IPS>],
"key": {
"algo": "rsa",
"size": 2048
}
}命令
1
cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=server server-csr.json | cfssljson -bare server
kubernetes
- ca-config.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{
"signing": {
"default": {
"expiry": "876000h"
},
"profiles": {
"server": {
"usages": [
"digital signature",
"key encipherment",
"server auth",
"client auth"
],
"expiry": "876000h"
},
"client": {
"usages": [
"digital signature",
"key encipherment",
"client auth"
],
"expiry": "876000h"
}
}
}
}
ca
ca-csr.json
1
2
3
4
5
6
7
8
9
10{
"CN": "kubernetes-ca",
"key": {
"algo": "rsa",
"size": 2048
},
"ca": {
"expiry": "876000h"
}
}命令
1
cfssl gencert -initca ca-csr.json | cfssljson -bare ca
apiserver
apiserver-csr.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20{
"CN": "kube-apiserver",
"hosts": [
"localhost",
"127.0.0.1",
<MASTER_HOST_IPS>,
<MASTER_HOST_NAMES>,
"<CLUSTER_SVC_IP>",
"<VIP>",
"kubernetes",
"kubernetes.default",
"kubernetes.default.svc",
"kubernetes.default.svc.cluster",
"kubernetes.default.svc.cluster.local"
],
"key": {
"algo": "rsa",
"size": 2048
}
}命令
1
cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=server apiserver-csr.json | cfssljson -bare apiserver
apiserver指定了-profile=server,但其应该不需要client auth,因为由apiserver主动向etcd、kubelet等发送请求时,会使用额外的证书(apiserver作为客户端),如apiserver-etcd-client.crt、apiserver-kubelet-client.crt。profile server包含client auth,是为了提供给kubelet用的。
apiserver-kubelet-client
apiserver-kubelet-client-csr.json
1
2
3
4
5
6
7
8
9
10
11
12
13{
"CN": "kube-apiserver-kubelet-client",
"hosts": [],
"key": {
"algo": "rsa",
"size": 2048
},
"names": [
{
"O": "system:masters"
}
]
}命令
1
cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=client apiserver-kubelet-client-csr.json | cfssljson -bare apiserver-kubelet-client
apiserver-etcd-client
apiserver-etcd-client-csr.json
1
2
3
4
5
6
7
8
9
10
11
12
13{
"CN": "kube-apiserver-etcd-client",
"hosts": [],
"key": {
"algo": "rsa",
"size": 2048
},
"names": [
{
"O": "system:masters"
}
]
}命令
1
cfssl gencert -ca=etcd/ca.pem -ca-key=etcd/ca-key.pem -config=etcd/ca-config.json -profile=client apiserver-etcd-client-csr.json | cfssljson -bare apiserver-etcd-client
front-proxy
- front-proxy-ca-config.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17{
"signing": {
"default": {
"expiry": "876000h"
},
"profiles": {
"client": {
"expiry": "876000h",
"usages": [
"digital signature",
"key encipherment",
"client auth"
]
}
}
}
}
front-proxy-ca
front-proxy-ca-csr.json
1
2
3
4
5
6
7
8
9
10{
"CN": "kubernetes-front-proxy-ca",
"key": {
"algo": "rsa",
"size": 2048
},
"ca": {
"expiry": "876000h"
}
}命令
1
cfssl gencert -initca front-proxy-ca-csr.json | cfssljson -bare front-proxy-ca
front-proxy-client
front-proxy-client-csr.json
1
2
3
4
5
6
7
8{
"CN": "front-proxy-client",
"hosts": [],
"key": {
"algo": "rsa",
"size": 2048
}
}命令
1
cfssl gencert -ca=front-proxy-ca.pem -ca-key=front-proxy-ca-key.pem -config=front-proxy-ca-config.json -profile=client front-proxy-client-csr.json | cfssljson -bare front-proxy-client
service account
- 命令
1
2openssl genrsa -out sa.key 2048
openssl rsa -in sa.key -pubout -out sa.pub
user accounts
admin
admin-csr.json
1
2
3
4
5
6
7
8
9
10
11
12
13{
"CN": "kubernetes-admin",
"hosts": [],
"key": {
"algo": "rsa",
"size": 2048
},
"names": [
{
"O": "system:masters"
}
]
}命令
1
cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=client admin-csr.json | cfssljson -bare admin
kubelet
- kubelet-csr.json.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17{
"CN": "system:node:<nodeName>",
"hosts": [
"127.0.0.1",
"localhost",
"HOST_IP"
],
"key": {
"algo": "rsa",
"size": 2048
},
"names": [
{
"O": "system:nodes"
}
]
}
<nodeName>
必须精确匹配由kubelet向apiserver注册时提供的节点名的值。
hosts字段不能为空,否则apiserver向kubelet发请求时kubelet无法验证ip的有效性。
- 命令
1
cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=server kubelet-csr.json | cfssljson -bare kubelet
注意:kubelet指定的profile需要包含client auth和server auth。具体原因后面介绍。
scheduler
scheduler-csr.json
1
2
3
4
5
6
7
8{
"CN": "system:kube-scheduler",
"hosts": [],
"key": {
"algo": "rsa",
"size": 2048
}
}命令
1
cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=client scheduler-csr.json | cfssljson -bare scheduler
controller-manager
controller-manager-csr.json
1
2
3
4
5
6
7
8{
"CN": "system:kube-controller-manager",
"hosts": [],
"key": {
"algo": "rsa",
"size": 2048
}
}命令
1
cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=client controller-manager-csr.json | cfssljson -bare controller-manager
格式转换
.pem和-key.pem直接转为.crt和.key文件。1
2
3
4
5for f in *-key.pem; do mv -v "$f" "${f%-key.pem}.key"; done
for f in *.pem; do mv -v "$f" "${f%.pem}.crt"; done
cd etcd
for f in *-key.pem; do mv -v "$f" "${f%-key.pem}.key"; done
for f in *.pem; do mv -v "$f" "${f%.pem}.crt"; done
配置user accounts证书
为user accounts配置上述自定义证书,参考Configure certificates for user accounts。1
2
3
4KUBECONFIG=<filename> kubectl config set-cluster default-cluster --server=https://<host ip>:6443 --certificate-authority <path-to-kubernetes-ca> --embed-certs
KUBECONFIG=<filename> kubectl config set-credentials <credential-name> --client-key <path-to-key>.pem --client-certificate <path-to-cert>.pem --embed-certs
KUBECONFIG=<filename> kubectl config set-context default-system --cluster default-cluster --user <credential-name>
KUBECONFIG=<filename> kubectl config use-context default-system
<filename>
分别为
- /etc/kubernetes/admin.conf
- /etc/kubernetes/kubelet.conf
- /etc/kubernetes/controller-manager.conf
- /etc/kubernetes/scheduler.conf
kubernetes认证场景
通过上述证书内容和csr.json,可知kubernetes内部常用的加解密算法为非对称加密算法RSA。接下来具体分析一些场景下的认证过程。首先介绍一下https双向认证过程。
https双向认证
kubectl
kubectl与apiserver通信,以控制和获取集群资源。这是一个双向认证过程。kubectl要验证apiserver的证书有效性,apiserver也要验证kubectl证书有效性。
默认证书
kubectl使用~/.kube/config配置文件,该文件拷贝自/etc/kubernetes/admin.conf。
需要关注的字段主要是:
- certificate-authority-data
用于验证apiserver发过来的证书。 - client-certificate-data
kubectl发送的证书,apiserver会对其进行验证。 - client-key-data
kubectl使用的密钥。
将内容进行base64 -d
和openssl x509 -in xxx.crt -text -noout
即可查看证书内容。可以发现,默认情况下的证书和密钥是kubeadm生成的,有效期只有一年。参考配置user accounts证书对其进行自定义。
通信过程
抓包研究下kubectl和apiserver的通信握手过程。apiserver监听端口为6443。((tcp[12] & 0xf0) >> 2)表示TCP头的大小。TLS包的第一个字节定义内容类型,值22(十六进制中的0x16)被定义为“握手”内容。1
tcpdump -ni any "tcp port 6443 and (tcp[((tcp[12] & 0xf0) >> 2)] = 0x16)" -w test.sap
执行kubectl get svc
,从而捕抓到kubectl和apiserver之间的握手包。
用wireshark查看test.sap。
可以发现,kubectl首先向apiserver发送了client hello报文,apiserver响应hello报文之后,发送自己的证书,并发送客户端证书请求。kubectl收到证书请求后,发送自己的证书,并验证apiserver证书的有效性。
具体的证书内容可以通过wireshark查看。
apiserver使用了--client-ca-file
字段指定了用于认证客户端证书的根证书。kubectl则是通过--certificate-authority-data
验证apiserver的证书。简单修改下certificate-authority-data的内容,测试一下。1
2# kubectl get pods
Unable to connect to the server: x509: certificate signed by unknown authority
查看抓包情况:
可以发现,apiserver的证书并没有通过kubectl的验证。
tls+rbac
参考使用kubectl访问Kubernetes集群时的身份验证和授权,apiserver负责集群访问权限控制,访问权限控制由身份验证(authentication)、授权(authorization)和准入控制(admission control)三步骤按序进行。身份验证支持的方法包括:client证书验证(https双向验证)、basic auth、普通token以及jwt token(用于serviceaccount)。授权支持的方式包括Node、RBAC、Webhook。
身份信息
kubectl的身份信息在client-certificate的数据中。从~/.kube/config中获取证书的持有者信息:1
Subject: O=system:masters, CN=kubernetes-admin
证书的O、CN字段用于提供RBAC所需的用户组和用户。起作用的主要是group = system:masters
。
参考User-facing Roles,可知kubernetes默认创建了cluster-admin的ClusterRole,并且绑定了group = system:masters
。1
2kubectl get clusterrolebinding/cluster-admin -o yaml
kubectl get clusterrole/cluster-admin -o yaml
可以发现,apiGroups、resources、verbs等都被授予了全部权限。
注:经过试验,修改了该场景下的CN后(即重新自定义证书),kubectl可以正常工作,因此在该场景下,CN应该没有发挥什么作用。
kubelet
kubelet.conf
kubelet.conf用于指明如何与apiserver进行通信。
需要关注的字段主要是:
- certificate-authority-data
用于验证apiserver发过来的证书。 - client-certificate-data
kubelet发送的证书,apiserver会对其进行验证。 - client-key-data
kubelet使用的密钥。
默认情况下,通过kubeadm init phase kubeconfig kubelet --config=kubeadm.yaml
生成的kubelet.conf将会包含有效期为一年的证书,默认使用了/etc/kubernetes/pki/ca.crt。在配置user accounts证书
一节,通过参数指定可以修改证书。
如果kubelet.conf的certificate-authority-data根证书内容有误,kubelet将会启动失败,出现类似以下错误日志:1
Unable to register node "xxx.xxx.xxx.xxx" with API server: Post https://xxx.xxx.xxx.xxx:6443/api/v1/nodes: x509: certificate signed by unknown authority
apiserver则将出现类似以下错误日志:1
http: TLS handshake error from xxx.xxx.xxx.xxx:48718: remote error: tls: bad certificate
如果kubelet.conf的client-certificate-data证书或client-key-data密钥内容有误,kubelet将会启动失败,出现类似以下错误日志:1
2Unable to load TLS configuration from existing bootstrap client config: tls: failed to find any PEM data in certificate input
Unable to load TLS configuration from existing bootstrap client config: tls: failed to find any PEM data in key input
这种场景下是无法使用kubectl log
命令的:1
Error from server: Get https://xxx.xxx.xxx.xxx:10250/containerLogs/kube-system/kube-apiserver-xxx.xxx.xxx.xxx/kube-apiserver?follow=true: dial tcp xxx.xxx.xxx.xxx:10250: connect: connection refused
参考apiserver-kubelet,可知通过kubectl查看log需要由apiserver连接到kubelet。
client-certificate-data和client-key-data也可以直接指向文件路径:1
2
3
4
5users:
- name: default-auth
user:
client-certificate: /var/lib/kubelet/pki/kubelet-client-current.pem
client-key: /var/lib/kubelet/pki/kubelet-client-current.pem
参考Kubernetes TLS bootstrapping那点事,可知kubelet-client-current.pem是一个软连接文件,当kubelet配置了--feature-gates=RotateKubeletClientCertificate=true
选项后,会在证书总有效期的70%~90%的时间内发起续期请求,请求被批准后会生成一个/var/lib/kubelet/pki/kubelet-client-时间戳.pem
。kubelet-client-current.pem文件则始终软连接到最新的真实证书文件,除首次启动外,kubelet一直会使用这个证书同apiserver通讯。参考kubelet,可知RotateKubeletClientCertificate
参数默认为true。
查看kubelet-client-current.pem文件:1
2
3
4
5
6-----BEGIN CERTIFICATE-----
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
-----END CERTIFICATE-----
-----BEGIN EC PRIVATE KEY-----
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
-----END EC PRIVATE KEY-----
使用openssl x509 -in ./cert -text -noout
查看证书内容,可以发现公钥加密算法为id-ecPublicKey,而不是rsaEncryption。
kubelet.conf vs kubelet-client-current.pem
既然kubelet.conf和kubelet-client-current.pem都用作kubelet和apiserver通信的证书,那两者之间的关系是什么?
基于kubelet启动的过程中需要向apiserver进行节点注册,以该场景为例进行抓握手包:1
tcpdump -ni any "tcp port 6443 and (tcp[((tcp[12] & 0xf0) >> 2)] = 0x16)" -w test.sap
然后重启kubelet:1
systemctl restart kubelet
查看握手包的Certificate的serialNumber,对比kubelet.conf和kubelet-client-current.pem证书中的serialNumber,发现默认情况下,kubelet是以kubelet-client-current.pem的内容与apiserver通信的。
由此,猜测kubelet.conf类似于备胎的作用,当不进行client证书轮转更新时,使用kubelet使用kubelet.conf与apiserver进行通信。以下进行实验证实:
- 修改/var/lib/kubelet/kubeadm-flags.env,添加
--feature-gates=RotateKubeletClientCertificate=false --rotate-certificates=false
systemctl restart kubelet
再次抓包,可以发现握手包的Certificate的serialNumber等于kubelet.conf证书中的serialNumber,猜测成立。
证书问题
实验过程中,实际上是出现了一些奇怪的问题。如果开启RotateKubeletClientCertificate的话,每次重启kubelet,若kubelet-client-current.pem不存在,则会自动创建新的证书文件(通过certificatesigningrequests请求),并将kubelet-client-current.pem指向它。
kubelet-client-current.pem内容错误
实验过程:
systemctl stop kubelet
rm /var/lib/kubelet/pki/kubelet-client-current.pem
systemctl restart kubelet
journalctl -xeu kubelet
发现kubelet无法注册节点,apiserver出现以下错误日志:1
Unable to authenticate the request due to an error: [x509: certificate signed by unknown authority, x509: certificate specifies an incompatible key usage]
查看kubelet-client-current.pem的证书内容,发现kubelet-client-current.pem的证书内容为kubelet.conf的证书内容。比对之前的证书内容,发现两者的差异是:
- kubelet-client-current.pem:X509v3 Extended Key Usage为TLS Web Server Authentication
- 之前的有效证书:X509v3 Extended Key Usage为TLS Web Client Authentication
猜测kubernetes在生成kubelet-client-current.pem的时候,若kubelet.conf的证书Key Usage包含Client Authentication,则轮转生成全新的证书内容(采用公钥加密算法为id-ecPublicKey);否则kubelet-client-current.pem直接引用kubelet.conf的证书内容。该猜测经过实验已证实。
在这个场景中,我们生成kubelet.crt的时候,指定了-profile=server
,且该profile不包含client auth
的usages;由该证书所生成的kubelet.conf、由该kubelet.conf所生成的kubelet-client-current.pem,并不满足认证要求。
查看log时apiserver和kubelet的验证
借助kubelet-client-current.pem证书出错(即只包含server auth)这个场景,验证下kubectl log
的执行过程:1
kubectl log kube-apiserver-xxx.xxx.xxx.xxx -n kube-system -f
输出以下日志:1
Error from server (InternalError): Internal error occurred: Authorization error (user=kube-apiserver-kubelet-client, verb=get, resource=nodes, subresource=proxy)
抓取kubectl<->apiserver和apiserver<->kubelet的握手包:1
2tcpdump -ni any "tcp port 6443 and (tcp[((tcp[12] & 0xf0) >> 2)] = 0x16)" -w 6443.sap
tcpdump -ni any "tcp port 10250 and (tcp[((tcp[12] & 0xf0) >> 2)] = 0x16)" -w 10250.sap
继续执行kubectl log
,查看握手包,发现:
- kubectl使用~/.kube/config(O=system:masters, CN=kubernetes-admin)的证书与apiserver通信,apiserver使用/etc/kubernetes/pki/apiserver.crt与kubectl通信(CN=kube-apiserver)。
- apiserver使用/etc/kubernetes/pki/apiserver-kubelet-client.crt与kubelet通信,kubelet使用/etc/kubernetes/pki/kubelet.crt与apiserver通信。
考虑到引用kubelet.crt的地方,是/var/lib/kubelet/config.yaml中的tlsCertFile和tlsPrivateKeyFile,这两个字段是由kubeadm.yaml指定的:1
2
3
4
5apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
address: "HOST_IP"
tlsCertFile: KUBELET_CERT
tlsPrivateKeyFile: KUBELET_KEY
若不指定,将生成自签名证书/var/lib/kubelet/pki/kubelet.crt和私钥/var/lib/kubelet/pki/kubelet.key。此处我们指定为自制证书路径/etc/kubernetes/pki/kubelet.crt和/etc/kubernetes/pki/kubelet.key。将其修改为临时生成的证书路径,然后执行kubectl log
,抓取10250端口的握手包,可以发现kubelet使用临时证书与apiserver通信。
在上一节中,kubelet向apiserver发请求注册节点,kubelet作为客户端,因此其证书需要包含client auth。在本节中,apiserver向kubelet发请求查看日志,kubelet作为服务端,其证书不但需要包含server auth,还需要包含host字段,用于验证apiserver所访问的ip。
若kubelet.crt不包含hosts字段,则apiserver可能报错:1
Error from server: Get https://xxx.xxx.xxx.xxx:10250/containerLogs/kube-system/kube-apiserver-xxx.xxx.xxx.xxx/kube-apiserver?follow=true: x509: cannot validate certificate for xxx.xxx.xxx.xxx because it doesn't contain any IP SANs
若kubelet.crt不包含server auth,则apiserver可能报错:1
Error from server: Get https://xxx.xxx.xxx.xxx:10250/containerLogs/kube-system/kube-apiserver-xxx.xxx.xxx.xxx/kube-apiserver?follow=true: x509: certificate specifies an incompatible key usage
因此,需要更新kubelet.crt,保证其同时包含client auth(确保正确生成kubelet-client-current.pem)和server auth(用于tlsCertFile),同时证书hosts字段包含节点ip。
身份信息
假设kubelet.crt同时包含client auth和server auth。获取kubelet证书的持有者信息:1
Subject: O=system:nodes, CN=system:node:<nodeName>
修改CN=test,生成证书,并修改kubelet.conf,可以发现以下现象:
kubelet发出的证书请求被拒绝,无法向apiserver查阅资源:1
2
3Failed while requesting a signed certificate from the master: cannot create certificate signing request: certificatesigningrequests.certificates.k8s.io is forbidden: User "test" cannot create resource "certificatesigningrequests" in API group "certificates.k8s.io" at the cluster scope
Failed to list *v1.Node: nodes "xxx.xxx.xxx.xxx" is forbidden: User "test" cannot list resource "nodes" in API group "" at the cluster scope
kubectl log出现认证错误:1
Error from server (InternalError): Internal error occurred: Authorization error (user=kube-apiserver-kubelet-client, verb=get, resource=nodes, subresource=proxy)
此时,kubelet-client-current.pem没有自动轮转,而是采用了kubelet.conf的内容。
修改O=test,生成证书,并修改kubelet.conf,可以发现以下现象:
kubelet发出的证书请求被拒绝,无法向apiserver查阅资源:1
2
3Failed while requesting a signed certificate from the master: cannot create certificate signing request: certificatesigningrequests.certificates.k8s.io is forbidden: User "system:nodes:xxx.xxx.xxx.xxx" cannot create resource "certificatesigningrequests" in API group "certificates.k8s.io" at the cluster scope
Failed to list *v1.Node: nodes "xxx.xxx.xxx.xxx" is forbidden: User "system:nodes:xxx.xxx.xxx.xxx" cannot list resource "nodes" in API group "" at the cluster scope
kubectl log出现认证错误:1
Error from server (InternalError): Internal error occurred: Authorization error (user=kube-apiserver-kubelet-client, verb=get, resource=nodes, subresource=proxy)
此时,kubelet-client-current.pem没有自动轮转,而是采用了kubelet.conf的内容。
由此可以发现,kubelet的证书需要同时满足CN和O的定义形式。具体可以参考Using Node Authorization和Using RBAC Authorization的说明。
kube-controller-manager
相关的启动参数:1
2
3
4
5
6
7
8
9
10--authentication-kubeconfig=/etc/kubernetes/controller-manager.conf,
--authorization-kubeconfig=/etc/kubernetes/controller-manager.conf,
--client-ca-file=/etc/kubernetes/pki/ca.crt,
--cluster-signing-cert-file=/etc/kubernetes/pki/ca.crt,
--cluster-signing-key-file=/etc/kubernetes/pki/ca.key,
--kubeconfig=/etc/kubernetes/controller-manager.conf,
--requestheader-client-ca-file=/etc/kubernetes/pki/front-proxy-ca.crt,
--root-ca-file=/etc/kubernetes/pki/ca.crt,
--service-account-private-key-file=/etc/kubernetes/pki/sa.key,
--use-service-account-credentials=true
controller-manager.conf用于与apiserver通信,其中根证书部分用于验证apiserver证书,证书密钥部分用于发送给apiserver。可以修改controller-manager.conf并通过重启docker进行验证。
若根证书无效,则kube-controller-manager日志会出现以下日志:1
error retrieving resource lock kube-system/kube-controller-manager: Get https://xxx.xxx.xxx.xxx:6443/api/v1/namespaces/kube-system/endpoints/kube-controller-manager?timeout=10s: x509: certificate signed by unknown authority
至于controller-manager与apiserver的通信,可以通过抓包查看,并重启docker:1
tcpdump -ni any "src host xxx.xxx.xxx.xxx and dst host xxx.xxx.xxx.xxx and tcp port 6443 and (tcp[((tcp[12] & 0xf0) >> 2)] = 0x16)" -w 6443.sap
关于service-account,参考管理Service Accounts,通过--service-account-private-key-file
参数项传入一个服务账户私钥文件至Token管理器,私钥用于为生成的服务账户token签名。同样地,通过--service-account-key-file
参数将对应的公钥传入kube-apiserver,公钥用于认证过程中的token校验。
我们可以通过创建serviceaccount的方式,并查看对应生成的secret,然后在jwt.io上查看token内容,以及进行签名验证。
access-token vs cert
实验场景,获取controller-manager metrics:1
2curl https://xxx.xxx.xxx.xxx:10257/metrics --cert /etc/kubernetes/pki/controller-manager.crt --key /etc/kubernetes/pki/controller-manager.key -k
curl https://xxx.xxx.xxx.xxx:10257/metrics --header "Authorization: Bearer $TOKEN" -k
得出以下结论:
- 服务端下发的是自签名的根证书和证书
- 客户端证书需要包含client auth
- 以controller-manager.crt为证书,User system:kube-controller-manager需要与已存在的system:auth-delegator ClusterRole进行绑定,不然会报以下错误:
Internal Server Error: "/metrics": subjectaccessreviews.authorization.k8s.io is forbidden: User "system:kube-controller-manager" cannot create resource "subjectaccessreviews" in API group "authorization.k8s.io" at the cluster scope
- 以controller-manager.crt为证书,还需要为User system:kube-controller-manager赋予metrics的访问权限
- 以apiserver-kubelet-client.crt为证书,不需要绑定system:auth-delegator ClusterRole,因为证书指定了O=system:masters,具备admin权限
- 以apiserver-etcd-client.crt为证书,Unauthorized,因为签发的根证书是etcd根证书
- 以secret admin-user为token的话,User system:kube-controller-manager需要与已存在的system:auth-delegator ClusterRole进行绑定
- 以secret default为token的话,User system:kube-controller-manager需要与已存在的system:auth-delegator ClusterRole进行绑定,同时需要为ServiceAccount default赋予metrics的访问权限
以上实验参考Securing Controller Manager and Scheduler Metrics,主要的配置内容如下:
User system:kube-controller-manager与已存在的system:auth-delegator ClusterRole进行绑定1
2
3
4
5
6
7
8
9
10
11
12kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: system:kube-controller-manager:auth-delegate
subjects:
- kind: User
name: system:kube-controller-manager
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: system:auth-delegator
apiGroup: rbac.authorization.k8s.io
secret default:default(<namespace>:<service-account>
)授权/metrics nonResourceURLs1
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
29apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: secure-metrics-scrape
rules:
- apiGroups:
- ""
resources:
- nodes/metrics
verbs:
- get
- nonResourceURLs:
- /metrics
verbs:
- get
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: metrics-endpoint
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: secure-metrics-scrape
subjects:
- kind: ServiceAccount
name: default
namespace: default
获取default:default token1
TOKEN=$(kubectl describe secret $(kubectl get secrets -n default | grep ^default | cut -f1 -d ' ') | grep -E '^token' | cut -f2 -d':' | tr -d " ")
为User system:kube-controller-manager赋予metrics的访问权限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
29apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: secure-metrics-scrape
rules:
- apiGroups:
- ""
resources:
- nodes/metrics
verbs:
- get
- nonResourceURLs:
- /metrics
verbs:
- get
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: metrics-endpoint
subjects:
- kind: User
name: system:kube-controller-manager
apiGroup: rbac.authorization.k8s.io
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: secure-metrics-scrape
front-proxy-client
front-proxy-client的相关配置主要是用于aggregated apiserver的。
apiserver关于front-proxy-client的相关参数:1
2
3
4
5
6
7--proxy-client-cert-file=/etc/kubernetes/pki/front-proxy-client.crt
--proxy-client-key-file=/etc/kubernetes/pki/front-proxy-client.key
--requestheader-allowed-names=front-proxy-client
--requestheader-client-ca-file=/etc/kubernetes/pki/front-proxy-ca.crt
--requestheader-extra-headers-prefix=X-Remote-Extra-
--requestheader-group-headers=X-Remote-Group
--requestheader-username-headers=X-Remote-User
aggregated apiserver
aggregated apiserver即聚合apiserver,用于解耦apiserver,集成用户自定义apiserver。这些apiserver可以跟core apiserver无缝衔接,使用kubectl也可以管理它们。可参考Kubernetes API Aggregation Setup。
使用kubectl访问kubernetes API
参考APIServiceSpec,apiservice的service字段表明:service是对该api server的服务引用,必须在端口443上通信。如果服务为空,这意味着api groupversion的处理将在此服务器上本地处理。调用将简单地委托给要执行的常规处理程序链。
查询所有api:1
kubectl get apiservice
查询指定api:1
kubectl get apiservice v1beta1.events.k8s.io -o yaml
访问api:1
kubectl get --raw=/apis/events.k8s.io/v1beta1
apiservice的注册参考Register APIService objects。
client-ca-file vs requestheader-client-ca-file
- client-ca-file
当请求到达apiserver时,如果开启client-ca-file选项,apiserver将检查请求的证书。如果证书是由client-ca-file所指定的ca证书签发的,请求将被视为合法的。user将取自CN=
字段,group将取自O=
字段。 - requestheader-client-ca-file
当请求到达apiserver时,如果开启requestheader-client-ca-file选项,apiserver将检查请求的证书。如果证书是由requestheader-client-ca-file所指定的ca证书签发的,请求将被视为潜在的合法请求。apiserver将检查证书的CN=
是否在requestheader-allowed-names列表中,如果在,允许该请求,否则拒绝。
如果同时指定两个选项,apiserver先检查requestheader-client-ca-file根证书,再检查client-ca-file根证书。常规的客户端请求匹配client-ca-file,聚合请求匹配requestheader-client-ca-file。若两者指定了相同的根证书,则可能会引发问题:通常情况下,client request证书能通过client-ca-file验证,在client-ca-file和requestheader-client-ca-file指定了相同的根证书的情况下,client request证书将匹配requestheader-client-ca-file,但CN=
通常不匹配requestheader-allowed-names,导致无法通过apiserver进行身份验证。(常规的客户端请求证书由于不匹配requestheader-client-ca-file,所以会尝试匹配client-ca-file)
认证流程
参考Authentication Flow,大致流程如下:
- Kubernetes apiserver:对请求用户进行身份验证,并授权他们对请求的API路径的权限。
- Kubernetes apiserver:将请求代理到扩展apiserver
- 扩展apiserver:验证来自Kubernetes apiserver的请求
当我们配置了apiserver关于front-proxy-client的相关参数,kubernetes将会在kube-system命名空间下创建extension-apiserver-authentication
configmap,扩展apiservers可以通过该configmap来验证请求。
这需要为扩展apiservers绑定适当的角色,默认的角色是kube-system命名空间下的extension-apiserver-authentication-reader
。 - 扩展apiserver:授权来自原始用户的请求
扩展apiserver通过向Kubernetes apiserver发送一个标准的SubjectAccessReview请求来验证用户/组是否被授权执行给定的请求。
这需要为扩展apiservers绑定适当的角色,Kubernetes包含一个默认的ClusterRolesystem:authdelegate-delegate
,它可以被授予扩展apiserver的服务帐户。 - 扩展apiserver:执行
apiserver认证源码实现
在以实际例子验证上述认证流程之前,这里讲解一下apiserver,已解答实际验证过程中的一些问题。
指定http处理链
创建apiserver配置信息,配置信息中指定默认的http处理链。
代码片段:1
2
3
4
5
6
7
8
9
10
11//func buildGenericConfig(s *options.ServerRunOptions, proxyTransport *http.Transport)
genericConfig = genericapiserver.NewConfig(legacyscheme.Codecs)
//func NewConfig(codecs serializer.CodecFactory)
return &Config{
BuildHandlerChainFunc: DefaultBuildHandlerChain,
...
}
//func DefaultBuildHandlerChain(apiHandler http.Handler, c *Config)
handler = genericapifilters.WithAuthentication(handler, c.Authentication.Authenticator, failedHandler, c.Authentication.APIAudiences)
参考:genericapiserver.NewConfig、DefaultBuildHandlerChain、genericapifilters.WithAuthentication
构建身份验证器
上述请求处理链绑定genericapifilters.WithAuthentication,然后传递c.Authentication.Authenticator身份验证器。这个身份验证器是一个union authenticator,包含了不同的认证场景下的不同的处理方法。
代码片段: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//func buildGenericConfig(s *options.ServerRunOptions, proxyTransport *http.Transport)
genericConfig.Authentication.Authenticator, genericConfig.OpenAPIConfig.SecurityDefinitions, err = BuildAuthenticator(s, clientgoExternalClient, versionedInformers)
//func BuildAuthenticator(s *options.ServerRunOptions, extclient clientgoclientset.Interface, versionedInformer clientgoinformers.SharedInformerFactory)
authenticatorConfig := s.Authentication.ToAuthenticationConfig()
...
return authenticatorConfig.New()
//func (config Config) New()
if config.RequestHeaderConfig != nil {
requestHeaderAuthenticator, err := headerrequest.NewSecure(
config.RequestHeaderConfig.ClientCA,
config.RequestHeaderConfig.AllowedClientNames,
config.RequestHeaderConfig.UsernameHeaders,
config.RequestHeaderConfig.GroupHeaders,
config.RequestHeaderConfig.ExtraHeaderPrefixes,
)
if err != nil {
return nil, nil, err
}
authenticators = append(authenticators, authenticator.WrapAudienceAgnosticRequest(config.APIAudiences, requestHeaderAuthenticator))
}
...
if len(config.ClientCAFile) > 0 {
certAuth, err := newAuthenticatorFromClientCAFile(config.ClientCAFile)
if err != nil {
return nil, nil, err
}
authenticators = append(authenticators, certAuth)
}
...
authenticator := union.New(authenticators...)
参考:genericConfig.Authentication.Authenticator、BuildAuthenticator、authenticatorConfig.New、headerrequest.NewSecure、NewSecure、union.New
验证过程
请求处理链绑定genericapifilters.WithAuthentication,然后传递c.Authentication.Authenticator身份验证器。实际调用Authenticator的AuthenticateRequest方法,而Authenticator是我们的union。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//func DefaultBuildHandlerChain(apiHandler http.Handler, c *Config) http.Handler
handler = genericapifilters.WithAuthentication(handler, c.Authentication.Authenticator, failedHandler, c.Authentication.APIAudiences)
//func WithAuthentication(handler http.Handler, auth authenticator.Request, failed http.Handler, apiAuds authenticator.Audiences) http.Handler
resp, ok, err := auth.AuthenticateRequest(req)
if err != nil || !ok {
if err != nil {
klog.Errorf("Unable to authenticate the request due to an error: %v", err)
}
failed.ServeHTTP(w, req)
return
}
//func (authHandler *unionAuthRequestHandler) AuthenticateRequest(req *http.Request) (*authenticator.Response, bool, error)
for _, currAuthRequestHandler := range authHandler.Handlers {
resp, ok, err := currAuthRequestHandler.AuthenticateRequest(req)
if err != nil {
if authHandler.FailOnError {
return resp, ok, err
}
errlist = append(errlist, err)
continue
}
if ok {
return resp, ok, err
}
}
return nil, false, utilerrors.NewAggregate(errlist)
每接收一个请求时,则遍历union authenticator的处理方法,一旦通过验证就跳出循环。union.AuthenticateRequest调用每个handler的AuthenticateRequest,按照func (config Config) New()
的指定顺序,分别是Front proxy、Basic auth、Cert、Token auth、Service account token、Bootstrap token、OIDC、Webhook…
这里我们关注一下Front proxy的处理流程,首先验证证书的合法性,然后判断CN是否在allowedCommonNames里面,最后返回到上层验证:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17//func (a *Verifier) AuthenticateRequest(req *http.Request) (*authenticator.Response, bool, error)
if _, err := req.TLS.PeerCertificates[0].Verify(optsCopy); err != nil {
return nil, false, err
}
if err := a.verifySubject(req.TLS.PeerCertificates[0].Subject); err != nil {
return nil, false, err
}
return a.auth.AuthenticateRequest(req)
//func (a *Verifier) verifySubject(subject pkix.Name) error
if len(a.allowedCommonNames) == 0 {
return nil
}
if a.allowedCommonNames.Has(subject.CommonName) {
return nil
}
return fmt.Errorf("x509: subject with cn=%s is not in the allowed list", subject.CommonName)
上层验证实际上是尝试从头部信息中获取name、group、extra信息:1
2
3
4
5
6
7//func (a *requestHeaderAuthRequestHandler) AuthenticateRequest(req *http.Request) (*authenticator.Response, bool, error)
name := headerValue(req.Header, a.nameHeaders)
if len(name) == 0 {
return nil, false, nil
}
groups := allHeaderValues(req.Header, a.groupHeaders)
extra := newExtra(req.Header, a.extraHeaderPrefixes)
可以发现,如果CN不在allowedCommonNames里,则会报错not in the allowed list
,返回nil, false, err
,由于err不为空,union.AuthenticateRequest将继续使用下一个handler进行验证,并将错误信息登记输出。
如果通过了front-proxy的证书认证,但是头部信息中没有包含name的话,验证也是失败的,返回nil, false, nil
,由于err为空,union.AuthenticateRequest将继续使用下一个handler进行验证,但没有将错误信息登记输出。
参考:genericapifilters.WithAuthentication、WithAuthentication、union.AuthenticateRequest、Verifier.AuthenticateRequest、requestHeaderAuthRequestHandler.AuthenticateRequest
验证认证流程
案例
这里采取的案例是prometheus-operator,它将会创建一个apiservice。1
2
3
4kubectl get apiservice
===output
v1beta1.metrics.k8s.io monitoring/prometheus-adapter True 18d
1 | kubectl get apiservice v1beta1.metrics.k8s.io -o yaml |
开启aggregator路由请求
尝试以下操作,默认出错:1
2
3
4
5
6
7
8
9kubectl api-resources
error: unable to retrieve the complete list of server APIs: metrics.k8s.io/v1beta1: Unauthorized
kubectl get --raw "/apis/metrics.k8s.io/v1beta1"
error: You must be logged in to the server (Unauthorized)
kubectl get <API_RESOURCE_NAME>.<API_VERSION>.<API_GROUP>
kubectl get pods.v1beta1.metrics.k8s.io
error: the server doesn't have a resource type "pods
参考CA Reusage and Conflicts,这是因为apiserver默认没有开启--enable-aggregator-routing=true
,该参数的作用是打开到endpoints IP的aggregator路由请求,替换cluster IP。将其添加到/etc/kubernetes/manifests/kube-apiserver.yaml(kubeadm安装的集群),重启kubelet,apiserver可能就地重启,所以可以通过docker stop/rm apiserver容器强制创建新服务。
通过curl方式验证
上述kubectl get pods.v1beta1.metrics.k8s.io
方式实际上是发送了以下请求:1
2//kubectl get pods.v1beta1.metrics.k8s.io --v=9
curl -k -v -XGET -H "Accept: application/json;as=Table;v=v1beta1;g=meta.k8s.io, application/json" -H "User-Agent: kubectl/v1.13.0 (linux/amd64) kubernetes/ddf47ac" 'https://xxx.xxx.xxx.xxx:6443/apis/metrics.k8s.io/v1beta1/namespaces/default/pods?limit=500'
这个请求实际上是使用了~/.kube/config(admin.conf)的证书,过的是client-ca-file验证,而不是requestheader-client-ca-file。
以下验证requestheader-client-ca-file的方式:
问题1:证书签发错误1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16curl -k -XGET 'https://xxx.xxx.xxx.xxx:6443/apis/metrics.k8s.io/v1beta1/namespaces/default/pods?limit=500' --cert /etc/kubernetes/pki/front-proxy-client.crt --key /etc/kubernetes/pki/front-proxy-client.key
===output
{
"kind": "Status",
"apiVersion": "v1",
"metadata": {
},
"status": "Failure",
"message": "Unauthorized",
"reason": "Unauthorized",
"code": 401
}
===apiserver log
Unable to authenticate the request due to an error: [x509: subject with cn=kube-front-client is not in the allowed list, x509: certificate signed by unknown authority]
/etc/kubernetes/pki/front-proxy-client.crt签发时使用了错误的CN。需要重新签发证书或将kube-front-client设置到--requestheader-allowed-names
。
问题2:没有指定username
重新签发证书后继续上述步骤,依然是Unauthorized,但是apiserver日志只输出:1
Unable to authenticate the request due to an error: x509: certificate signed by unknown authority
根据上一节的内容,我们知道这是由于请求头部没有带上username,front proxy认证失败,继续检查认证时由于front-proxy-client.crt不是由apiserver指定的client-ca-file所签发,因此报错。
执行以下命令:1
curl -k -XGET -H "X-Remote-User:front-proxy-client" 'https://xxx.xxx.xxx.xxx:6443/apis/metrics.k8s.io/v1beta1/namespaces/default/pods?limit=500' --cert /etc/kubernetes/pki/front-proxy-client.crt --key /etc/kubernetes/pki/front-proxy-client.key
问题3:没有权限
上述步骤将产生权限不足的问题:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15{
"kind": "Status",
"apiVersion": "v1",
"metadata": {
},
"status": "Failure",
"message": "pods.metrics.k8s.io is forbidden: User \"front-proxy-client\" cannot list resource \"pods\" in API group \"metrics.k8s.io\" in the namespace \"default\"",
"reason": "Forbidden",
"details": {
"group": "metrics.k8s.io",
"kind": "pods"
},
"code": 403
}
使用以下文件进行授权:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: secure-metrics-scrape
rules:
- apiGroups:
- metrics.k8s.io
resources:
- pods
verbs:
- "*"
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: metrics-endpoint
subjects:
- kind: User
name: front-proxy-client
apiGroup: rbac.authorization.k8s.io
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: secure-metrics-scrape
再次执行上述命令,将成功获得输出。
抓包验证:
进入prometheus-adapter容器,查看设备,设备号为33:1
cat /sys/class/net/eth0/iflink
进入容器所在宿主机,查看设备对,假设为calixxxxx:1
2
3ip link |grep ^33
或
for i in /sys/class/net/*/ifindex; do grep -l 33 $i; done
docker stop/rm强制删除apiserver,然后重新curl(避免apiserver使用已有连接,从而捕捉不到握手包),抓握手包:1
tcpdump -i calixxxxx "(tcp[((tcp[12] & 0xf0) >> 2)] = 0x16)" -w 6443.sap
由于启用了calico和ipvs,查看握手包,可以发现实际是tunnel向prometheus-adapter pod通信。通信双方client是发送了front-proxy-client证书,而server端则是发送临时签发的证书。
开启caBundle:
若apiservice开启caBundle,添加caBundle之后发送curl指令:1
2
3
4curl:Error: 'x509: certificate is valid for localhost, localhost, not prometheus-adapter.monitoring.svc'
prometheus-adapter的pod日志,其中xxx.xxx.xxx.xxx是IPIP Tunnel的ip:
http: TLS handshake error from xxx.xxx.xxx.xxx:40812: remote error: tls: bad certificate
证书或配置及其作用
证书或配置 | 目前已知作用 |
---|---|
*.conf相关 | |
~/.kube/config(admin.conf) | 与apiserver通信时kubectl使用的证书(apiserver使用apiserver.crt) |
kubelet.conf | key usage不包含client auth、O或CN不符合规定时,会导致kubelet-client-current.pem直接采用其证书内容(在kubelet-client-current.pem不存在的情况下) |
controller-manager.conf | 与apiserver通信时kube-controller-manager使用的证书(apiserver使用apiserver.crt) |
scheduler.conf | - |
kubelet相关 | |
/var/lib/kubelet/pki/kubelet-client-current.pem | kubelet向apiserver注册节点、获取资源信息等操作时kubelet使用的证书(apiserver使用apiserver.crt) |
kubelet.crt | 由kubectl log触发的apiserver与kubelet通信时,kubelet使用的证书(tlsCertFile字段指定) |
apiserver-kubelet-client.crt | 由kubectl log触发的apiserver与kubelet通信时,apiserver使用的证书 |
apiserver相关 | |
apiserver.crt | kubectl与apiserver通信时apiserver使用的证书;kubelet向apiserver注册节点、获取资源信息等操作时apiserver使用的证书;kube-controller-manager与apiserver通信时apiserver使用的证书 |
apiserver-etcd-client.crt | - |
front-proxy相关 | |
front-proxy-client.crt | 用于扩展apiserver的认证 |
service account相关 | |
sa.pub | apiserver用于认证过程中的token校验 |
etcd相关 | |
healthcheck-client.crt | - |
peer.crt | - |
server.crt | - |
access token
通过上文可知,kube-controller-manager通过--service-account-private-key-file
参数项传入一个服务账户私钥文件至Token管理器,私钥用于为生成的服务账户token签名。同样地,通过--service-account-key-file
参数将对应的公钥传入kube-apiserver,公钥用于认证过程中的token校验。
参考Accessing the API from a Pod,可知每个pod里面都放置了token文件夹和ca证书,用于在需要时与apiserver安全通信。
从Pod中定位和验证apiserver的推荐方式是通过kubernetes.default.svc这个DNS名称,该名称将会解析为服务IP,然后服务 IP将会路由到apiserver。
向apiserver进行身份验证的推荐方法是使用ServiceAccount凭证。ServiceAccount凭证放置在pod中每个容器的文件系统中,位于/var/run/secrets/kubernetes.io/serviceaccount/token,该凭证即为对应命名空间下默认创建的default-token。/var/run/secrets/kubernetes.io/serviceaccount/ca.crt,用于验证apiserver的服务证书。默认命名空间将被放置在/var/run/secrets/kubernetes.io/serviceaccount/namespace文件中。