一、概述
kubernetes 中有两种用户类型:服务账户(service account)和 普通用户(user)。这两种用户类型对应了两种使用场景。
服务账户提供给在集群中运行的 pod,当这些 pod 要和 apiserver 通信时,就是使用 serviceaccount 来认证和授权。服务账户是存储在 k8s 集群中的,基于 RBAC,可以和角色进行绑定,从而拥有特定资源的特定权限。
普通用户是非 pod 的场景下用来做认证和授权。比如像 k8s 的一些关键组件: scheduler, kubelet 和 controller manager,包括使用 kubectl 和 k8s 集群做交互。
二、服务账户
2.1 自动化
即使我们不在 namespace 下创建服务账户,也不为 pod 绑定任何的服务账户,pod 的 serviceAccount 字段也会被设置为 default。任何 namespace 下都会有这样的服务账户。我们可以查看这个名为 default 的服务账户,它会对应一个 secret。secret 中记录了 ca.crt,namespace 和 token 这三个值。
这整个过程由三个组件完成:
- 服务账户准入控制器(Service account admission controller)
- Token 控制器(Token controller)
- 服务账户控制器(Service account controller)
其中,服务账户控制器负责在每个 namespace 下维护默认的服务账户。这样当新的 namespace 被创建后,就会自动创建一个 default 服务账户。即使删除了该服务账户,也会立刻被重新自动创建出来。
token 控制器负责以下几项工作:
- 检测服务账户的创建,并且创建相应的 Secret 以支持 API 访问。
- 检测服务账户的删除,并且删除所有相应的服务账户 Token Secret。
- 检测 Secret 的增加,保证相应的服务账户存在,如有需要,为 Secret 增加 token。
- 检测 Secret 的删除,如有需要,从相应的服务账户中移除引用。
至于为什么需要为服务账户创建 token 并生成 secret,将在后面提到。
现在我们知道了服务账号和其 token 的自动化过程。那服务账户准入控制器又是什么作用呢?我们将目光投到 pod 创建的过程中,大多数时候我们都不会为 pod 指定服务账户。那么 pod 在创建成功后,关联的 default 服务账户是怎么回事呢?
这里就是服务账户准入控制器的作用了。它通过 admission controller 的机制来对 pod 进行修改。在 pod 被创建或更新时,会执行以下操作:
- 如果该 pod 没有
ServiceAccount
设置,将其ServiceAccount
设为default
。 -
保证 pod 所关联的
ServiceAccount
存在,否则拒绝该 pod。 -
如果 pod 不包含
ImagePullSecrets
设置,那么 将ServiceAccount
中的ImagePullSecrets
信息添加到 pod 中。 -
将一个包含用于 API 访问的 token 的
volume
添加到 pod 中。 -
将挂载于
/var/run/secrets/kubernetes.io/serviceaccount
的volumeSource
添加到 pod 下的每个容器中。
2.2 认证
假设我们的 pod 已经设置好了服务账户,现在要和 apiserver 通信,那么 apiserver 是怎么认证这个服务账户是有效的呢?
我们在上面提到,token 控制器会为服务账户创建 secret,secret 中的 token 就是服务账户的校验信息,具体来说是通过 JWT 来认证的。这个 token 中会存储服务账户所在的命名空间,名称,UID 和 secret 的名称等信息。如果你对 token 内容感兴趣的话,可以将 token 值用 base64 解码,粘贴到这里看看 token 中的内容。然后也可以将私钥和公钥粘贴上去验证签名是否正确。具体内容如下:
{
"iss": "kubernetes/serviceaccount",
"kubernetes.io/serviceaccount/namespace": "default",
"kubernetes.io/serviceaccount/secret.name": "default-token-894m5",
"kubernetes.io/serviceaccount/service-account.name": "default",
"kubernetes.io/serviceaccount/service-account.uid": "df5a8a9c-14d4-44c7-a55f-0100f51fc848",
"sub": "system:serviceaccount:default:default"
}
这个 token 在生成的过程中,使用了服务账户专属的私钥进行签名,这个私钥是在 contrller-manager 启动时,通过--service-account-private-key-file
传进去的。同样的,为了在 apiserver 中对这个 token 进行校验,需要在 apiserver 启动时通过参数--service-account-key-file
传入对应的公钥。
2.3 授权
认证的问题解决了,那么 apiserver 怎么知道该请求的服务账户是否有权限操作当前的资源呢?在 k8s 中,常用的授权策略就是 RBAC 了。我们通过将服务账户和角色关联,就可以让服务账户有指定资源的相关操作权限了。
三、普通用户
3.1 认证
不同于服务账户的是,k8s 本身并不存储普通用户的任何信息,那么 apiserver 是如何认证普通用户的呢?
在创建 k8s 集群时,一般都会有一个根证书负责签发集群中所需的其他证书。那么可以认为,如果一个普通用户可以提供由根证书签发的证书,他就是一个合法的用户。其中,证书的 common name 就是用户名,orgnization 是用户组。比如我们本地集群上的 controller-manager 的证书信息如下:
$ cfssl certinfo -cert controller-manager.pem
{
"subject": {
"common_name": "system:kube-controller-manager",
"names": [
"system:kube-controller-manager"
]
},
"issuer": {
"common_name": "kubernetes",
"names": [
"kubernetes"
]
},
"serial_number": "7884702304157281003",
"not_before": "2020-10-09T05:51:52Z",
"not_after": "2021-10-09T05:51:54Z",
"sigalg": "SHA256WithRSA",
"authority_key_id": "11:F5:D7:48:AE:2E:7F:59:DD:4C:C4:A8:97:D2:C0:21:98:C6:3A:A7",
"subject_key_id": "",
"pem": "-----BEGIN CERTIFICATE-----\nMIIDCDCCAfCgAwIBAgIIbWwW+H7/quswDQYJKoZIhvcNAQELBQAwFTETMBEGA1UE\nAxMKa3ViZXJuZXRlczAeFw0yMDEwMDkwNTUxNTJaFw0yMTEwMDkwNTUxNTRaMCkx\nJzAlBgNVBAMTHnN5c3RlbTprdWJlLWNvbnRyb2xsZXItbWFuYWdlcjCCASIwDQYJ\nKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMPCkXDAttbJHnoLuhGFPr/28ag8NoI5\nY0e00uv3ltyHlakfCeOV48eBgpMka3BdUxFOTHI5wtumlU3iymdDvTnKkLc75v6p\nQ0Hfx0DYz8ykDcHQ04hIsgyXecaHl+hfy90bYAbF8V43MjA0X2VmIyLxS6wXgeM6\n8d/jc1X8Ggpw53ow7L4XiCMlXDPwzLlVUThYHRN+PA5EdADZHAzgXjsyg379/ori\nbS/NZtmizzfHGWugrfwBGPL187mp1xN1tyjR+7obtsQYpgZ0Emz74fWNlike2I69\ntlBDWYC5ddsbHtDu4h/H5guwFtZ3+VVLogyw3CntPvoV840Ro5jxmtMCAwEAAaNI\nMEYwDgYDVR0PAQH/BAQDAgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMCMB8GA1UdIwQY\nMBaAFBH110iuLn9Z3UzEqJfSwCGYxjqnMA0GCSqGSIb3DQEBCwUAA4IBAQBvUxh0\n+TDJn19qJPWXu5MGrRs1Efn+KCgSVMDcak9MfnG3kzCZ94SKw5PRYGQ6fzuUsgwT\nkbGJ3o4PR/BkZ9R2UUHa2prydQTHN+Qb/DuF3kVYTRbWxTN3br8Tp1uqiQVOLPe0\nrfRelwVR6y39O5Wc3VQCnQKM/ih4k2LKGwinq2sO7HN6pjwoKfapaOb050vrGOTu\n5RmX+SWs7CeWzITjC3sLfFyP/lh8zK7TINOKRx1/QBHlCnX4wnsXpOIe4Jf4ol1b\nKKGcicAcSrj252oOIxspAW8a7vX4DjVGRTSneQen5wbHeZbkeMyuvAVs2a73x94d\nfTH4K9+zxCLAVZFs\n-----END CERTIFICATE-----\n"
}
其中,subject 是证书申请人的信息,issuer 是签发人的信息。这里可以知道,controller-manager 使用的是 system:kube-controller-manager
这个用户。
3.2 授权
跟服务账户的授权一样,普通用户也可以通过 RBAC 的机制来绑定角色,然后拥有某些资源的某些权限。比如 controller-manager 的 ClusterRoleBinding 信息如下:
$ kubectl get clusterrolebinding system:kube-controller-manager -o yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
annotations:
rbac.authorization.kubernetes.io/autoupdate: "true"
creationTimestamp: "2020-09-22T12:33:24Z"
labels:
kubernetes.io/bootstrapping: rbac-defaults
name: system:kube-controller-manager
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:kube-controller-manager
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: User
name: system:kube-controller-manager
也就是绑定到了 system:kube-controller-manager
这个集群角色,如果你感兴趣的话,还可以继续看这个角色绑定了哪些资源极其操作权限。
3.3 实践
因为普通用户需要我们自己为其签发证书,然后授权。下面用一个简单的例子来走一遍。
首先创建一个证书签发请求的 json 文件:
{
"CN": "jiang",
"key": {
"algo": "ecdsa",
"size": 256
},
"names": [
{
"O": "dev"
}
]
}
CN 是 common name 的简写。也就是我们要设置的用户名。O 是 orgnization 的简写,可以理解为用户组。接下来用 k8s 的根证书签发,需要 ca.crt 和 ca.key。这两个文件可以在 master 节点上的 /etc/kubernetes/certs
或其他地方找到。
cfssl gencert -ca ca.crt -ca-key=ca.key jiang.json | cfssljson -bare jiang
这条命令会生成三个新的文件:
- jiang-key.pem:私钥
- jiang.pem:证书
- jiang.csr: 证书签发请求文件
下面我们用 jiang 的私钥和证书生成 kubeconfig 中的用户:
kubectl config set-credentials jiang --client-key=./jiang-key.pem --client-certificate=./jiang.pem --embed-certs
接下来生成新的 context,指定 k8s 集群要使用的用户:
kubectl config set-context k8s-jiang --user=jiang --cluster=k8s
为 jiang 这个用户创建角色和绑定。这里只允许 jiang 这个用户读取 default namespace 下的 pod。
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: pod-reader
namespace: default
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "watch", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: read-pods
namespace: default
subjects:
- kind: User
name: jiang
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: pod-reader
apiGroup: rbac.authorization.k8s.io
这样就完成了对 jiang 这个用户的认证和授权了。使用下面命令切换到这个用户:
kubectl config use-context k8s-jiang