Kubernetes笔记(9) - 认证、授权与准入控制
访问控制概述
API Server作为Kubernetes集群系统的网关,是访问及管理资源对象的唯一入口,其余所有需要访问集群资源的组件,包括kube-controller-manager、kube-scheduler、kubelet和kube-proxy等集群基础组件、CoreDNS等集群的附加组件以及此前使用的kubectl命令等都要经由此网关进行集群访问和管理。
API Server会对每一次的访问请求进行合法性检验,包括用户身份鉴别、操作权限验证以及操作是否符合全局规范的约束等。
Kubernetes以插件的方式进行对请求的认证、授权:通过认证插件来识别客户端身份;然后通过一到多个授权插件来检查用户是否有权限执行其发出的资源操作请求;最后还要通过一到多个准入控制插件的遍历检测,例如检查目标Namespace资源对象是否存在、是否违反系统资源限制等等。
用户账号与服务账号
认证插件从客户端请求中提取出的用户及所属组的信息并不会被保存,它们仅仅用于检验用户是否有权限执行其所请求的操作。客户端访问API服务的途径通常有三种:kubectl、客户端库或者直接使用REST接口进行请求,而可以执行此类请求的主体也被Kubernetes分为两类:
- Pod对象,对应Service Account(服务账号),是指由Kubernetes API管理的账号,用于为Pod之中的服务进程在访问Kubernetes API时提供身份标识(identity)。Service Account通常要绑定于特定的名称空间,它们由API Server创建,或者通过API调用手动创建,附带着一组存储为Secret的用于访问API Server的凭据。
- 操作人员,对应User Account(用户账号),是指由独立于Kubernetes之外的其他服务管理的用户账号,例如由管理员分发的密钥。
用户组
User Account用于人工操控,作用于系统全局,其名称必须全局唯一;Service Account隶属于名称空间,仅用于实现某些特定的操作任务,更轻量。这两类账号都可以隶属于一个或多个用户组。用户组只是用户账号的逻辑集合,它本身并没有操作权限,但附加于组上的权限可由其内部的所有用户继承,以实现高效的授权管理机制。
Kubernetes内建了一些特殊用途的组:
- system:unauthenticated,未能通过任何一个授权插件检验的账号,即未通过认证测试的用户所属的组。
- system:authenticated,认证成功后的用户自动加入的一个组,用于快捷引用所有正常通过认证的用户账号。
- system:serviceaccounts,当前系统上的所有ServiceAccount对象。
- system:serviceaccounts:(namespace) 特定名称空间内所有的Service Account对象。
认证
身份验证插件负责对API请求的认证,支持的认证方式有:客户端证书、承载令牌(bearer tokens)、 身份验证代理(authenticating proxy)、HTTP basic认证等。
API Server接收到访问请求时,它将调用认证插件尝试提取如下信息:
- Username:用户名,如kubernetes-admin等。
- UID:用户的数字标签符,用于确保用户身份的唯一性。
- Groups:用户所属的组,用于权限指派和继承。
- Extra:键值数据类型的字符串,用于提供认证时需要用到的额外信息。
API Server支持同时启用多种认证机制,但至少应该分别为ServiceAccount和User Account各自启用一个认证插件。同时启用多种认证机制时,认证过程会以串行的方式进行,直到一种认证机制成功完成即结束。
授权
成功通过身份认证后的操作请求还需要转交给授权插件进行许可权限检查,以确保其拥有执行相应的操作的许可。主要支持使用四类内建的授权插件:
- Node:基于Pod资源的目标调度节点来实现的对kubelet的访问控制。
- ABAC:attribute-based access control,基于属性的访问控制。
- RBAC:role-based access control,基于角色的访问控制。
- Webhook:基于HTTP回调机制通过外部REST服务检查确认用户授权的访问控制。
此外还有AlwaysDeny和AlwaysAllow两个特殊的授权插件,其中AlwaysDeny仅用于测试,而AlwaysAllow则用于关闭授权检查。
Service Account的管理与应用
API Server是访问及管理资源对象的唯一入口,它会对每一次访问请求进行合法性检验。每个Pod对象都会对应一个Service Account,若创建Pod资源时未予以明确指定,则名为ServiceAccount的准入控制器会为其自动附加当前名称空间中默认的服务账户,其名称通常为default:
✗ kubectl describe serviceaccount default
Namespace: default
...
Mountable secrets: default-token-m6bz5
Tokens: default-token-m6bz5
Pod通过存储卷挂载的方式使用这些信息,位置在/var/run/secrets/kubernetes.io/serviceaccount
✗ kubectl describe pod configmap-vol
...
Mounts:
/var/run/secrets/kubernetes.io/serviceaccount from default-token-m6bz5 (ro)
...
Volumes:
default-token-m6bz5:
Type: Secret (a volume populated by a Secret)
SecretName: default-token-m6bz5
Optional: false
挂载点目录中存在三个文件:ca.crt、namespace和token,其中,token文件保存了Service Account的认证token,容器中的进程使用它向API Server发起连接请求,进而由认证插件完成用户认证并将其用户名传递给授权插件。
Service Account自动化
Kubernetes系统通过三个独立的组件间的相互协作来实现服务账户的自动化,三个组件具体为:Service Account准入控制器、令牌控制器(token controller)和Service Account账户控制器。
ServiceAccount账户控制器负责为名称空间管理相应的资源,并确保每个名称空间中都存在一个名为“default”的Service Account对象。
ServiceAccount准入控制器是API Server的一部分,负责在创建或更新Pod时对其按需进行Service Account对象相关信息的修改,包括如下操作:
- 若Pod没有明确定义使用的ServiceAccount对象,则将其设置为“default”
- 确保Pod明确引用的ServiceAccount已存在,否则请求将被拒绝
- 若Pod对象中不包含ImagePullSecerts,则把ServiceAccount的ImagePullSecrets添加于其上
- 为带有访问API的令牌的Pod添加一个存储卷
- 为Pod对象中的每个容器添加一个volumeMounts,挂载至/var/run/secrets/kubernetes.io/serviceaccount
令牌控制器是controller-manager的子组件,工作于异步模式。其负责的任务有:
- 监控Service Account的创建操作,并为其添加用于访问API的Secret对象
- 监控Service Account的删除操作,并删除其相关的所有Service Account令牌密钥
- 监控Secret对象的添加操作,确保其引用的Service Account已存在,并在必要时为Secret对象添加认证令牌
- 监控Secret对象的删除操作,以确保删除每个ServiceAccount中对此Secret的引用。
创建服务账户
Service Account是Kubernetes API上的一种资源类型,它隶属于名称空间,每个名称空间都有一个名为default的默认资源对象,可通过kubectl get serviceaccounts --all-namespaces
查看。
每个Pod对象只能附加其所属名称空间中的一个Service Account资源,但一个Service Account资源可由多个Pod对象共享使用。创建Pod资源时,可使用“spec.serviceAccountName”属性直接指定要使用的Service Account对象,如果省略则使用默认资源对象。
下面的配置清单创建名为sa-demo的服务账户,其余的信息则交由系统自动生成,包括认证令牌的Secret对象:
apiVersion: v1
kind: ServiceAccount
metadata:
name: sa-demo
调用imagePullSecret资源对象
ServiceAccount资源还可以基于spec.imagePullSecret字段附带一组docker-registry资源列表,用于在进行容器创建时,从某私有镜像仓库下载镜像文件之前进行服务认证。比如下面的清单指定了在Kubernetes笔记(8) - ConfigMap和Secret中创建的local-registry:
apiVersion: v1
kind: ServiceAccount
metadata:
name: sa-demo
imagePullSecrets:
- name: local-registry
RBAC:基于角色的访问控制
在RBAC访问控制机制中,User是一个可以独立访问计算机系统中的数据或者用数据表示的其他资源的主体;Role是指一个组织或任务中的工作或者位置,它代表了一种权利、资格和责任;Permission是允许执行的操作。
K8s使用的RBAC授权插件支持Role和ClusterRole两类角色,其中Role作用于名称空间级别,用于定义名称空间内的资源权限集合,而ClusterRole则用于组织集群级别的资源权限集合,它们都是标准的API资源类型。
Role和RoleBinding
Role仅是一组许可(permission)权限的集合,它描述了对哪些资源可执行何种操作,资源配置清单中使用rules字段嵌套授权规则,下面的Role对象的配置清单示例,设定了读取、列出及监视Pod对象的权限:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: pods-reader
rules:
- apiGroups: [""]
resources: ["pods", "pods/log"]
verbs: ["get", "list", "watch"]
没有指定namespace,默认创建到了default下,role需要“绑定”(Role-Binding)到主体(如user、group或service account)之上才能发生作用。
RoleBinding用于将Role中定义的权限赋予一个或一组用户,它由一组主体,以及一个要引用来赋予这组主体的Role或ClusterRole组成,比如下面的RoleBinding配置清单将pods-reader角色赋给了用户kube-user1,于是kube-user1就有了对应的权限:
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: role-binding-1
subjects:
- kind: User
name: kube-user1
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: pods-reader
apiGroup: rbac.authorization.k8s.io
ClusterRole和ClusterRoleBinding
ClusterRole的许可授权作用于整个集群,因此常用于控制Role无法生效的资源类型,这包括集群级别的资源(如Nodes)、非资源类型的端点(如/healthz)和作用于所有名称空间的资源(例如,跨名称空间获取任何资源的权限)。
集群级别的角色资源ClusterRole资源除了能够管理与Role资源一样的许可权限之外,还可以用于集群级组件的授权,配置方式及其在rules字段中可内嵌的字段也与Role资源类似,kind为ClusterRole。
对于namespace级别的资源的访问控制来说,RoleBinding也能够将主体绑定至ClusterRole资源之上,但仅能赋予用户访问Role-Binding资源本身所在的名称空间之内可由ClusterRole赋予的权限,若借助ClusterRoleBinding进行绑定,则用户就具有了所有相关名称空间中的资源的访问权限。
但对于非namespace级别的资源,如nodes、persistentvolumes等资源,以及非资源型的URL(/api、/apis、/healthz、/swaggerapi和/version),则无法通过RoleBinding绑定来授权给用户。它们也只能定义在ClusterRole中,且需要基于ClusterRoleBinding进行授权。不过,对此类资源的读取权限已经由系统默认的名称同为system:discovery的ClusterRole和ClusterRoleBinding两个资源自动设定。
执行kubectl get clusterrole system:discovery -o yaml
可以查看:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: system:discovery
...
rules:
- nonResourceURLs:
- /api
- /api/*
- /apis
- /apis/*
- /healthz
- /livez
- /openapi
- /openapi/*
- /readyz
- /version
- /version/
verbs:
- get
准入控制器
在完成身份认证和权限检查之后,将由准入控制器进一步验证,比如执行对象的语义验证、设置缺失字段的默认值、限制所有容器使用的镜像文件必须来自某个特定的Registry、检查Pod对象的资源需求是否超出了指定的限制范围等。
准入控制器分为Mutating-AdmissionWebhook和ValidatingAdmissionWebhook两种类型,分别用于在API中执行对象配置的“变异”和“验证”操作。变异控制器可以修改他们许可的对象,而验证控制器则一般不会。在具体的代码实现上,一个准入控制器可以兼具验证、变异两项功能。
在运行时准入控制可分为两个阶段,第一个阶段串行运行各变异型控制器,第二个阶段串行运行各验证型控制器,一旦任一阶段中的任何控制器拒绝请求,则立即拒绝整个请求,并向用户返回错误提示。
LimitRanger准入控制器
对于没有指定资源需求及资源限制的容器来说,其应用有因故障吞掉所在工作节点上的所有可用计算资源的风险,使用LimitRange资源可以为某个名称空间中的每个容器指定计算资源用量,或者设置默认的计算资源需求和计算资源限制。定义了LimitRange对象后,客户端提交创建或修改的资源对象将受到LimitRanger控制器的检查。
LimitRange资源支持限制容器、Pod和PersistentVolumeClaim三种资源对象的系统资源用量,其中Pod和容器主要用于定义可用的CPU和内存资源范围,而PersistentVolume-Claim则主要定义存储空间的限制范围。
下面的配置清单在default空间中定义了容器的资源限制:
apiVersion: v1
kind: LimitRange
metadata:
name: cpu-limit-range
spec:
limits:
- default:
cpu: 1
defaultRequest:
cpu: 1
max:
cpu: 2000m
maxLimitRequestRatio:
cpu: 4
type: Container
在创建Pod对象时,如果没有指定资源限制,则LimitRange资源会自动为其设置,而且如果Request超出max的限制后会被block。
ResourceQuota资源与准入控制器
尽管LimitRange资源能限制单个容器、Pod及PVC等相关计算资源或存储资源的用量,但多个对象占用资源的总和仍然可能超出系统的限制。ResourceQuota资源用于定义名称空间的对象数量或系统资源配额,它支持限制每种资源类型的对象总数,以及所有对象所能消耗的计算资源及存储资源总量等。一个命名空间可以创建一个ResourceQuota对象。
ResourceQuota对象可限制指定名称空间中非终止状态的所有Pod对象的计算资源需求及计算资源限制总量,比如
- requests.cpu:CPU资源需求的总量限额
- requests.cpu:内存资源需求的总量限额。
- limits.cpu:CPU资源限制的总量限额。
- limits.memory:内存资源限制的总量限额。
还可以限制特定名称空间中可以使用的PVC数量和这些PVC资源的空间大小总量,一部分举例:
- persistentvolumeclaims:可以创建的PVC总数
- requests.storage:所有PVC存储需求的总量限额
在1.9版本起支持以count/<resource>.<group>
的格式对所有资源类型对象的计数配额,如count/deployments.apps
count/services
等。
apiVersion: v1
kind: ResourceQuota
metadata:
name: resource-quota-1
spec:
hard:
pods: 2
requests.cpu: 500m
requests.memory: 1Gi
limits.cpu: 2
limits.memory: 2Gi
count/deployments.apps: 1
persistentvolumeclaims: 2
apply上述资源清单后执行kubectl describe quota resource-quota-1
:
Name: resource-quota-1
Namespace: default
Resource Used Hard
-------- ---- ----
count/deployments.apps 0 1
limits.cpu 0 2
limits.memory 0 2Gi
persistentvolumeclaims 1 2
pods 4 2
requests.cpu 0 500m
requests.memory 0 1Gi
从打印结果可以看出在创建ResourceQuota前pod数量为4个,所以创建ResourceQuota后就无法添加新的pod了。
此外一旦启用了计算资源需求和计算资源限制配额,那么创建的任何Pod对象都必须设置此两类属性,否则会被block。
学习资料
《Kubernetes实战进阶》 马永亮著