以前在基于k8s做应用开发的时候,都是使用admin来使用k8s,基本不用去关注授权的问题。但是,当我们将k8s作为PaaS平台的容器编排引擎,并引入多租户时,就涉及到权限管理相关的问题了。那么,如果你刚好是公司的系统管理员,当你部署完一套k8s,准备将其提供给多个部门使用的时候(他们希望彼此互不影响),接下来的内容就特别适合你了。
概要介绍
K8s的安全问题越来越成为大家都在关注的重点,挑战一方面来自于运行时容器层面宿主机的安全,另一方面来自于k8s本身的安全。
认证方式
k8s的授权默认是基于RBAC的方式,这个比较好理解,但是认证却支持好几种方式:
- 客户端证书
- Bearer Tokens
- Service Account Token
- BootStrap Token
- Static Token
- HTTP Basic Auth
- Authenticating Proxy
接下来我们重点分析前三种类型,至于Authenticating Proxy之类,这里暂不做分析。
权限控制原理
要解决文章开始时提到的问题,按照常规业务系统的设计,不外乎以下几步:
- 将系统中模块的子功能的操作权限赋予角色;
- 将用户与角色绑定,让用户拥有角色对应的操作权限;
- 认证用户的登录;
k8s系统中基于RBAC的授权刚好就是前面两步做的事情:
- 首先创建role,在role中指定该role所拥有的权限,能够允许执行的所有操作。
- 然后创建rolebinding,这一步binding的不止是用户;在k8s中,允许binding到role的可以是
user
,group
,service account
。其中user
就是用户,group
是用户组,service account
可以理解为系统为内部服务分配的一个账户。
如果希望role和rolebinding不止是局限于对应的namespace,可以使用clusterrole和clusterrolebinding,这将使role的操作权限扩大到集群层面,而不是在单独某一个namespace内。
那么,第三步又是如何实现的呢?下面就介绍几种方法。
客户端证书认证
客户端证书认证就是客户端向k8s提交带有用户名、用户组等信息的CSR,然后管理员在k8s上为该客户签发证书。以后,用户使用该证书去访问k8s,api-server就能够辨识出用户。结合RBAC的配置,如果该用户或者用户组通过rolebinding绑定了role,那么该用户就拥有该role对对应资源的操作权限。
下面便是操作流程,通过示例,可以再理解一遍上面的流程。
在本地主机上生成客户端的key和CSR文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22# 生成key文件
$ openssl genrsa -out ljchen.key 4096
# 创建csr配置文件
$ cat csr.cnf
[ req ]
default_bits = 2048
prompt = no
default_md = sha256
distinguished_name = dn
[ dn ]
CN = ljchen
O = dev
[ v3_ext ]
authorityKeyIdentifier=keyid,issuer:always
basicConstraints=CA:FALSE
keyUsage=keyEncipherment,dataEncipherment
extendedKeyUsage=serverAuth,clientAuth
# 生成csr文件
$ openssl req -config ./csr.cnf -new -key ljchen.key -nodes -out ljchen.csr向k8s提交CSR,管理员签发后生成客户端证书
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# 创建CSR的k8s yaml
$ cat csr.yaml
apiVersion: certificates.k8s.io/v1beta1
kind: CertificateSigningRequest
metadata:
name: mycsr
spec:
groups:
- system:authenticated
request: ${BASE64_CSR}
usages:
- digital signature
- key encipherment
- server auth
- client auth
# 下发CSR到k8s
$ export BASE64_CSR=$(cat ./ljchen.csr | base64 | tr -d '\n')
$ cat csr.yaml | envsubst | kubectl apply -f -
# 在k8s上为CSR生成证书
$ kubectl get csr
$ kubectl certificate approve mycsr
$ kubectl get csr
NAME AGE REQUESTOR CONDITION
mycsr 9s 28b93...d73801ee46 Approved,Issued
# 导出证书
$ kubectl get csr mycsr -o jsonpath=’{.status.certificate}’ \
| base64 --decode > ljchen.crt
# 查看证书信息
$ openssl x509 -in ./ljchen.crt -noout -text
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
01:27:cb:76:d1:72:cd:73:a5:be:06:b1:ae:f9:6c:99:11:5c:36:c8
Signature Algorithm: sha256WithRSAEncryption
Issuer: CN=kubernetes
Validity
Not Before: Jun 13 02:04:00 2019 GMT
Not After : Jun 12 02:04:00 2020 GMT
Subject: O=dev, CN=ljchen
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (4096 bit)
...配置RBAC, 在k8s端使用证书来控制用户认证
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
50
51
52
53
54
55
56
57$ kubectl create ns development
# 创建role
$ cat role.yaml
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
namespace: development
name: dev
rules:
- apiGroups: [""]
resources: ["pods", "services"]
verbs: ["create", "get", "update", "list", "delete"]
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["create", "get", "update", "list", "delete"]
$ kubectl apply -f role.yaml
# 创建role-binding, 分两种情况,可以绑定到user或者group,两者二选一
# 绑定到user上
$ cat role-binding-user.yaml
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: dev
namespace: development
subjects:
- kind: User
name: ljchen
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: dev
apiGroup: rbac.authorization.k8s.io
# 绑定到group上
$ cat role-binding-group.yaml
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: dev
namespace: development
subjects:
- kind: Group
name: dev
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: dev
apiGroup: rbac.authorization.k8s.io
$ kubectl apply -f role-binding-group.yaml在客户端,配置kubeconfig文件
1
2
3# 1. 先基于生成的ljchen.crt和已存在的ca来配置kubeconfig文件
# 2. 将ljchen.key应用到kubeconfig中
$ kubectl config set-credentials ljchen --client-key=~/.kube/ljchen.key --embed-certs=true
Service Account Token认证
service account本来是系统服务使用的账户,当我们创建service account时,它会自带一个secret,这个secret就是token。
前面谈到,rolebinding除了支持绑定user,group之外,还支持绑定到service account。所以,为了方便,我们有时候通过绑定role到某个service account的方式来直接使用sa的sercret来访问系统资源。比如部署dashboard的时候,就经常用这种方法来获取token登录UI。
下面的示例就是创建service account并取得token来登录系统的流程。
服务端获取token
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#Create a new ServiceAccount
kubectl create serviceaccount k8sadmin -n kube-system
#Create a ClusterRoleBinding with Cluster Admin Privileges(系统管理员角色!!)
kubectl create clusterrolebinding k8sadmin --clusterrole=cluster-admin --serviceaccount=kube-system:k8sadmin
#serviceAccount 会自动生成secret,对应的token就是secret的值
kubectl get secret -n kube-system | grep k8sadmin | cut -d " " -f1 | xargs -n 1 | xargs kubectl get secret -n kube-system -o yaml
apiVersion: v1
data:
ca.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUN5RENDQWJDZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREFWTVJNd0VRWURWUVFERXdwcmRXSmwKY201bGRHVnpNQjRYRFRFNU1EWXhNakV6TWpZd09Gb1hEVEk1TURZd09URXpNall3T0Zvd0ZURVRNQkVHQTFVRQpBeE1LYTNWaVpYSnVaWFJsY3pDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBTktnCnRiSnQxSTZ6QlFVTmE2WHROK1pQN2xLeDdxbVk2aGJiaVByTXVGbmRhanBvd0RYdDhHVlJmNGFhbWVLS2Z0enEKWlJvSko5eHg5T3lnRkVINzRLY2tGRFhCbElUck9oY0t6TjJIWEI4SUlKM3BvN0Qzc0VScHZEYVZ6L0Z5TmRWagpBU0VhQzhlRWRSa2g2akhVeHJUTWlUMlJZemMrZGNaWC83MHcrTW90bjE5Skt4d0Y5N3h3U2EvYmd6NUdXK1V3CllBd0E0TWRwWG1YM1R1TUx5Rm9kSTVZOUt1VHBpVWd1OFp5Sm5ySzBpQ3g5MlpQMGRSVUlITXVRakxKWkhyY2QKOERsSmNnL0ZsOXlwaDh3UTZScDZSbzFlZ05tY2xGVFJBQm12OVhvMmNOdHJ4Mm1BTmp4aDBycjFkU21NWEJqUgpqS1Y0SFdqVWRzcERKbVpXRnJzQ0F3RUFBYU1qTUNFd0RnWURWUjBQQVFIL0JBUURBZ0trTUE4R0ExVWRFd0VCCi93UUZNQU1CQWY4d0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFNZkxia016RzZ6aGpUSGxtaU93SFJqL05BSWEKM0ZOY2lvekhJcUU2VkE5UUlOZ0V4a1pzQnBRREdBWVZvYk1Ec3RSQXQxY2M4cW5qdVltSnoreFlDZTFYY0xLMgpWRlpWdkdOZzJCRmJrTFdtYlRPeXhNNmQ0S2VvZnh6VDQ4cmU3U1pmQWRLRnRrVXN5T3AvUndQWGlOMVFpcy8vCkV6NDA1RlczTFZRWGtpMjh2cG5KUG5WNUhoZy9FY0ZMOXoveXdSUEtldmFRZUE5NGFFVXNuOEJXTGtZcWs3NWgKalM4dlhlNi9KOGVvWDdybVdhOG9QZUdQYmpqZ2E0Q0UyK2N1L0RqMFNFWEduK2xzM2w1V3dGZVNVNXdwd21XUwptdUdPdDZXVTdaa3hJbkNSTERudGovZzlJOFVGai9JZFBvd3B2bU1oUW5DOXFtV2pCZ21RSkhsN29iUT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=
namespace: a3ViZS1zeXN0ZW0=
token: ZXlKaGJHY2lPaUpTVXpJMU5pSXNJbXRwWkNJNklpSjkuZXlKcGMzTWlPaUpyZFdKbGNtNWxkR1Z6TDNObGNuWnBZMlZoWTJOdmRXNTBJaXdpYTNWaVpYSnVaWFJsY3k1cGJ5OXpaWEoyYVdObFlXTmpiM1Z1ZEM5dVlXMWxjM0JoWTJVaU9pSnJkV0psTFhONWMzUmxiU0lzSW10MVltVnlibVYwWlhNdWFXOHZjMlZ5ZG1salpXRmpZMjkxYm5RdmMyVmpjbVYwTG01aGJXVWlPaUpyT0hOaFpHMXBiaTEwYjJ0bGJpMXFiamQ0ZGlJc0ltdDFZbVZ5Ym1WMFpYTXVhVzh2YzJWeWRtbGpaV0ZqWTI5MWJuUXZjMlZ5ZG1salpTMWhZMk52ZFc1MExtNWhiV1VpT2lKck9ITmhaRzFwYmlJc0ltdDFZbVZ5Ym1WMFpYTXVhVzh2YzJWeWRtbGpaV0ZqWTI5MWJuUXZjMlZ5ZG1salpTMWhZMk52ZFc1MExuVnBaQ0k2SW1RMll6TmpPR1V5TFRoa01UVXRNVEZsT1MwNE0yVmtMVEF3TVRZelpURTBPV05tTWlJc0luTjFZaUk2SW5ONWMzUmxiVHB6WlhKMmFXTmxZV05qYjNWdWREcHJkV0psTFhONWMzUmxiVHByT0hOaFpHMXBiaUo5LnBlRGtqREp5UlliRDVfdFJQZDBKaDB5VEpSTDhNM3M1ZlJka1dYUWNDd2RGM0J3TE9mRVBPaWI0bEF3US1OcUptQS1sUlZ2dnQ4ZTVqU3h4UVVJSlRJYWp6S0RxaWYzQUpPVElYVEpmYy1vRGg1VUw2T01WdVJhdlhLNkZkbk1rd281RlhOSzVOYnl4WGx5RGdoaUJqWGc0ZUVXUWRLcXdKc05GYjBtcm15N2czb2NrZUsyQjZONzNDMWNwUGtIajRqTzY3bHl0OVFrQmtRRU1odGlNaURoZlJlTWFzLXF1ZFIzbGh3Z2xZUlNBbkdzelFOSnFKbFZxTzNUSkdEVjNmc0tUNVBlZFVHU1ZjM0JlQm1ZLXhyLUtyZFR5dE1rSk1rRUtoOHNOWEZ2UzN1MFowYjRRaVhwd296cjFkVlBkUnU4NDB2X2hkN1JPUkpOQkZ0UlV1dw==
kind: Secret
metadata:
annotations:
kubernetes.io/service-account.name: k8sadmin
kubernetes.io/service-account.uid: d6c3c8e2-8d15-11e9-83ed-00163e149cf2
creationTimestamp: "2019-06-12T13:27:34Z"
name: k8sadmin-token-jn7xv
namespace: kube-system
resourceVersion: "487"
selfLink: /api/v1/namespaces/kube-system/secrets/k8sadmin-token-jn7xv
uid: d6c522cd-8d15-11e9-83ed-00163e149cf2
type: kubernetes.io/service-account-token
# 获取token值
kubectl get secret -n kube-system | grep k8sadmin | cut -d " " -f1 | xargs -n 1 | xargs kubectl get secret -o 'jsonpath={.data.token}' -n kube-system | base64 --decode客户端使用token
1
2
3
4kubectl -s https://10.x.x.x:6443 --token eyJhbGciOiJSUzI1NiIsImtpZCI6IiJ9.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlLXN5c3RlbSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJrOHNhZG1pbi10b2tlbi1iNG1xcCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50Lm5hbWUiOiJrOHNhZG1pbiIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6IjVjN2RlYTVkLTUzYWMtMTFlOS1iMmNmLTUyNTQwMGZmNzI5YSIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDprdWJlLXN5c3RlbTprOHNhZG1pbiJ9.bpyv4SuQ1P-LyGIs5udfx74qonM21ickQA60og47HT5MHQDCypPyrsjBwq73mEzVvDmw6A42WKtBq_Tv4V7cT4fB-pnmxtkuESkmDdIr0FH7LCSyd6MIvIkqxkVvawy74AiWB7YbgbA1bJq5_btNPl8kPfFNz9SSPpKSnv3wc6Ln8kfvZRSWM-K_6sE4QQOhlcoshwqzCPSH9hU3HjkcYTg-QAry-IThjSLAoFiq4sgSsl95MG51sxPYhvM5hl-FLVGAehnJtlPlwc29BU5zfeCv64_hhLHIwYT74pLgVNe6O_McuhnreG5W2YcqxngFOUHuHrdogLlwRe_KELRS-Q get nodes
NAME STATUS ROLES AGE VERSION
k8s-master Ready master 73d v1.14.0
BootStrap Token认证
bootstrap token是kubeadm部署系统的时候,为了方便node加入到集群,从而提供了一个后门。该token对应的user拥有自动签发客户端CSR的权限,这样添加一台新节点的时候,就不用管理员到系统上面手动签发CSR请求了。
bootstrap-token都是以“bootstrap-token-”开头的名称,其中auth-extra-groups指定了其所属的group信息。clusterrolebinding也就是通过绑定clusterrole到该group,从而允许持有该token的kubeadm节点拥有签发证书的权限的。
token中的auth-extra-groups信息
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# 查看secret内容
$ kcs get secret bootstrap-token-gb4kis -o yaml
apiVersion: v1
data:
auth-extra-groups: c3lzdGVtOmJvb3RzdHJhcHBlcnM6a3ViZWFkbTpkZWZhdWx0LW5vZGUtdG9rZW4=
description: VGhlIGRlZmF1bHQgYm9vdHN0cmFwIHRva2VuIGdlbmVyYXRlZCBieSAna3ViZWFkbSBpbml0Jy4=
expiration: MjAxOS0wNi0xM1QyMToyNjozMyswODowMA==
token-id: Z2I0a2lz
token-secret: cW40M3d6ZGJzd2c4ZjdlOA==
usage-bootstrap-authentication: dHJ1ZQ==
usage-bootstrap-signing: dHJ1ZQ==
kind: Secret
metadata:
creationTimestamp: "2019-06-12T13:26:33Z"
name: bootstrap-token-gb4kis
namespace: kube-system
resourceVersion: "168"
selfLink: /api/v1/namespaces/kube-system/secrets/bootstrap-token-gb4kis
uid: b228ddbb-8d15-11e9-83ed-00163e149cf2
type: bootstrap.kubernetes.io/token
# 解出auth-extra-groups对应的base64编码信息为
$ echo "c3lzdGVtOmJvb3RzdHJhcHBlcnM6a3ViZWFkbTpkZWZhdWx0LW5vZGUtdG9rZW4=" | base64 -d
system:bootstrappers:kubeadm:default-node-token
# 其中system:bootstrappers为group信息
#接下来重点关注该group对应的clusterrolebinding
$ kcs get clusterrolebinding | grep kubeadm
kubeadm:kubelet-bootstrap 14h
kubeadm:node-autoapprove-bootstrap 14h
kubeadm:node-autoapprove-certificate-rotation 14h
kubeadm:node-proxier 14h绑定的clusterrole
clusterrolebinding | clusterrole |
---|---|
kubeadm:kubelet-bootstrap | system:node-bootstrapper |
kubeadm:node-autoapprove-bootstrap | system:certificates.k8s.io:certificatesigningrequests:nodeclient |
kubeadm:node-autoapprove-certificate-rotation | system:certificates.k8s.io:certificatesigningrequests:selfnodeclient |
kubeadm:node-proxier | system:node-proxier |
静态Token认证
api server参数:
–token-auth-file string
If set, the file that will be used to secure the secure port of the API server via token authentication.
api server通过--token-auth-file=SOMEFILE
读取文件中的Token。当前,token是无期限持续的,除非重启api server。token文件是一个至少包含3列的csv文件:token, user name, user uid
,后跟可选的组名。
如果您有多个组,则列必须是双引号,例如:
1 | token,user,uid,"group1,group2,group3" |
用token唯一标识客户端,只要api server存在该token,则认为认证通过,但是如果需要新增Token,则需要重启kube-apiserver组件。当通过客户端使用 bearer token 认证时,API服务器需要一个值为带有Bearer THETOKEN
值的Authorization头。bearer token必须是一个字符序列,能够放在HTTP请求头中。
HTTP Basic Auth
api server参数:
–basic-auth-file string
If set, the file that will be used to admit requests to the secure port of the API server via http basic authentication.
基础认证模式通过在api server中设置-–basic-auth-file=SOMEFILE
来启用。一旦api server服务启动,加载的用户名和密码信息就不会发生改变,任何对源文件的修改必须重启api server才能生效。
静态密码文件是CSV格式的文件,每行对应一个用户的信息:
1 | password,user,uid,"group1,group2,group3" |
当Http客户端使用基础认证时,api server需要一个带有Basic BASE64ENCODED(USER:PASSWORD) 值的Authorization头。