Kubernetes API访问控制
kubernetes主要通过API server对外提供服务,对于这样的系统来说,请求访问的安全性是非常重要的考虑因素。如果不对请求加以限制,那么会导致请求被滥用,甚至黑客的攻击。
apiserver的本质是一个web服务器,底层代码是基于go-restful这个web框架搭建的。传统的web服务器在安全方面一般都会提供认证、授权机制,以过滤器的方式实现。apiserver此外还提供了准入控制。
kubernetes对于通过https访问API的请求会经过三个步骤,前两个是认证和授权,认证解决用户是谁的问题,授权解决用户能做什么的问题。第三个是准入控制,也能在一定程度上提高安全性,不过更多的是资源管理方面的作用。
一般到达apiserver的请求分为两类:
- 用户对apiserver的请求(kubectl客户端、客户端库或者构造REST请求来访问kubernetes API);
- pod中业务逻辑与apiserver之间的交互。
当请求到达API时,会经历身份认证、鉴权和准入控制三个步骤。当然只有在通过https访问的时候才会经历这三个步骤,http不需要鉴权。
- Authentication 认证阶段:判断请求用户是否为能够访问集群的合法用户。如果用户是个非法用户,那 apiserver 会返回一个 401 的状态码,并终止该请求;
- 如果用户合法的话,我们的 apiserver 会进入到访问控制的第二阶段 Authorization:鉴权阶段。在该阶段中 apiserver 会判断用户是否有权限进行请求中的操作。如果无权进行操作,apiserver 会返回 403 的状态码,并同样终止该请求;
- 如果用户有权进行该操作的话,访问控制会进入到第三个阶段:AdmissionControl。在该阶段中 apiserver 的 admission controller 会判断请求是否是一个安全合规的请求。如果最终验证通过的话,访问控制流程才会结束。
认证
当用户(真人用户)访问集群(例如使用kubectl命令)时,apiserver会将用户认证为一个特定的user account(目前通常时admin,除非系统管理员自定义了集权配置)。
除此之外,pod容器中的进程也可以与apiserver联系,当pod联系apiserver时,它们会被认证为一个特定的service account(例如default)。
用户认证
kubernetes中用户分为两类:kubernetes管理的服务账号和普通用户。
普通用户(normal users)
Kubernetes 没有自身的用户管理能力,我们无法像操作 Pod 一样,通过 API 的方式创建删除一个用户实例。同时我们也无法在 ETCD 中找到用户对应的存储对象。普通用户可由外部系统管理。
那在Kubernetes的访问控制流程中用户模型是如何产生的呢?答案就在请求方的访问控制凭证中,也就是我们平时使用的kube-config中的证书,或者是Pod中引入的Service Account。经过Kubernetes认证流程之后,apiserver 会将请求中凭证中的用户身份转化为对应的 User 和 Groups 这样的用户模型。在随后的鉴权操作和审计操作流程中,apiserver 都会使用到改用户模型实例。
kubernetes能够使用集群证书机构为用户签名合法证书(集群给普通用户颁发证书),拥有此证书的用户即为合法用户。基于这样的配置,kubernetes使用证书中的subject
字段来确定用户名,然后交给RBAC角色访问控制子系统。
Kubernetes支持的认证方式主要包括:
- HTTP Base 认证
该认证方式下,通过用户名密码的方式,管理员会将Username和Password组成的白名单放置在 apiserver 读取的静态配置文件上面进行认证,该方式一般用于测试场景,在安全方面是不推荐且不可拓展的一种方式。
- TLS认证(X509 证书认证)
TLS是安全传输层协议,包含两部分:TLS记录协议和TLS握手协议,TLS记录协议主要保证传输过程中信息传输的完整性和私密性,这一部分通过协商后的密钥来加密数据。TLS握手协议主要是为了认证对方的身份、协商密钥。在握手协议中主要涉及到身份认证。
该方式是 apiserver 中相对应用较多的使用方式,首先访问者会使用由集群 CA 签发的,或是添加在 apiserver Client CA 中授信 CA 签发的客户端证书去访问 apiserver。apiserver 服务端在接收到请求后,会进行 TLS 的握手流程。
除了验证证书的合法性,apiserver 还会校验客户端证书的请求源地址等信息。开启双向认证,X509 认证是一个比较安全的方式,也是 Kubernetes 组件之间默认使用的认证方式,同时也是 kubectl 客户端对应的 kube-config 中经常使用到的访问凭证。
- Bearer Tokens(JSON Web Tokens)
- Service Account
- OpenID Connect
- Webhooks
该方式的 Tokens 是通用的 JWT 的形式,其中包含了签发者、用户的身份、过期时间等多种元信息。它的认证方式也是常见的私钥加签,公钥验签的一个基本流程。基于 Token 的认证使用场景也很广泛,比如 Kubernetes Pod 应用中经常使用到的 Service Account,其中就会自动绑定一个签名后的 JWT Token 用于请求 apiserver。
另外 apiserver 还支持基于 OpenID 协议的 Token 认证,可以通过对 apiserver 的配置连接一个指定的外部 IDP,同时可以通过 Keycloak,Dex 这样的开源服务来管理 IDP,请求者可以按照自己熟悉的方式在原身份认证服务上进行登录认证,并最终返回一个相应的 JWT token,为了后面的 apiserver 的鉴权流程。
除此之外,还可以使用 Webhooks 的方式,将请求的 Token 发送到指定外部服务进行 Token 的验签。
服务账号(service accounts)
当pod中进程访问apiserver的时候是通过Service Account来通过身份认证的。其是唯一可以通过 kubernetes API 创建和管理的访问凭证。
当pod中的客户端调用apiserver时,在http header中传递了一个token字符串,类似于http token认证方式,但不同的是:
- 这个toke内容来自pod里指定路径下的一个文件(/run/secrets/kubernetes.io/serviceaccount/token),这种token是动态生成的,确切地说,是由kubenetes controller进程用apiserver地私钥(--service-account-private-key-file指定地私钥)签名生成地一个JWT Secret。
- 官方提供的客户端REST框架代码里,通过HTTPS方式与apiserver建立连接后,会用pod里指定路径下的一个CA证书(/run/secrets/kubenetes.io/serviceaccount/ca.crt)验证apiserver发来的证书,验证是否被CA证书签名的合法证书。
- apiserver收到这个token后,采用自己的私钥对token进行合法验证。
pod与apiserver通信的认证过程中涉及到了三个文件:
- /run/secrets/kubernetes.io/serviceaccount/token
- /run/secrets/kubernetes.io/serviceaccount/ca.crt
- /run/secrets/kubernetes.io/serviceaccount/namespace
这三个文件起到了类似Secret(秘密凭据)的作用,所以被称为kubenetes secret对象,其从属于Service Account对象的一部分,一个Service Account对象可以包含不同的Secret对象,分别用于不同目的的认证活动。
当一个namespace创建完成之后,会同时在该namespace下生成名为default的一个Service Account,这个Service Account对象里有一个名为Tokens的可以被当作Volume一样被Mount到pod里的Secret,该Secret对象包含三个数据项:token、ca.crt、namespace。当pod启动时,这个secret自动被Mount到pod的指定目录下(上述的文件目录),用来协助完成pod中的进程访问apiserver时的身份认证过程。
API 请求则或者与某普通用户相关联,或者与某服务账号相关联,亦或者被视作匿名请求。这意味着集群内外的每个进程在向 API 服务器发起请求时都必须通过身份认证,否则会被视作匿名用户。这里的进程可以是在某工作站上 输入 kubectl
命令的操作人员,也可以是节点上的 kubelet
组件,还可以是控制面的成员。
_疑问:pod本是承载业务需求,为什么pod中的进程或者服务要访问k8s集群?为每个pod提供service account,不是给攻击者可乘之机了吗?
使用kubeconfig
为了方便用户访问apiserver,常用的方式是采用kubeconfig文件。这个配置文件里描述了集群、上下文和用户信息。kubeconfig是用户本地连接Kubernetes集群使用的重要访问凭证。
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUM1ekNDQWMrZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREFWTVJNd0VRWURWUVFERXdwcmRXSmwKY201bGRHVnpNQjRYRFRJeE1ETXhPVEE1TlRRMU5Gb1hEVE14TURNeE56QTVOVFExTkZvd0ZURVRNQkVHQTFVRQpBeE1LYTNWaVpYSnVaWFJsY3pDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBTHNmCkFISlJXUzJFNHpHenljU1Jnc1o0M295VmlFREpwSTR3NjMycWpsWmROZ2JHMFJEVkVBdmJzb3p5b3luQjV3WnUKTDdidm9mbmNSRGNhdnBxZlpVK3ROaThoOEZFV01mS3doQlVCdk5Kb3VQc0p3WE1GYi9CZjVBRFNuNlN2TGhZQgo2bWM3TzVWak8xcWdxUURVR1BoRkJxRDBTckFwMk9sNmV4OVdyYXRXYlg3RDVVR2JKa2tGY1NBYUZjeHlIT0R5CnZkV2Z4bDBJdVdVajduNHZWb29KVGUwSWxOOGpnLzdTYlhFcFpoczljOGhpbldrbFhPOHFRam5VYVZ0eFAvc2kKZEY1Y2toZ0tST0Q0cTNuTElqeE9qZUpSVXRuTUxzOGZPNG40cG4zb1l4SjhhdWFXV25zT2hLYm1jWVZhb3BjRgpYbXU4L0MrWU9IUUtIZVpGWVpjQ0F3RUFBYU5DTUVBd0RnWURWUjBQQVFIL0JBUURBZ0trTUE4R0ExVWRFd0VCCi93UUZNQU1CQWY4d0hRWURWUjBPQkJZRUZCUFU4cFQxK3RaSlBsOWlmVW9vTmhIZ2RmOUZNQTBHQ1NxR1NJYjMKRFFFQkN3VUFBNElCQVFCei9oWFhOZDZSR0V4d09DNDg1bW9xK2F6TVJqQkl2MGJsN1VaeUNaZWs4MVF3MjJqaApmOGZXUEZYZGQrSCtYSDViOThCVkVHU1lRSWYyR1IxVmxDYXRKc0M2L3gwUzdPdlkzK09haHBTd3NDZDE1Y09xCms1dWtva2pZek81QXNwdzBJdTdGOEFncTFaL3djbDBFcUZ3bnNRcWUzOU1Tdis5cVBLYVJ2YmRRZjBZUWc5bkYKWjdvaS9lWjJvcFVrZjZ4QURKK1VkUExKRkVTOUswS1B2MCtjV0V3Zm1LVGhpR0pmZXl0aDk3YUdtUlg4NkthYQowV0lUTFEyWFY3bFNJdy94V21vdEgzcCtSeDQxWmRuTTJnNmx2dXR5YzJRUUFPMEFwbmk3WmhPS1k3V3ZnK2d4CnlGNXRLcWdxQi9sbi9wTk5NcTVQcmM1V1AwWnRkaTRQTlB6RQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==
server: https://10.0.0.200:6443
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: ... # 用户的证书,有kubenetes签发
client-key-data: ...
配置本地kubeconfig:
- 下载集群ca
- 使用kubectl添加集群连接信息
kubectl config set-cluster sandbox --certificate-authority=ca.pem --embed-certs=true --server=https://ip:6443
- 将新的密钥信息加入kubectl配置中
kubectl config set-credentials name --client-certificate=name.crt --clent-key=name.key --embed-certs=true
- 添加新的context入口到kubectl配置中
kubectl config set-context sandbox-name --cluster=sandbox --user=name
使用kubeconfig:
- 设置kubeconfig环境变量
export KUBECONFIG_SAVED=$KUBECONFIG
export KUBECONFIG=$KUBECONFIG:config-demo:config-demo-2
kubectl config view
- 将$HOME/.kube/config追加到KUBECONFIG环境变量设置中
export KUBECONFIG=$KUBECONFIG:$HOME/.kube/config
- 多集群config的合并和切换
KUBECONFIG=file1:file2:file3 kubectl config view --merge --flatten > ~./kube/all-config export (不同的config的name需要唯一)
KUBECONFIG=~/.kube/all-config
kubectl config get-contexts
kubectl config use-context [your-context]
鉴权
鉴权是apiserver完成第一步认证后,需要对用户的权限进行限制的操作。
授权方式也有很多:AlwaysDeny、AlwaysAllow、ABAC、RBAC、node等,高版本默认使用RBAC和node。
RBAC(Role-Based Access Control)
RBAC三要素:
- 谁(subject),可以是开发人员、集群管理员,也可以是系统组件进程,或者是pod中的逻辑进程;
- 对象资源,API Resource,kubenetes中的各种资源。
- 操作,对请求的资源进行那些操作,增删改查、list、get、watch等。
要启用RBAC,请使用--authorization-mode=RBAC
启动API Server。RBAC 是官方才 1.6 版本之后推荐使用的授权方式,因为它比较灵活,而且能够很好地实现资源隔离效果。
这种方法引入了一个重要的概念:Role,翻译成角色。所有的权限都是围绕角色进行的,也就是说角色本身会包含一系列的权限规则,表明某个角色能做哪些事情。比如管理员可以操作所有的资源,某个 namespace 的用户只能修改该 namespace的内容,或者有些角色只允许读取资源。角色和 pods、services 这些资源一样,可以通过 API 创建和删除,因此用户可以非常灵活地根据需求创建角色。
Role和ClusterRole
在RBAC API中,一个role包含了一套表示一组权限的规则,用户在指定的命名空间资源上可以进行那些操作。
权限以纯粹的累加形式累积(没有”否定”的规则)。一个Role对象只能用于授予对某一单一命名空间中资源的访问权限。
以下示例描述了”default”命名空间中的一个Role对象的定义,用于授予对pod的读访问权限:
kind: Role
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
namespace: default
name: pod-reader
rules:
- apiGroups: [""] # Role模板中需要指定apiGroup名称,空字符串""表明使用core API group
resources: ["pods"] # 访问哪些资源
verbs: ["get", "watch", "list"] # 有哪些可操作的权限
ClusterRole对象可以授予与Role对象相同的权限,但由于它们属于集群范围对象, 也可以使用它们授予对以下几种资源的访问权限:
- 集群范围资源(例如节点,即node)
- 非资源类型endpoint(例如”/healthz”)
- 跨所有命名空间的命名空间范围资源(例如pod,需要运行命令
kubectl get pods --all-namespaces
来查询集群中所有的pod)
下面示例中的ClusterRole定义可用于授予用户对某一特定命名空间,或者所有命名空间中的secret(取决于其绑定方式)的读访问权限:
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
# 鉴于ClusterRole是集群范围对象,所以这里不需要定义"namespace"字段
name: secret-reader
rules:
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "watch", "list"]
RoleBinding与ClusterRoleBinding
当完成一个namespace下的Role创建后,还需要建立主体(object)与Role的绑定。
RoleBinding将一个Role中定义的各种权限授予一个或者一组主体(用户)。 RoleBinding包含了一组相关主体(即subject, 包括用户——User、用户组——Group、或者服务账户——Service Account)以及对被授予角色的引用。 在命名空间中可以通过RoleBinding
对象授予权限,而集群范围的权限授予则通过ClusterRoleBinding
对象完成。
RoleBinding
可以引用在同一命名空间内定义的Role
对象。 下面示例中定义的RoleBinding
对象在default命名空间中将pod-reader角色授予用户jane。 这一授权将允许用户jane从default命名空间中读取pod。
# 以下角色绑定定义将允许用户"jane"从"default"命名空间中读取pod。
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: read-pods
namespace: default
subjects: # 绑定谁
- kind: User # 可以是User、Group、Service Account,支持绑定多个对象
name: jane
apiGroup: rbac.authorization.k8s.io
roleRef: # 哪个Role,只能指定为唯一的Role
kind: Role
name: pod-reader
apiGroup: rbac.authorization.k8s.io
注意:
Role和ClusterRole模板中唯一不同的点是一个需要指定namespace,一个不需要指定。
RoleBinding和ClusterRoleBinding模板也是在namespce和roleRef中的权限对象上不同,其他定义格式是一样的。
准入控制
判断你的操作是否符合集群的要求,这是一种更灵活的管控机制,用户还可以根据自己需求定义准入插件来管理集群。kubenetes中将准入模块分为三种,validating验证型、mutating修改型、二者兼有,准入的默认配置是NodeRestriction。
授权和准入的区别是:
- 运行时机不同。认证/授权模块相当于是web开发中的filter,是针对请求头和证书进行分析操作,运行在具体的handle处理逻辑之前,而准入模块可以操作具体的请求体,是在具体的处理逻辑中。
- 管控范围不同。授权模块只管控用户有没有操作的权限,具体操作的细节并不关注。准入模块可以管控用户申请的资源大小,可以细粒度地划分用户权限,可以检查用户创建的命名空间是否存在等等。
- 运行逻辑不同。准入模块之间是逻辑与的关系,设定之后用户的任何请求必须满足所有的准入检查,如果一个插件不满足就会拒绝请求,认证/授权模块之间是逻辑或的关系,只要满足一个就可以通过。
API接口定义
方便学习API接口,我们使用curl命令,得到以JSON方式返回的api信息。
- 为匿名用户绑定管理员权限
kubectl create clusterrolebinding test:anonymous --clusterrole=cluster-admin --user=system:anonymous
- curl访问api
# curl -k https:localhost:6443/api
{
"kind": "APIVersions",
"versions": [
"v1"
],
"serverAddressByClientCIDRs": [
{
"clientCIDR": "0.0.0.0/0",
"serverAddress": "10.0.0.200:6443"
}
]
}
查看目前支持的资源类型
# curl -k https:localhost:6443/api/v1
{
"kind": "APIResourceList",
"groupVersion": "v1",
"resources": [
...
{
"name": "namespaces",
"singularName": "",
"namespaced": false,
"kind": "Namespace",
"verbs": [
"create",
"delete",
"get",
"list",
"patch",
"update",
"watch"
],
"shortNames": [
"ns"
],
"storageVersionHash": "Q3oi5N2YM8M="
},
...
{
"name": "nodes",
"singularName": "",
"namespaced": false,
"kind": "Node",
"verbs": [
"create",
"delete",
"deletecollection",
"get",
"list",
"patch",
"update",
"watch"
],
"shortNames": [
"no"
],
"storageVersionHash": "XwShjMxG9Fs="
},
...
{
"name": "pods",
"singularName": "",
"namespaced": true,
"kind": "Pod",
"verbs": [
"create",
"delete",
"deletecollection",
"get",
"list",
"patch",
"update",
"watch"
],
"shortNames": [
"po"
],
"categories": [
"all"
],
"storageVersionHash": "xPOwRZ+Yhw8="
},
{
"name": "services",
"singularName": "",
"namespaced": true,
"kind": "Service",
"verbs": [
"create",
"delete",
"get",
"list",
"patch",
"update",
"watch"
],
"shortNames": [
"svc"
],
"categories": [
"all"
],
"storageVersionHash": "0/CO1lhkEBI="
},
...
]
}
根据这些资源名称,可以通过API进行访问
# curl https://localhost:6443/api/v1/pods
# curl https://localhost:6443/api/v1/nodes
# curl https://localhost:6443/api/v1/services
proxy api接口
这类接口的作用是代理REST请求,即apiserver把收到的REST请求转发到某个node上的kubelet守护进程的REST端口上,由该kubelet进程负责响应。
该类接口路径为:
- /api/v1/proxy/nodes/{name}/pods # 列出指定节点内所有Pod信息
- /api/v1/proxy/nodes/{name}/stats # 列出指定节点物理资源的统计信息
- /api/v1/proxy/nodes/{name}/spec # 列出指定节点的概要信息
如果kubelet进程在启动过程中包含--enable-debugging-handlers = true参数,那么proxy api还会增加以下接口:
- /api/v1/proxy/nodes/{name}/run # 在节点上运行某个容器
- /api/v1/proxy/nodes/{name}/exec # 在节点上的某个容器中运行某条命令
- /api/v1/proxy/nodes/{name}/attach # 在节点上attach某个容器
- /api/v1/proxy/nodes/{name}/logs # 列出节点上的各类日志信息
- ...
另外,可以通过proxy api里关于pod相关接口访问到pod中某个容器提供的服务。
- /api/v1/proxy/namespace/{namespace}/pod/{name}/{path:*} # 访问pod的某个服务接口
- /api/v1/proxy/namespace/{namespace}/pod/{name} # 访问pod
示例:
首先得到pod的名字
# kubectl get pods
# curl -k https://localhost:6443/api/v1/proxy/namespaces/{namespace}/pods/{name}
也可以在浏览器中输入此地址
所以可以通过proxy api的方式去访问pod容器的服务,这种方式多用于管理的目的,比如逐一排查service的pod副本,检查哪些pod的服务存在异常问题。
除此之外,proxy api也有service的proxy接口,其接口定义与pod的接口定义基本一样:
/api/vi/proxy/namespaces/{namespace}/services/{name}
示例:
https://10.0.0.200:6443/api/v1/proxy/namespaces/default/services/myweb/demo/
API服务器在两个端口上提供服务:
- localhoost端口:
- 用于测试和引导,以及主控节点上的其他组件(调度器,控制器管理器)与API通信。
- 没有TLS
- 默认为端口8080,使用--insecure-port进行更改
- 默认IP为localhost,使用--insecure-bind-address进行更改
- 请求绕过身份认证和鉴权模块
- 由准入控制模块处理的请求
- 收需要访问的主机的保护
- 安全端口:
- 尽可能使用
- 使用TLS。用--tls-cert-file设置证书,用--tls-privite-key-file设置密钥
- 默认端口6443,使用--secure-port更改
- 默认IP是第一个非本地网络接口,使用--bind-adress更改
- 请求须经身份认证和鉴权组件处理
- 请求须经准入控制模块处理