Kubernetes安全概述
对于k8s api接口的访问不会是任何人能够轻易使用的,此前我们是通过kubectl这个客户端工具进行访问的,如果人人都可以通过kubectl来访问,那么就很可能会删除别人的应用,这是非常危险的。因此k8s对于整个系统的认证、授权和所谓的后续的访问控制做了非常精密和精细的设计。当然考虑k8s从诞生到现在还不算太长,可能会陆续有一些漏洞和缺陷被发现,但至少到现在为止,它在模型设计上已经做得足够安全了。
对于管理员来讲,整个k8s集群的apiserver是访问控制的唯一入口,但是如果我们在集群上部署了应用程序,通过Ingress或者通过Service把后端服务暴露出去,这些服务是不需要通过apiserver来访问的,只需要通过节点的NodePort或Ingress中Ingress Controller中的DaemonSet共享宿主机节点网络名称所监听的宿主机的地址来访问。
apiserver中的 api 是分了群组的,而且每一个群组还可以迭代,但是不管怎么样,任何用户试图到这平台上来操作资源对象,他们必须要经历所谓三种安全相关的操作:
- 1、认证:任何客户端访问之前,经过apiserver操作之前,得先完成认证操作。用户得有正确的账号。
- 2、授权检查:认证通过以后,只是证明了他是当前系统合法的用户,是否拥有删除对应资源的权限,还需要做授权检查。
- 3、准入控制:授权检查完后,我们可以操作某些资源了,但是有些资源的操作还要级联到其他资源和相应的环境,才能满足它的相应条件。因此,级联到的其他资源用户是否有权限,以及他是不是在我们所指定的操作范围内。
k8s是高度模块化设计的,它的认证、授权和准入控制各自都通过插件的方式,可由用户自定义选择经由什么样的插件来完成何种控制逻辑。
认证
对认证来讲,可能有n多个插件,支持多种不同的认证方式。
令牌认证
所谓令牌认证就是双方有一个域共享密钥,服务端存了一个密码,就像mysql认证一样,我们在服务器上先创建一个密码存放下来,随后登录时就拿这个密码登录。这种称为对称密钥登录方式。但是由于k8s提供的是restful风格的接口,它的所有服务都是通过http协议提供的,因此认证信息只能经由http协议的首部进行传递,这种首部在传递时,域共享密钥的编码信息我们称为认证令牌,就叫token。这是最简单的认证方式,但是这种认证,一般只是双方交换下是否拥有域共享密钥就足够了。
ssl认证
ssl认证能让客户端去确认服务器的身份。去和服务器端通信之前,先要求服务器发一个服务器证书过来,先看一看这个证书是否是我们认可的CA签署的。这是第一步。如果是我们认可的CA签署的,而且里面的签署信息与我们访问的服务器信息保持一致,那我们就认为这个服务器的身份得到认证了。但是在k8s通信当中,服务器还要认证客户端的身份。因此kubectl自己也应该有一个证书,有自己的私钥,而且这个证书必须是server端认可的CA所签署的证书。客户端身份也要与证书中标识的身份保持一致。所以双方都需要互相做双向证书认证。认证后双方实现ssl加密通信。
其它认证方式
其实认证还有很多种,以上只是比较常见的两种方式。认证插件可能会有很多,但是用户经过任何一个认证插件通过以后,即表示认证通过,无需经由其他插件进行检查。无需进行串行检查。
授权
k8s支持n种授权插件来完成用户的权限检查。k8s 1.6之后开始支持RBAC的认证,此前有ABAC之类的认证。现在用的比较广泛的、安全的就是RBAC。除此之外还有基于节点认证。最重要的是RBAC授权检查机制。Role Based Access Control,基于角色的访问控制机制。这种基于角色的访问控制机制一般只有许可授权,没有拒绝授权,因为默认都是拒绝的。
默认情况下使用kubeadm部署的k8s集群是强制启用了RBAC认证的,此前的操作到现在还没涉及到所谓授权操作控制逻辑,我们一直在使用k8s的最高管理权限,系统管理员cluster admin来实现操作,而不是普通用户。这就相当于在Linux上使用root用户去操作。随后如果使用admin这样的用户,或者其他普通用户,会发现很有可能这些用户只有只读权限,只能看见有哪些资源对象,而无法去创建、编辑和删除资源对象。所以RBAC可以做到非常灵活。
常见的插件:Node、ABAC、RBAC、Webhook。同样的,不需要串行检查。这些插件可以同时启用。我们也可以定义比较独特的访问控制逻辑。
准入控制
一般而言,准入控制本身只是用来定义对应授权检查完后,后续的其他安全检查操作。了解即可。
apiserver如何校验请求
客户端对apiserver发起操作请求,apiserver要识别这个用户能不能执行他请求的操作:
1、用户账号user:username、uid
2、用户所属的组group:
3、extra:用来提供一些额外信息
用户拿着这用户账号去请求时,一定会请求某个特定的api资源,而k8s的apiserver是分了组的,所以到底向哪个组的哪个版本的api资源对象发出请求,必须要进行标识,而标识则通过用户在http协议当中,request path来标识,比如访问apps下的v1版本的某个资源,资源要么属于集群,要么在名称空间下,名称空间属于集群级别的,所有名称空间级别的资源访问时都要指明名称空间:
我们可以基于请求这个url来增删改查,此前执行的kubectl create、delete等事实上都被转换成一个http协议的请求来请求的。
可以演示下,api服务只监听在6443上,它是https的,双向都要做证书认证。我们的curl本地是没有认证的。可以换一种方式。在本地启一个代理。这是kubectl自带的功能。首先kubectl自身有认证能力,其自带的配置文件中拥有认证信息,如下:
所以如果要使用curl请求apiserver是要带着认证信息的,但是curl没有认证信息。在kubectl节点上,运行kubectl proxy命令,就相当于这个家伙在本地作为服务运行了,它监听在我们指定的端口上,为了安全起见,一般监听在127.0.0.1:8080,端口可以自己指定。随后使用curl命令,就访问本地的8080端口,然后由kubectl反向代理至apiserver。
在另一个终端上使用curl访问apiserver:
【注意】:只有是核心群组的资源,访问时是/api/,其他的都是/apis/group_name/version/
只看某个具体的对象:
- HTTP request verb: 对于指定的资源做什么操作。在restful风格中我们称为action,操作,verb也是操作的。
get, post, put, delete API - requests verb: 上面http的请求方法要转成对API对象的请求。也就是映射到k8s语境中对应的请求有哪些:
get, list, create, update, patch, watch, proxy, redirect, delete, deletecollection - Resource: 请求哪个资源,一般是资源的id,或者资源的名称,不同的方法中可能不一样
- Subresource: 有些资源可能还有子资源,restful风格中允许有子资源的
- Namespace:名称空间
- API group:所属的api群组
上述示例中的request path中已经把上述的都表达出来了。
serviceaccount
- 1、集群外部的客户端:比如访问apiserver对外通信的监听地址 6443端口
- 2、Pod中容器里的应用程序访问api
比如运行系统级的服务CoreDNS,需要从api获取用户到底创建什么Service,给Service创建解析记录。创建k8s的图形控制接口Dashboard,这个Dashboard的Pod可以通过Service暴露到集群外部去,可以通过一个浏览器打开它,而用户有可能通过此Dashboard在当前集群中增删改查资源,所以这个Pod也得跟apiserver打交道。Pod和apiserver通信使用的是apiserver在集群内的地址。
客户端与apiserver通信,首先apiserver要把自己的证书传给客户端,客户端去校验apiserver身份,同时apiserver还要去校验客户端身份。服务器apiserver发给它的客户端Pod的时候,证书中标明自己的身份不能是172.16.206.32这个地址,而应该是10.96.0.1,因为客户端访问的是10.96.0.1。所以在apiserver上,如果自己手动建立证书,那么必须确保这个证书的持有者的名称能解析到两条A记录。或者在证书里使用IP地址,这两个地址都得包含进来。另外,Pod客户端也得有证书,apiserver也得验证客户端。此前我们使用kubectl,我们给它提供了配置文件,能进行认证。Pod跟apiserver打交道,怎么认证呢?认证不应该是由Pod内的应用程序来提供的,而是应该由Pod来提供。这些信息在哪里?
整个k8s的apiserver的账号有两类:
- 一类是实实在在的现实中的人类用户。kubectl不会自己去访问,一定是人去发起它去访问的;
- 第二类用户叫 Pod客户端。人用的账号叫useraccount,Pod去连接apiserver时使用的账号叫serviceaccount。事实上每一个Pod都需要与apiserver打交道,只不过它的权限有大有小而已。默认每一个Pod在运行时,它都会有相关信息的。
|
|
随便看一个Pod,里面默认就有一个存储卷下面有个default-token,token是令牌,这就是Pod serviceaccount认证时用到认证信息,因为认证信息毕竟是敏感信息,所以通过secret来定义。并以存储卷的形式关联到Pod之上,从而让Pod内运行的应用通过secret中保存的认证信息来连接apiserver,并完成认证的。这就是每一个名称空间中都有一个默认的secret的原因。让名称空间所有的Pod资源试图去联系apiserver时预置的一个认证信息。所以所有Pod都可以连接apiserver。当然这个secret中包含的认证信息仅仅是获取当前Pod自身的相关属性,不能随便看别人的。如果我们想扩展一个Pod,比如有个pod,打算让它去管其他pod的,那我们就必须得自己手动创建一个serviceaccount,并且创建pod时附加这个我们自己定义的serviceaccount。
serviceaccount示例
serviceaccount也属于标准的k8s资源,可以创建一个serviceaccount,创建完以后,然后在定义Pod时,有个spec.serviceAccountName字段可以加载这个我们定义的serviceaccount。
创建时可以只给个serviceaccount名字,因为serviceaccount本身不带权限,只不过是个账号而已,我们将来可以使用RBAC来实现对这个serviceaccount授予更大的权限。所以授权不属于serviceaccount,我们只不过可以换个专用账号给这个Pod。回头可以给这个专用账号授予更大的权限。肯定不能把权限授予默认的serviceaccount,会导致这个名称空间的所有Pod都会拥有这个serviceaccount所拥有的权限。能认证不代表权限。
此前创建每个资源都是自己手动写yaml文件,现在这是快捷方法了。如果某个资源支持在命令行创建,那么可以使用–dry-run这种方式生成一个框架。可以使用输出重定向保存到文件中,拿来改一改就行了。还有一种方式:
–export:显示的内容比较贴切我们手动写的yaml文件的内容。
|
|
|
|
【补充】:我们写的资源清单,提交给apiserver时,apiserver告诉kubelet,node节点要创建运行一个Pod,而这个Pod中的容器的启动要依赖于一个私有registry之上的镜像时,认证信息是放到secret中的。然后Pod清单文件中,pods.spec.imagePullSecrets引用secret。但是这种认证方式可能有一些缺陷,因此我们也可以在Pod中不使用imagePullSecrets,而是直接使用serviceAccountName。这个sa账号是可以附带认证到私有registry上的secret信息的。这样子我们在Pod配置文件清单中就不会泄露出去secret到底用的是哪个相关信息了。因为只能看见sa。
上面我们已经看到了serviceaccount可以使用token信息,而token其实就是一串base64编码的子串,它实际上是个域共享密钥。我们系统会自动生成,当然你也可以手动创建。
TLS认证,双向认证无非是双方都配置证书和私钥,而且双方都要认可CA颁发的证书。不过这在k8s上用的证书一般都是私有CA做的证书,而且这个私有CA千万不要轻易授权证书出去,因为做的任何一个证书出去,都可以被人家拿来当做客户端来连接apiserver完成认证。所以这个CA应该是专用CA,不会去做任何其他事情。
kubectl认证
kubectl使用配置文件来认证。
kubectl config –help 用来管理kubectl的配置文件。所有连往apiserver的客户端,在认证时,如果我们要基于配置文件来保存认证信息,而不是刚才看到serviceaccount使用token来认证,我们就应该为它配置一个配置文件。
k8s集群中的所有组件,除了apiserver之外的组件,像controller-manager、Scheduler都想要连入apiserver,都需要被apiserver所认证。所以它们都算的上是apiserver的客户端,包括kubectl。那么这每个组件为了能够连入正确的apiserver,需要提供正确的账号、证书、私钥等认证需要的信息,或者token。我们需要把这些信息保存为一个配置文件。这个配置文件有个专门的称呼,叫kubeconfig。是apiserver的客户端连入apiserver时使用的认证格式的客户端配置文件。kubectl也是这样的客户端,它也有自己的配置文件,可以使用kubectl config view来查看。
kubectl的配置文件是 ~/.kube/config。
这个配置文件格式和资源清单文件有点像,解释几个字段:
- clusters:集群列表。可以有多个集群。
- certificate-authority-data: REDACTED 认证到这个集群,这个集群发过来的证书用什么方式去检验
- server: https://172.20.0.70:6443 –集群的apiserver监听的外部地址
- users:用户列表,可以有多个。
- certificate-authority-data: REDACTED –客户端证书
- client-key-data: REDACTED –客户端私钥
- contents:上下文列表。用哪个账号访问哪个集群。
列表,每个content还有个name - current-content:当前上下文。当前是用哪个账号访问哪个集群的。
这个配置文件不单单是用来访问一个集群的,如果有多个k8s集群,但是只有一台机器作为客户端,一会想访问A集群,一会想访问B集群。为了让一个kubectl能控制多个集群,比如下面有3个集群,每个集群上所用的账号可能是不一样的,所以配置文件中有这么几项配置:
- 集群列表,A、B、C三个集群
- 对于每个集群来讲,可能会用到不同的账号,于是有了用户列表。
- 打算用哪个用户访问哪个集群,假设第一个账号和第二个账号都是访问第一个集群的,而第二个集群和第三个集群的账号都是第三个账号,两个集群有着同一个名字的账号,这是有这种可能性的。因此就需要定义contents了,使用哪个账号访问哪个集群。
使用kubeadm创建集群,它会把当前这个集群初始化的过程中创建多个私有CA,其中有一个CA是跟apiserver相关的。如下:
其中ca.crt 和 ca.key。
如果有需要,我们也可以自己用这个ca来签署一个自己自定义的证书和私钥,拿来做认证都没问题。只要是apiserver信任的ca签署的证书,都可以认证连入集群的。
接下来可以来创建自己的账号了,不可以使用这个目录下的其他证书和私钥,主要证书里的CN是用户名,pki下面的证书的里的CN都是已经定义了的名字。这里自做一组证书和私钥,作为另外一个账号去认证到apiserver上的证书文件。
创建的账号不是操作系统账号,而是连接apiserver的账号,需要先创建一个私钥,生成一个签署证书,证书中的证书持有者跟用户的用户名必须保持一致,就是证书的subject。
生成私钥:
生成证书签署请求:
使用CA去签署证书:
查看证书内容,以文本输出:
接下来我们把这个用户账号信息添加到kubectl配置文件中,这个账号是由Kubernetes集群的CA创建的证书,所以认证不会有任何问题。
|
|
–embed-certs=true 表示把证书隐藏起来。
设置个上下文,让wisedu这个用户账号也能访问k8s集群。
设置当前上下文,即设置使用这个用户wisedu访问集群kubernetes:
|
|
这个用户可没有管理员权限。
以上少演示了一个步骤,就是添加集群,因为这里只有一个集群。
现在把当前上下文切换回来,使用一个新的配置文件来演示,有一个选项–kubeconfig可以指定配置文件路径。
设置一个集群: