k8s 对我们整个系统的认证,授权,访问控制做了精密的设置;对于 k8s 集群来说, apiserver 是整个集群访问控制的唯一入口,我们在 k8s 集群之上部署应用程序的时候,也可以通过宿主机的
NodePor
t 暴露的端口访问里面的程序,用户访问 kubernetes 集群需要经历如下认证过程:认证->授权->准入控制(adminationcontroller)
- 认证(Authenticating):对客户端的认证,通俗点就是用户名密码验证
- 授权(Authorization):对资源的授权, k8s 中的资源无非是容器,最终其实就是容器的计算,网络,存储资源,当一个请求经过认证后,需要访问某一个资源(比如创建一个 pod),授权检查会根据授权规则判定该资源(比如某 namespace 下的 pod)是否是该客户可访问的。
- 准入(Admission Control)机制:准入控制器(Admission Controller)位于 API Server 中,在对象被持久化之前,准入控制器拦截对 API Server 的请求,一般用来做身份验证和授权。其中包含两个特殊的控制器:MutatingAdmissionWebhook 和 ValidatingAdmissionWebhook。分别作为配置的变异和验证准入控制 webhook。
- 变更(Mutating)准入控制:修改请求的对象
- 验证(Validating)准入控制:验证请求的对象
准入控制器是在 API Server 的启动参数配置的。一个准入控制器可能属于以上两者中的一种,也可能两者都属于。当请求到达 API Server 的时候首先执行变更准入控制,然后再执行验证准入控制。
我们在部署 Kubernetes 集群的时候都会默认开启一系列准入控制器,如果没有设置这些准入控制器的话可以说你的 Kubernetes 集群就是在裸奔。应该只有集群管理员可以修改集群的准入控制器。例如我会默认开启如下的准入控制器。
--admissioncontrol=ServiceAccount,NamespaceLifecycle,NamespaceExists,LimitRanger,ResourceQuota,MutatingAdmissionWebhook,ValidatingAdmissionWebhook
k8s 的整体架构也是一个微服务的架构,所有的请求都是通过一个 GateWay,也就是 kubeapiserver 这个组件(对外提供 REST 服务), k8s 中客户端有两类,一种是普通用户, 一种是集群内的 Pod,这两种客户端的认证机制略有不同,但无论是哪一种,都需要依次经过认证,授权,准入这三个机制。
k8s认证授权准入的基本工作逻辑
当K8S用户试图通过API-Server来完成资源操作时,他通常要完成3个步骤:
- 对用户的身份做验证,看用户的身份是否有合法的访问系统的权限。
- 对用户请求的动作是否得到确切的授权。如果用户执行的是读操作,那么到此结束,如果为写操作则需要进入第3步。
- 检查用户所提交的资源是否合乎规范,用户的修改是否合乎规范,如果合乎规范不符合标准格式,还需要将其转换,最后存入到ETCD中。
认证(Authenticating)
1、 认证支持多种插件
(1)令牌(token)认证:双方有一个共享密钥,服务器上先创建一个密码下来, 客户端登陆的时候拿这个密码登陆即可,这个就是对称密钥认证方式;k8s 提供了一个 restful
风格的接口,它的所有服务都是通过 http 协议提供的,因此认证信息只能经由 http 协议的认证首部进行传递,这种认证首部进行传递通常叫做令牌
;
(2) ssl 认证:对于 k8s 访问来讲, ssl 认证能让客户端确认服务器的认证身份,我们在跟服务器通信的时候,需要服务器发过来一个证书,我们需要确认这个证书是不是 ca
签署的,如果是我们认可的 ca 签署的,里面的 subj 信息与我们访问的目标主机信息保持一致,没有问题,那么我们就认为服务器的身份得到认证了, k8s 中最重要的是服务器还需要认证客户端的信息, kubectl 也应该有一个证书,这个证书也是 server
所认可的 ca
签署的证书,双方需要互相认证,实现加密通信,这就是 ssl 认证。
2、 kubernetes 上的账号
客户端对 apiserver 发起请求, apiserver 要识别这个用户是否有请求的权限,要识别用户本身能否通过 apiserver 执行相应的操作,那么需要哪些信息才能识别用户信息来完成对用户的相关的访问控制呢?
kubectl explain pods.spec
可以看到有一个字段 serviceAccountName(服务账号名称)
,这个就是我们 pod 连接 apiserver
时使用的账号, 因此整个 kubernetes
集群中的账号有两类,ServiceAccount(服务账号)
, User account(用户账号)
。
User account
:实实在在现实中的人,人可以登陆的账号,客户端想要对 apiserver 发起请求,apiserver 要识别这个客户端是否有请求的权限,那么不同的用户就会有不同的权限,靠用户账号表示,叫做 username。是登陆 k8s 物理机器的用户。
ServiceAccount
:方便 Pod 里面的进程调用 Kubernetes API 或其他外部服务而设计的,是kubernetes 中的一种资源。是登陆 dashboard 使用的账号。
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
;当开启 ServiceAccount Admission Controller
后:
1) 每个 Pod 在创建后都会自动设置 spec.serviceAccount
为 default
(除非指定了其他ServiceAccout)
2) 验证 Pod 引用的 service account
已经存在,否则拒绝创建;
当创建 pod 的时候,如果没有指定一个 serviceaccount
,系统会自动在与该 pod 相同的 namespace
下为其指派一个 default service account
。这是 pod
和 apiserver
之间进行通信的账号,如下:
root@k8s-master01:~# kubectl get pod
NAME READY STATUS RESTARTS AGE
demo-pod 1/1 Running 0 103d
root@k8s-master01:~# kubectl get pods demo-pod -o yaml | grep "serviceAccountName"
serviceAccountName: default
root@k8s-master01:~# kubectl describe pod demo-pod
...
Volumes:
default-token-64cz2:
Type: Secret (a volume populated by a Secret)
SecretName: default-token-64cz2
Optional: false
...
从上面可以看到每个 Pod
无论定义与否都会有个存储卷,这个存储卷为 default-token-***
。pod
和 apiserver
的认证信息通过 secret
进行定义,由于认证信息属于敏感信息,所以需要保存在 secret
资源当中,并以存储卷的方式挂载到 Pod 当中。从而让 Pod 内运行的应用通过对应的 secret
中的信息来连接 apiserver
,并完成认证。每个 namespace
中都有一个默认的叫做 default
的 serviceaccount
资源。查看名称空间内的 secret,也可以看到对应的 default token
。让当前名称空间中所有的 pod 在连接 apiserver
时可以使用的预制认证信息,从而保证 pod 之间的通信。
root@k8s-master01:~# kubectl get sa
NAME SECRETS AGE
default 1 104d
root@k8s-master01:~# kubectl get secret
NAME TYPE DATA AGE
default-token-64cz2 kubernetes.io/service-account-token 3 104d
默认的 service account
仅仅只能获取当前 Pod 自身的相关属性,无法观察到其他名称空间 Pod 的相关属性信息。如果想要扩展 Pod,假设有一个 Pod 需要用于管理其他 Pod 或者是其他资源对象,是无法通过自身的名称空间的 service account
进行获取其他 Pod 的相关属性信息的,此时就需要进行手动创建一个 service account
,并在创建 Pod 时进行定义。那么 service account
该如何进行定义呢?实际上, service Account
也属于标准的 k8s
资源,可以创建一个 service Account
,创建之后由我们创建的 pod 使用 service AccountName
去加载自己定义的 service Account
就可以了,如下:
root@k8s-master01:~# kubectl create serviceaccount test
serviceaccount/test created
root@k8s-master01:~# kubectl get sa
NAME SECRETS AGE
...
test 1 8s
# 可以看到已经创建了 test 的 serviceacount
# kubectl describe sa test 查看 test 这个账号的详细信息
root@k8s-master01:~# kubectl describe sa test
Name: test
Namespace: default
Labels: <none>
Annotations: <none>
Image pull secrets: <none>
Mountable secrets: test-token-mpdww
Tokens: test-token-mpdww
Events: <none>
# 上面可以看到生成了一个 test-token-mpdww 的 secret 和 test-token-mpdww 的 token
root@k8s-master01:~# kubectl get secret
NAME TYPE DATA AGE
...
test-token-mpdww kubernetes.io/service-account-token 3 117s
root@k8s-master01:~# kubectl describe secret test-token-mpdww
Name: test-token-mpdww
Namespace: default
Labels: <none>
Annotations: kubernetes.io/service-account.name: test
kubernetes.io/service-account.uid: d5ac8679-3cea-46c4-bb53-2901c65d4587
Type: kubernetes.io/service-account-token
Data
====
ca.crt: 1066 bytes
namespace: 7 bytes
token: eyJhbGciOiJSUzI1NiIsImtpZCI6IjJvWHZ2Q2p2aDktU1hDa0I0TkpGcUpPTXFZSnYyU2dOdzJuZmJUczhDaW8ifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZWZhdWx0Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6InRlc3QtdG9rZW4tbXBkd3ciLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC5uYW1lIjoidGVzdCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6ImQ1YWM4Njc5LTNjZWEtNDZjNC1iYjUzLTI5MDFjNjVkNDU4NyIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDpkZWZhdWx0OnRlc3QifQ.qK6gpEva5Z2bpOXF9hJrgPsWYNAyNe-CI1lavzHVFLD4aVH7VjoeJ0jy91RAwUhnjUDOGbVTSLnxTvZZJO68ByB_Si2e_CTcxiGDbY3Bc5vQzBbZyebVtRRDeii_tVSrybmELs3k4gpBKyxeeu6G0WTC-zM2WhPvNi0aydODeUok6KFvnydXWJ_NTcb57qonOBNxB5meKbE35MrPDNVAzyW_GaQpJi49Gbu1wpf3tVBTEebKezretGYVcAJgY9Dy6DUZ-jvt1erfnvXi3d2H7kdUSyel1dN8i2l82eTVb3GCU5f6Uua93IbpaWVmHan0qZXZowz_Edy5BoFYM5w-YQ
# 上面可以看到生成了 test-token-hnc57 的 token 详细信息,这个 token 就是 sa 连接 apiserver 的认证信息,这个 token 也是登陆 k8s dashboard 的 token,这些是一个认证信息,能够登陆 k8s,能认证到 k8s, 但是不能做别的事情,不代表权限,想要做其他事情,需要授权。
kubeconfig 文件
在 K8S 集群当中,每一个用户对资源的访问都是需要通过 apiserver
进行通信认证才能进行访问的,那么在此机制当中,对资源的访问可以是 token
,也可以是通过配置文件的方式进行保存和使用认证信息,可以通过 kubectl config
进行查看配置,如下:
root@k8s-master01:~# kubectl config view
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: DATA+OMITTED
server: https://kube-api:6443 #apiserver 的地址
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
在上面的配置文件当中,定义了集群、 上下文以及用户。其中 Config
也是 K8S
的标准资源之一,在该配置文件当中定义了一个集群列表,指定的集群可以有多个;用户列表也可以有多个,指明集群中的用户;而在上下文列表当中,是进行定义可以使用哪个用户对哪个集群进行访问,以及当前使用的上下文是什么。
授权
如果用户通过认证,什么权限都没有,需要一些后续的授权操作,如对资源的增删该查等, kubernetes1.6
之后开始有 RBAC(基于角色的访问控制机制)
授权检查机制。
Kubernetes 的授权是基于插件形成的,其常用的授权插件有以下几种:
Node(节点认证)
ABAC(基于属性的访问控制)
RBAC(基于角色的访问控制)
Webhook(基于 http 回调机制的访问控制)
什么是 RBAC(基于角色的访问控制)
让一个用户(Users)扮演一个角色(Role),角色拥有权限,从而让用户拥有这样的权限,随后在授权机制当中,只需要将权限授予某个角色,此时用户将获取对应角色的权限,从而实现角色的访问控制。如图:
在 k8s 的授权机制当中,采用 RBAC
的方式进行授权,其工作逻辑是,把对对象的操作权限定义到一个角色当中,再将用户绑定到该角色,从而使用户得到对应角色的权限。如果通过 rolebinding
绑定 role
,只能对 rolebingding
所在的名称空间的资源有权限,上图 user1 这个用户绑定到 role1 上,只对 role1 这个名称空间的资源有权限,对其他名称空间资源没有权限,属于名称空间级别的;
另外, k8s 为此还有一种集群级别的授权机制,就是定义一个集群角色(ClusterRole)
,对集群内的所有资源都有可操作的权限,从而将 User2 通过 ClusterRoleBinding
到 ClusterRole
,从而使 User2 拥有集群的操作权限。
Role
、 RoleBinding
、 ClusterRole
和 ClusterRoleBinding
的关系如下图:
角色绑定有以下三种情况:
- 用户通过
rolebinding
绑定role
- 用户通过
clusterrolebinding
绑定clusterrole
- 用户通过
rolebinding
绑定clusterrole
rolebinding 绑定 clusterrole 的好处:
假如有 6 个名称空间,每个名称空间的用户都需要对自己的名称空间有管理员权限,那么需要定义 6 个 role
和 rolebinding
,然后依次绑定,如果名称空间更多,我们需要定义更多的 role,这个是很麻烦的,所以我们引入 clusterrole
,定义一个 clusterrole
,对 clusterrole
授予所有权限,然后用户通过 rolebinding
绑定到 clusterrole
,就会拥有自己名称空间的管理员权限了
注:RoleBinding 仅仅对当前名称空间有对应的权限
准入控制
一般而言,准入控制只是用来定义我们授权检查完成之后的后续的其他安全检查操作的,进一步补充了授权机制,由多个插件组合实行,一般而言在创建,删除,修改或者做代理时做补充;
Kubernetes 的 Admission Control
实际上是一个准入控制器(Admission Controller)插件列表,发送到 APIServer
的请求都需要经过这个列表中的每个准入控制器插件的检查,如果某一个控制器插件准入失败,就准入失败。
控制器插件如下:
AlwaysAdmit
:允许所有请求通过。AlwaysPullImages
:在启动容器之前总是去下载镜像,相当于每当容器启动前做一次用于是否有权使用该容器镜像的检查。AlwaysDeny
:禁止所有请求通过,用于测试。DenyEscalatingExec
:拒绝 exec 和 attach 命令到有升级特权的 Pod 的终端用户访问。如果集中包含升级特权的容器,而要限制终端用户在这些容器中执行命令的能力,推荐使用此插件。ImagePolicyWebhook ServiceAccount
:这个插件实现了 serviceAccounts 等等自动化,如果使用 ServiceAccount 对象,强烈推荐使用这个插件。SecurityContextDeny
:将 Pod 定义中定义了的 SecurityContext 选项全部失效。SecurityContext 包含在容器中定义了操作系统级别的安全选型如 fsGroup, selinux 等选项。ResourceQuota
:用于 namespace 上的配额管理,它会观察进入的请求,确保在 namespace 上的配额不超标。推荐将这个插件放到准入控制器列表的最后一个。ResourceQuota 准入控制器既可以限制某个 namespace 中创建资源的数量,又可以限制某个 namespace 中被 Pod 请求的资源总量。ResourceQuota 准入控制器和 ResourceQuota 资源对象一起可以实现资源配额管理。LimitRanger
:用于 Pod 和容器上的配额管理,它会观察进入的请求,确保 Pod 和容器上的配额不会超标。准入控制器 LimitRanger 和资源对象 LimitRange 一起实现资源限制管理。NamespaceLifecycle
:当一个请求是在一个不存在的 namespace 下创建资源对象时,该请求会被拒绝。当删除一个 namespace 时,将会删除该 namespace 下的所有资源对象。- ...
当 Kubernetes 版本>=1.6.0,官方建议使用这些插件:
–admissioncontrol=NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,DefaultStorageClass,ResourceQuota,DefaultTolerationSeconds
当 Kubernetes 版本>=1.4.0,官方建议使用这些插件:
–admissioncontrol=NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,ResourceQuota
以上是标准的准入插件,如果是自己定制的话, k8s1.7 版 出了两个 alpha features, Initializers 和 External Admission Webhooks
常见的认证、授权和准入的实现
认证方式
X509数字证书认证
;- 证书中的Subject中的 CommonName , CN:被k8s当作用户名使用;
- 证书中的Subject中的Orgnization,O:被k8s当做组名;
引导令牌(Token)
:节点加入集群时的临时认证。静态令牌
:存储于API Server进程可直接加载到的文件中保存的令牌,该文件内容会由API Server缓存于内存中;静态密码
:存储于API Server进程可直接加载到的文件中保存的账户和密码令牌,该文件内容会由API Server缓存于内存中;ServiceAccount令牌
:专用于认证ServiceAccount账号。OpenID Connect令牌
:OIDC令牌,OAuth 2Webhook令牌
代理认证
授权方式
Node
:节点授权,专用于控制kubeletABAC
:Attribution,通过使用将属性组合在一起的策略向用户授予访问权限RBAC
: Role-Based AC, 基于角色的访问控制Webhook
准入控制器
LimitRanger
:定义pod的默认资源限制ResourceQuota
:定义每一个名称空间的资源限额PSP
: PodSecurityPolicy。在集群级别限制用户拥有哪些特权的。