机制说明
Kubernetes作为一个分布式集群的管理工具,保证集群的安全性是一个很重要的任务,API Server
是集群内部各个组件通信的中介,也是外部控制的入口,因此Kubernetes的安全机制基本就是围绕保护API Server
来设计的,在Kubernetes中,采用了认证(Authentication)、鉴权(Authorization)、准入控制(Admission Controll)三步来保证API Server
的安全。
Kubernetes API访问控制
用户使用kubectl
、客户端库或构造REST请求来访问Kubernetes API
。用户和Kubernetes
服务账户都可以被鉴权访问API。当请求到达API时,会经历多个阶段,如下图所示
在典型的Kubernetes集群中,API服务器在442端口上提供服务,受TLS保护,API服务器出出示证书。该证书可以使用私有证书颁发机构(CA)签名,也可以基于链接到公认的CA的公钥基础架构签名。如果你的集群使用私有证书颁发机构,你需要在客户端的~/.kube/config
文件中提供该证书的副本,以便你可以信任该链接并确认连接没有被拦截。
Authentication
在Kubernetes中,认证分为下面三种
- HTTP Token认证:通过一个Token来识别合法用户
- HTTP Token的认证是用一个很长的特殊编码方式的并且难以被模仿的字符串,即Token来表达客户的一种方式,Token是一个很长的很复杂的字符串,每一个Token对一个一个用户名存放在
API Server
能访问的文件中,当客户端发起API调用请求时,需要在HTTP Header
里放入Token
- HTTP Token的认证是用一个很长的特殊编码方式的并且难以被模仿的字符串,即Token来表达客户的一种方式,Token是一个很长的很复杂的字符串,每一个Token对一个一个用户名存放在
- HTTP Base认证:通过用户名+密码的方式认证
- 用户名+密码采用
Base64
算法进行编码后的字符串放在HTTP Request
中的Heather Authorization
域里发送给服务端,服务端收到后进行编码,获取用户名及密码。
- 用户名+密码采用
- HTTPS证书认证:基于CA根证书签名的客户端身份认证方式。这种方式最为严格,对于上面的两种方式,相当于只做了服务端认证客户端,并没有做客户端对服务端的认证。
需要认证的节点
- Kubernetes组件对
API Server
的访问:kubectl
、Controller Manager
、Scheduler
、kubelet
、kube-proxy
- Kubernetes管理的Pod对容器的访问:Pod(dashboard也是以Pod形式运行)
安全性说明
Controller Manager
、Scheduler
与API Server
在同一台机器,所以直接使用API Server
的非安全端口访问,--insecure-bind-address=127.0.0.1
kubectl
、kubelet
、kube-proxy
访问API Server
都需要证书进行HTTPS双向认证
证书颁发
- 手动签发:通过Kubernetes集群根CA进行签发HTTPS证书
- 自动签发:
kubelet
首次访问API Server
时,使用Token做认证,通过后,Controller Manager
会为kubelet
生成一个证书,以后的访问都是用证书做认证。
kubeconfig
kubeconfig文件包含集群参数(CA证书,API Server
地址),客户端参数(生成的证书和私钥),集群的context信息(集群名称、用户名)。Kubernetes组件通过启动时指定不同的kubeconfig文件可以切换到不同的集群。
查看kubeconfig文件
ServiceAccount
Pod中的容器访问API Server
,因为Pod的创建、销毁是动态的,因此要为它手动生成证书就比较麻烦,Kubernetes使用了SA解决Pod访问API Server
的认证问题。
Secret与SA的关系
Kubernetes设计了一种资源对象叫做Secret,分为两类,一种是用于ServiceAccount
的server-account-token
,另一种是用于保存用户自定义保密信息的Opaque
。ServiceAccount
中包含三个部分。Token、ca.crt、namespace
- Token是使用
API Server
私钥签名的JWT,用于访问API Server
时,Server端认证 - ca.crt根证书,用于Client端验证
API Server
发送的证书 - namespace标识这个
service-account-token
的作用域名空间
查看ServiceAccount
默认情况下,每个namespace都会有一个ServiceAccount
,如果Pod在创建时没有指定ServiceAccount
,就会使用Pod所属的namespace的ServiceAccount
。默认的挂载目录是/run/secrets/kubernetes.io/serviceaccount/
首先我们查看一下当前存在的命名空间
1 | caoyifan@MacBookPro .kube % kubectl get ns |
接着查看命名空间为kube-system
下的pod
1 | caoyifan@MacBookPro ~ % kubectl get pod -n kube-system |
之后进入名为kube-proxy-zw8fj
的pod中
1 | kubectl exec -it kube-proxy-zw8fj -n kube-system -- /bin/sh |
进入到挂载目录并查看token的详细信息
Authorization
在认证过程中,只是通信双方确认了对方是可信的,并且可以相互通信,而鉴权是确定请求方有哪些资源的权限,API Server
目前支持以下几种授权策略(通过API Server
的启动参数--authorization-mode
设置)
- AlwaysDeny:表示拒绝所有的请求,一般用于测试
- AlwaysAllow:允许接收所有请求,如果集群不需要授权流程,则可以采用该策略
- ABAC(Attribute-Based Access Control):基于属性的访问控制,表示使用用户配置的授权规则对用户请求进行匹配和控制
- Webhook:通过调用外部REST服务对用户进行授权
- RBAC(ROle-Based Access Control):基于角色的访问控制,现行默认规则
RBAC授权模式
RBAC在Kubernetes1.5中引入,现行版本成为默认标准,相对其他访问控制方式,拥有以下优势
- 对集群中的资源和非资源均拥有完整的覆盖
- 整个RBAC完全由几个API对象完成,同其他API对象一样,可以用
kubectl
或API进行操作 - 可以在运行时进行调整,无需重启
API Server
RBAC的API资源对象说明
RBAC引入了4个新的顶级资源对象:Role
、ClusterRole
、RoleBinding
、ClusterRoleBinding
。四种对象类型均可以通过kubectl
与API操作。部分关系如下图所示
需要注意的是Kubernetes并不会提供用户管理,对于User、Group、ServiceAccount指定的用户,Kubernetes组件(kubectl、kube-proxy)或是其他自定义的用户在向CA申请证书时,需要提供一个证书请求文件。
1 | { |
API Server
会把客户端证书的CN
字段作为User,把names.O
字段作为Group
kubelet
使用TLS Bootstrapping
认证时,API Server
可以使用Bootstrap Tokens
或者Token authentication file
验证token,无论哪一种,Kubernetes都会为token绑定一个默认的User和Group。Pod使用ServiceAccount
认证时,service-account-token
中的JWT会保存User信息,有了用户信息,再创建一对角色/角色绑定(集群角色/集群角色绑定)资源对象,就可以完成权限绑定了。
Role and ClusterRole
在RBAC API中,Role表示一组规则权限,权限只会增加(累加权限),不存在一个资源一开始就有很多权限而通过RBAC对其进行减少的操作,Role可以定义在一个namespace中,如果想要跨namespace则可以创建ClusterRole
1 | kind: Role |
ClusterRole具有和Role相同的权限角色控制能力,不同的是,ClusterRole是集群级别的,可以用于
- 集群级别的资源控制(例如node访问权限)
- 非资源型 endpoints(例如
/healthz
访问) - 所有命名空间资源控制(例如pods)
1 | kind: ClusterRole |
RoleBinding and ClusterRoleBinding
RoleBinding可以将角色中定义的权限授予用户或用户组,RoleBinding包含一组权限列表(subjects),权限列表中包含有不同形式的待授予权限资源类型(users,groups,service accounts),RoleBinding同样包含对被Bind的Role引用,RoleBinding适用于某个命名空间内授权,而ClusterRoleBinding适用于集群范围内的授权。
例如,要将default命名空间的pod-reader
Role授予elssm用户,此后elssm用户在default命名空间将具有pod-reader
的权限
1 | kind: RoleBinding |
RoleBinding同样可以引用ClusterRole来对当前namespace内用户、用户组或ServiceAccount进行授权,这种操作允许集群管理员在整个集群内定义一些通用的ClusterRole,然后在不同的namespace中使用RoleBinding来引用。
例如,以下RoleBinding引用了一个ClusterRole,这个ClusterRole具有整个集群内对secrets的访问权限,但是其授权用户warry
只能访问development空间中的secrets(因为RoleBinding定义在development命名空间)
1 | kind: RoleBinding |
除此以外,还可以使用ClusterRoleBinding对整个集群中的所有命名空间资源权限进行授权,以下ClusterRoleBinding例子展示了授权manager组内所有用户在全部命名空间中对secrets进行访问
1 | kind: ClusterRoleBinding |
Admission Controll
准入控制是API Server
的插件集合,通过添加不同的插件,实现额外的准入控制规则,甚至于API Server
的一些主要的功能都需要通过Admission Controllers
,比如ServiceAccount
一些插件的功能
- Namespace Lifecycle:防止在不存在的namespace上创建对象,防止删除系统预置的namespace
- LimitRanger:确保请求的资源不会超过所在namespace的LimitRange的限制
- Service Account:实现了自动化添加ServiceAccount
- ResourceQuota:确保请求的资源不会超过资源的ResourceQuota限制
配置节点的安全上下文
我们可以在Pod或其所属容器的描述中通过security-Context选项配置其他与安全性相关的特性,这个选项可以适用于整个pod,或者每个pod中单独的容器。
配置安全上下文可以使我们完成很多事情,例如
- 指定容器中运行进程的用户(用户ID)
- 阻止容器使用root用户运行
- 使用特权模式运行容器,使其对宿主节点的内核具有完全的访问权限
- 通过添加或禁用内核功能,配置细粒度的内核访问权限
- 设置SELinux(安全增强型Linux)选项,加强对容器的限制
- 阻止进程写入容器的根文件系统
运行没有配置安全上下文的pod
创建一个名为pod-with-defaults
的YAML文件,内容如下
1 | apiVersion: v1 |
启动pod
1 | caoyifan@MacBookPro ~ % kubectl create -f pod-with-defaults.yaml |
查看这个容器中的用户ID和组ID以及它所属的用户组
1 | caoyifan@MacBookPro ~ % kubectl exec pod-with-defaults -- id |
发现这个容器在用户ID(uid)为0的用户,用户组ID(gid)为0的用户组下运行,它同样还属于一些其他的用户组。
运行指定用户的pod
为了使用一个与镜像中不同的用户ID来运行pod,需要设置该pod的securityContext.runAsUser
选项,可以通过以下代码来运行一个使用guest用户运行的容器。
创建一个名为pod-as-user-guest
的YAML文件,内容如下,其中id405对应guest用户
1 | apiVersion: v1 |
启动pod后查看,发现该容器在guest用户下运行
1 | caoyifan@MacBookPro ~ % kubectl exec pod-as-user-guest -- id |
阻止容器以root用户运行
创建一个名为pod-run-as-non-root
的YAML文件,内容如下
1 | apiVersion: v1 |
启动pod后查看pod信息
1 | caoyifan@MacBookPro DockerTest % kubectl get pod pod-run-as-non-root |
发现pod并没有运行,通过describe查看具体信息
1 | caoyifan@MacBookPro DockerTest % kubectl describe pod pod-run-as-non-root |
运行使用特权模式的pod
为了获取宿主机内核的完整权限,该pod需要在特权模式下运行,这可以通过将容器的securityContext
中的privileged
设置为true
实现。
创建一个名为pod-privileged
的YAML文件,内容如下
1 | apiVersion: v1 |
部署好这个pod之后,我们与之前部署的非特权模式的pod做对比。
首先使用之前的名为pod-with-defaults
的pod,通过列出/dev
目录下文件的方式查看非特权模式容器中的设备,如下所示
1 | caoyifan@MacBookPro DockerTest % kubectl exec pod-with-defaults -- ls /dev |
接下来我们列出特权模式容器/dev
目录下的文件,可以发现,特权模式的pod可以看到宿主节点上的所有设备。
1 | caoyifan@MacBookPro DockerTest % kubectl exec pod-privileged -- ls /dev |
为容器单独添加内核功能
相比于让容器运行在特权模式下以给予其无限的权限,一个更安全的做法是只给予它是用真正需要的内核功能的权限,Kubernetes允许为特定的容器添加内核功能,或禁用部分内核功能,以允许对容器进行更加精细的权限控制,从而限制攻击之恶潜在侵入的一些影响。
例如,一个容器通常不允许修改系统时间,我们可以通过修改名为pod-with-defaults
的pod中的时间来验证。
1 | caoyifan@MacBookPro DockerTest % kubectl exec pod-with-defaults -- date +%T -s "12:00:00" |
如果需要允许容器修改系统时间,可以在容器的securityContext.capabilities
里add一项名为CAP_SYS_TIME
的功能。
首先创建一个名为pod-add-settime-capability
的YAML文件,内容如下。
1 | apiVersion: v1 |
注意Linux内核功能的名称通常以CAP_
开头,但是在pod spec
中指定内核功能时,必须省略CAP_
前缀
启动好pod之后,在新的容器中运行同样的命令,发现可以成功修改系统时间
1 | caoyifan@MacBookPro DockerTest % kubectl exec pod-add-settime-capability -- date +%T -s "12:00:00" |
在容器中禁用内核功能
默认情况下,容器拥有CAP_CHOWN
权限,允许进程修改文件系统中文件的所有者。如下示例,可以在pod-with-defaults
中将/tmp
目录的所有者改为guest用户
1 | caoyifan@MacBookPro DockerTest % kubectl exec pod-with-defaults -- chown guest /tmp |
为了阻止容器的这种行为,可以在容器的securityContext.capabilities
里drop一项名为CHOWN
的功能
首先创建一个名为pod-drop-chown-capability
的YAML文件,内容如下
1 | apiVersion: v1 |
禁用CHOWN
内核功能后,则不允许在这个pod中修改文件所有者
1 | caoyifan@MacBookPro DockerTest % kubectl exec pod-drop-chown-capability -- chown guest /tmp |
阻止对容器根文件系统的写入
因为安全原因,可以需要组织容器中的进程对容器的根文件系统进行写入,仅允许他们写入挂载的存储卷。我们可以通过将容器的securityContext.readOnlyRootFilesystem
设置为true来实现。
创建一个名为pod-with-readonly-filesystem
的YAML文件,内容如下
1 | apiVersion: v1 |
这个pod中的容器虽然以root用户运行,拥有/
目录的写权限,但在该目录下写入一个文件会失败,如下所示
1 | caoyifan@MacBookPro DockerTest % kubectl exec pod-with-readonly-filesystem -- touch /new-file |
但是对于挂载的卷的写入时允许的,如下所示
1 | caoyifan@MacBookPro DockerTest % kubectl exec pod-with-readonly-filesystem -- touch /volume/new-file |