api结构设计&认证授权&安全上下文
K8s APIServer与安全管控完整进阶笔记
一、API组织形式
APIServer是Kubernetes集群的统一控制入口,核心职责是对外提供API接口,所有集群组件、客户端与集群的交互都必须通过APIServer完成,是集群控制平面的核心枢纽。
1.1 APIServer三大核心功能
APIServer的能力可以分为三个核心维度,分别对接底层存储、外部客户端与集群安全管控。
第一,对底层组件的支撑能力。APIServer向下封装了对etcd数据库的所有操作,集群所有资源的增删改查都通过APIServer落地到etcd,禁止其他组件直接操作etcd;同时对上为所有集群组件提供了list-watch机制,各组件可以通过监听的方式实时感知资源变化,实现事件驱动的协作模式。
第二,对客户端的接口能力。APIServer对客户端统一提供API接口,总体基于RESTful风格设计,所有资源操作都遵循标准的HTTP请求规范。典型的API请求路径格式如下:
https://集群IP:端口/api/apps/v1/namespace/default/pods
第三,对访问安全的管控能力。所有客户端访问APIServer都需要经过一系列权限校验流程,共分为三层校验:
1、检查凭证:验证用户是否持有合法身份凭证,确认账号身份的合法性,凭证代表的是用户的身份信息。
2、检验授权:验证用户是否对请求中指定的API资源拥有对应的操作权限。
3、准入控制器:请求会经过准入控制器处理,包含两类Webhook:变更类mutate与校验类validate。
1.2 API设计的核心挑战与解决思路
Kubernetes需要持续迭代新功能、新增资源类型,同时还要兼容旧版本API,因此API设计面临两大核心挑战:
1、新增的API如何能够方便的扩展与演进,支持新功能快速迭代;
2、新老API并存场景下,如何能做到彼此互不影响,某个特定的资源既有稳定版也有实验版可用,可以非常方便的进行切换。
针对以上挑战,核心解决思路可以总结为一句话:同一种资源分配给不同的API路径,通过路径区分版本。例如Deployment资源可以同时存在两个版本路径:
apps/v1/deployment 稳定的/老的
v1beta1/v1/deployment 新的
1.3 API层级组织架构
Kubernetes API在RESTful风格的基础上,做了分组+多版本的设计,这是其API设计的核心亮点,既支持灵活扩展,又兼顾版本兼容。
API整体采用分层组织的结构,从顶层到具体资源依次为:
1、顶级路径大类,共四类:
/api 核心组
/apis/ 扩展组,核心组以外的资源
/metrics 性能指标,获取apiserver的性能指标数据
/healthz 用于检查api server 的健康状态
2、细分组名:就是kubectl explain看到的group,全局唯一不会重复。非核心组下的资源分组,细分组名全局唯一,编写资源清单时指定的就是细分组名,例如apps、batch、extensions。
apiVersion: apps/v1
3、具体版本:每个分组下可以同时存在多个版本,例如v1、v2alpha1。
4、具体资源:对应各类Kubernetes资源对象,例如job、namespace。
需要特别强调,根据是否属于核心组,API完整路径分为两种构成形式:
非核心组API构成:/顶级大类/细分组名/版本/namespaces/具体的名称空间/资源类型
核心组API构成:/api/版本/namespaces/具体的名称空间/资源类型
# 完整api
https://apiserver_ip:port/apis/apps/v1/namespaces/default/deployment
# 完整api的组成
- /apis/apps/v1/namespaces/default/deployment 这一部分由yaml文件给出
- https://apiserver_ip:port/ 这一部分由/root/.kube/config文件给出
注意:某些资源没有细分组,比如service, pod, secret, namespace。上面的完整API表达是非核心组的,如果是核心组的资源,没有细分组,直接对接版本。
apiVersion: v1
kind: Pod
metadata:
spec:
查看API层级的命令:
# 查看顶级大类
kubectl get --raw /
# 查看某个顶级大类的细分组
kubectl get --raw /apis/ | python -m json.tool
kubectl get --raw /api/ | python -m json.tool
# 查看细分组里的版本
kubectl get --raw /apis/apps | python -m json.tool
kubectl get --raw /api/v1 | python -m json.tool
# 查看版本下的资源
kubectl get --raw /apis/apps/v1 | python -m json.tool
API是通用入口,除了kubectl之外,也可以用curl等工具直接访问K8s资源,需要先启动代理:
kubectl proxy
curl http://127.0.0.1:8081/apis/v1
1.4 API版本分级与演进
同一资源的不同版本按照稳定性分为三个级别,新特性会按照固定路径逐步迭代成熟:
1、Alpha版:尝鲜版本,新特性一定最先出现在这里,功能不稳定、可能随时调整或删除,仅用于测试验证。
2、Beta版:稳定性提高版本,相对Alpha版更稳定一些,经过了初步验证,但仍可能存在变更,用于灰度使用。
3、稳定级别:正式发布的稳定版本,比如v1版本表示是最稳定版,功能向后兼容,可用于生产环境。
版本演进规律可以总结为:出生在Alpha版,成长在Beta版,毕业在v1稳定版。
二、认证、授权与准入控制
承接APIServer的安全管控能力,Kubernetes通过认证、授权、准入控制三层机制,实现对集群访问的全链路安全管控,核心基于RBAC权限模型实现精细化权限管理。
2.1 权限控制基础维度(储备知识)
授权的三要素:Kubernetes的权限管控围绕三个核心维度展开,限制用户对某个API下的某种类型资源拥有某种权限:
1、API分组(apiGroups):对应API组织中的分组,Kubernetes API是分组、多版本的结构,权限按分组进行隔离。
2、资源类型(resource type / resources):对应各类Kubernetes资源对象,常见的资源类型包括Pods、Configmaps、Deployments、Nodes、Secrets、Namespaces等。
3、具体的操作(verbs):对资源可以执行的具体动作,常见操作包括:
create
delete
update、edit、patch
get、list&watch
exec
2.2 用户实体与身份凭证
权限管控的主体是用户实体(subject),也就是各类账号。Kubernetes中每种账号都只是代号而已,必须关联上凭证才能使用,集群确认用户身份用的是凭证,而不是账号名本身。
凭证中包含用户的身份信息,例如证书、Token、归属的名称空间等。
账号分为两大类,分别对应不同的使用场景:
1、用户名、用户组:给人用的,用于运维人员、开发人员操作集群的场景。
2、ServiceAccount(服务账号):给Pod用的,让Pod内的运行环境拥有对Kubernetes的操作权限,实现Pod与集群的安全交互。
2.3 RBAC权限控制机制
RBAC是Kubernetes权限管理的核心机制,核心逻辑是:将账号与角色进行绑定,角色中定义具体的权限规则,实现身份与权限的解耦。简单来说,账号相当于人,角色相当于职位,绑定相当于任职。
2.3.1 两类角色
角色是权限规则的载体,根据可见范围分为两类:
1、Role(私有角色):此类角色只能被单个名称空间看到并加以引用,即该类角色是私有的,属于命名空间级资源。
2、ClusterRole(公共角色):此类角色能够被所有名称空间看到并加以引用,即该类角色是公有的,属于集群级资源。
需要特别强调:角色本身声明的命名空间,只是声明自己这个角色能够被一个还是所有名称空间看到;至于角色下的rules规则能在哪个名称空间执行,取决于绑定方式。
rules规则集:定义对哪些api groups里的哪些resource资源拥有什么操作权限,完整Role示例:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: pod-reader
namespace: default
rules:
- apiGroups: [""] # "" 表示核心API组
resources: ["pods"]
verbs: ["get", "watch", "list"]
resourceNames: [] # 可选,限制到具体资源名称,空代表所有
2.3.2 三种绑定方式
绑定操作的作用是将账号与角色关联起来,使账号获得角色对应的权限,共分为三种绑定场景:
1、RoleBinding绑定Role:授予的权限只能在一个名称空间中使用,对应私有角色的命名空间内授权。
2、ClusterRoleBinding绑定ClusterRole:授予的权限能在所有名称空间中使用,对应集群级公共角色的全局授权。
3、RoleBinding绑定ClusterRole:应对的场景是,拿到一个公共的权限声明,然后用RoleBinding绑定死在一个名称空间,实现公共权限模板的单命名空间复用。
注意:Role + ClusterRoleBinding 是不允许的,ClusterRoleBinding 不能引用命名空间级 Role。
2.4 APIServer完整请求验证流程
所有发往APIServer的请求,都会依次经过三层安全校验,全部通过后才会被真正执行:
1、认证(Authentication):基于凭证确认用户身份,校验凭证的合法性,身份不合法直接拒绝请求。
2、授权(Authorization):验证该凭证关联的权限,检查是否拥有对目标apiGroup、目标resources的对应verbs操作权限,权限不足直接拒绝。
3、准入控制(Admission Controllers):触发两类Webhook处理:mutate变更类Webhook会修改请求内容,补充默认配置;validate校验类Webhook会校验请求合规性,不符合规则则拒绝请求。
2.5 RBAC实战案例
2.5.1 用户实体为普通用户/用户组
实验1:RoleBinding绑定Role类型角色,通过自签证书实现自定义用户。
# 1、创建私钥文件,得到egon.key
(umask 077;openssl genrsa -out egon.key 2048)
# 2、使用egon.key创建证书签名请求文件,得到egon.csr
# CN表示用户名,O表示组
openssl req -new -key egon.key -out egon.csr -subj "/CN=egon/O=techstar"
# 3、使用k8s的CA证书批准上述证书请求,k8s的CA证书位于/etc/kubernetes/pki/目录下
openssl x509 -req -in egon.csr -CA /etc/kubernetes/pki/ca.crt -CAkey /etc/kubernetes/pki/ca.key -CAcreateserial -out egon.crt -days 500
# 二进制安装的集群,CA证书路径
# /opt/kubernetes/ssl/ca.pem 对应ca.crt
# /opt/kubernetes/ssl/ca-key.pem 对应ca.key
# 4、查看证书详情
openssl x509 -in egon.crt -text -noout
# 5、使用证书和私钥在集群中创建新的凭证和上下文
kubectl config set-credentials egon --client-certificate=./egon.crt --client-key=./egon.key --embed-certs=true
# 6、为用户设置新的Context,指定特定名称空间
kubectl config set-context egon-context --cluster=kubernetes --namespace=kube-system --user=egon
创建Role角色:
# dry-run模式生成yaml模板
kubectl create role role1 --verb=get,list,watch --resource=pods --dry-run=client -o yaml > role-demo.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: role1
namespace: kube-system
rules:
- apiGroups: ['', 'apps']
# pod属于核心API组,使用空字符串;Deployment、ReplicaSet属于apps api组
resources: ['deployments', 'replicasets', 'pods']
verbs: ['get', 'list', 'watch', 'create', 'update', 'patch', 'delete']
# 也可以直接使用 ['*'] 代表全部权限
# 创建role
kubectl apply -f role-demo.yaml
kubectl get role -n kube-system
RoleBinding角色绑定:
# dry-run模式生成yaml模板
kubectl create rolebinding rolebinding1 --role=role1 --user=egon --dry-run=client -o yaml > rolebinding-demo.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
namespace: kube-system
name: rolebinding1
subjects: # 用户实体,授权对象
- apiGroup: rbac.authorization.k8s.io
kind: User
name: egon # 证书创建的用户名
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: role1 # 关联的角色名称
kubectl apply -f rolebinding-demo.yaml
kubectl上下文相关命令与优先级:
优先级最高:kubectl --kubeconfig=/xx.yaml get pod
优先级其次:export KUBECONFIG=/xx.yaml,默认为/etc/kubernetes/admin.conf
优先级最低:~/.kube/config
kubectl config view
kubectl config get-contexts
kubectl config use-context kubernetes-admin@kubernetes
kubectl config delete-context xxx
kubectl config view -o jsonpath='{.users[*].name}'
kubectl config unset users.<user-name>
# 导出配置给他人使用
kubectl config view --minify --flatten --context=egon-context > egon-context-kubeconfig.yaml
总结:有了上下文之后,用户只需要安装kubectl就可以访问k8s了。
实验2:用户通过ClusterRoleBinding绑定ClusterRole类型角色。
# 生成集群角色模板
kubectl create clusterrole cluster-read --verb=get,list,watch --resource=pods --dry-run=client -o yaml > clusterrole-demo.yaml
# 创建集群角色
kubectl apply -f clusterrole-demo.yaml
# 删除之前的命名空间级绑定
kubectl delete rolebinding rolebinding1 -n kube-system
# 生成集群角色绑定模板
kubectl create clusterrolebinding egon-read-all-pods --clusterrole=cluster-read --user=egon --dry-run=client -o yaml > clusterrolebinding-demo.yaml
# 应用清单完成绑定
kubectl apply -f clusterrolebinding-demo.yaml
绑定完成后,用户 egon 在所有命名空间下,都拥有 pods 的 get、list、watch 权限。
针对用户账号的规则总结:账号拥有的权限取决于两种绑定方式,一个账号有单个名称空间级别的权限,还是全局名称空间下的权限由绑定方式决定;账号本身可以在哪里看见,取决于角色。
实验3:用户通过RoleBinding绑定ClusterRole类型角色。
# 删除全局绑定
kubectl delete clusterrolebinding egon-read-all-pods
# 生成RoleBinding绑定ClusterRole的模板
kubectl create rolebinding rolebinding2 --clusterrole=cluster-read --user=egon --dry-run=client -o yaml > test.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: rolebinding2
namespace: kube-system # 关键:权限仅限制在此命名空间
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole # 引用集群角色ClusterRole
name: cluster-read
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: User
name: egon
kubectl apply -f test.yaml
2.5.2 用户实体为ServiceAccount
实验4:RoleBinding绑定Role角色。ServiceAccount不需要配置上下文,只能在某个名称空间下可见,不能跨名称空间关联ServiceAccount。
# 创建服务账号
kubectl create sa egon-sa -n kube-system
# 创建命名空间级 Role
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: role3
namespace: kube-system
rules:
- apiGroups: [''] # 核心组("")
resources: ['pods']
verbs: ['get', 'watch', 'list']
- apiGroups: ['apps']
resources: ['deployments']
verbs: ['get', 'list', 'watch', 'create', 'update', 'patch', 'delete']
# 创建 RoleBinding,绑定 Role 与 ServiceAccount
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: rolebinding3
namespace: kube-system
subjects:
- kind: ServiceAccount
name: egon-sa
namespace: kube-system
roleRef:
kind: Role
name: role3
apiGroup: rbac.authorization.k8s.io
验证方式1:kubectl auth can-i 预先校验权限,快速排查问题。
kubectl auth can-i <动词> <资源> --as=system:serviceaccount:<命名空间>:<sa名称>
kubectl auth can-i get pods -n default --as=system:serviceaccount:kube-system:egon-sa
# 输出 no
kubectl auth can-i get pods -n kube-system --as=system:serviceaccount:kube-system:egon-sa
# 输出 yes
# 模拟普通用户egon查看pod权限
kubectl auth can-i list pods --as=egon -n kube-system
验证方式2:Pod中通过serviceAccountName指定ServiceAccount,启动后自动挂载对应Token,容器内程序用Token访问APIServer验证权限。
apiVersion: apps/v1
kind: Deployment
metadata:
name: test1
namespace: kube-system # 必须与SA、权限绑定的命名空间保持一致
spec:
replicas: 1
selector:
matchLabels:
app: test1
template:
metadata:
labels:
app: test1
spec:
serviceAccountName: egon-sa # 指定Pod使用的服务账号
containers:
- image: centos
name: centos
command: ["/bin/sh","-c","sleep 10000"]
kubectl -n kube-system exec -ti test1-5478bb8944-52f6s sh
# Pod指定serviceAccountName后,k8s自动把服务账号凭证挂载至容器内固定路径
cat /var/run/secrets/kubernetes.io/serviceaccount/token
# 将token赋值给变量
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
# 使用token直接调用K8s APIServer接口验证权限
curl --header "Authorization: Bearer $TOKEN" --insecure \
https://kubernetes.default.svc/api/v1/namespaces/default/pods
curl --header "Authorization: Bearer $TOKEN" --insecure \
https://kubernetes.default.svc/apis/apps/v1/namespaces/kube-system/deployments
注意:如果指定的ServiceAccount不存在,Pod直接启动失败。
实验5:ServiceAccount通过ClusterRoleBinding绑定ClusterRole。
# 创建ServiceAccount
kubectl create sa egon-sa2 -n kube-system
# 查看集群内置ClusterRole cluster-admin
kubectl get clusterrole cluster-admin -o yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
annotations:
rbac.authorization.kubernetes.io/autoupdate: "true"
labels:
kubernetes.io/bootstrapping: rbac-defaults
name: cluster-admin
rules:
- apiGroups:
- '*'
resources:
- '*'
verbs:
- '*'
- nonResourceURLs:
- '*'
verbs:
- '*'
# 创建ClusterRoleBinding,绑定SA与cluster-admin
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: egon-sa2-clusterrolebinding
subjects:
- kind: ServiceAccount
name: egon-sa2
namespace: kube-system
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
# 权限验证
kubectl auth can-i get pods -n default --as=system:serviceaccount:kube-system:egon-sa2
# 返回 yes
kubectl auth can-i get pods -n kube-system --as=system:serviceaccount:kube-system:egon-sa2
# 返回 yes
RBAC绑定规则总结记忆:
Role + RoleBinding:权限限制在绑定的namespace
ClusterRole + ClusterRoleBinding:权限全局,全部namespace
ClusterRole + RoleBinding:复用集群权限模板,但仅在RoleBinding所在namespace生效
Role + ClusterRoleBinding:不允许,ClusterRoleBinding不能引用命名空间级Role。
三、Security Context安全上下文
Security Context是Pod与容器层面的安全配置机制,属于集群安全体系在工作负载侧的落地,用于定义Pod或容器的运行权限、安全属性,从负载层面限制容器的行为,降低安全风险。
3.1 Linux Capability基础
Linux内核2.2之前,进程的权限只有root和非root,root永远拥有一切权限,非root不能执行任何特权操作。但很多时候,需要非root用户能有部分特权。
于是诞生了suid,可以让用户拿到文件属主的身份,但这种机制很可能成为漏洞被利用。
之后诞生了Linux capability机制,把root权限的特权做了细分,可以对细分后的权限单独授权,实现基础权限是普通的,但能够拥有部分特权。
宿主机上的capability设置命令:
getcap /bin/ping
su - egon -c "ping -c1 www.baidu.com"
setcap cap_net_admin,cap_net_raw+p /bin/ping
getcap /bin/ping
ping -c1 www.baidu.com
# +p代表增加,-p代表减少 permitted
容器内的权限说明:容器内只有rootfs,没有内核,宿主机上的内核改动会影响所有容器。容器内的root账号并不等同于宿主机的root账号,容器内部的root只拥有宿主机上root账号的部分权限,而非全部。
如果想让容器内root账号拥有所有宿主机的root权限,可以开启特权模式:
docker run --privileged
docker container run -it --rm --cap-add NET_ADMIN --name test test_cap:v1.0 sh
# 也可以用--cap-drop去掉一些特权
nerdctl run -it --rm busybox /bin/sh
# 默认无法创建虚拟网卡
nerdctl run -it --rm --cap-add=NET_ADMIN busybox /bin/sh
# 添加NET_ADMIN能力后可以创建虚拟网卡
ip link add temp_eth0 type dummy
K8s中配置Linux Capabilities的示例:
apiVersion: v1
kind: Pod
metadata:
name: cpb-demo
spec:
containers:
- name: cpb
image: busybox
args:
- sleep
- '3600'
securityContext:
capabilities:
add:
- NET_ADMIN # 新增网络管理能力,允许创建虚拟网卡、修改网络接口
drop:
- KILL # 删除默认的KILL能力,容器内无法发送kill信号
kubectl exec -it cpb-demo /bin/sh
ip link add temp-dummy0 type dummy
3.2 核心作用
安全上下文是K8s用于定义pod或容器安全设置的配置,可以对容器的运行身份、权限范围、系统调用能力等进行约束,避免容器拥有过高权限、防止容器逃逸、减少攻击面,保障节点与集群的整体安全。
3.3 常用配置项
安全上下文可以配置在Pod级别和容器级别,Pod级别配置对Pod内所有容器生效,容器级别仅对单个容器生效,核心配置项包括:
1、运行用户与用户组:指定容器运行时使用的UID、GID,限制容器运行身份,避免默认使用root用户。
2、特权模式控制:禁止容器运行在特权模式,防止容器获得节点的root权限。
3、只读根文件系统:将容器的根文件系统设置为只读,防止容器内程序修改系统文件。
4、权限提升限制:禁止容器进程获得比启动时更高的权限。
5、Linux能力限制:按需添加内核能力,默认移除所有不必要的能力,缩小权限范围。
6、Seccomp配置:限制容器可以调用的系统调用,减少内核层面的攻击面。
3.4 配置实验案例
实验1:Pod级别的security context
直接限制容器中的用户身份,以非root启动,Pod级配置对所有容器生效,fsGroup仅Pod级别支持。
apiVersion: v1
kind: Pod
metadata:
name: security-context-pod-demo
spec:
securityContext: # pod级别配置,作用于pod内所有容器
runAsUser: 1000 # pod内全部容器进程以UID=1000运行
runAsGroup: 3000 # pod内全部容器进程以GID=3000运行;省略默认GID为0(root组)
fsGroup: 2000 # pod挂载的volume,文件归属组ID修改为2000
volumes:
- name: my-volume
emptyDir: {}
containers:
- name: sec-ctx-demo
image: busybox
command: ['sh', '-c', 'sleep 60m']
volumeMounts:
- name: my-volume
mountPath: /pod/demo # 目录内文件GID受fsGroup控制
securityContext: # 容器级配置
allowPrivilegeEscalation: false # 禁止进程提权
创建完成后可进入容器执行id命令查看用户身份,验证配置生效。
实验2:Container级别的security context
容器内securityContext优先级高于Pod级别;容器级别配置不会影响volume(fsGroup仅Pod级别生效)。
apiVersion: v1
kind: Pod
metadata:
name: security-context-container-demo
spec:
securityContext:
runAsUser: 1000
containers:
- name: demo1
image: busybox
command: ['sh','-c','sleep 60m']
securityContext:
runAsUser: 2000 # 当前容器覆盖pod级别配置,以2000为准
allowPrivilegeEscalation: false
- name: demo2
image: busybox
command: ['sh','-c','sleep 60m']
# 无容器级配置,沿用pod级 runAsUser:1000
创建完成后可分别进入两个容器执行id命令,验证不同容器的用户身份差异。
实验3:特权模式
特权模式只能在container级别设置,Pod级别不支持。
典型场景:部署Elasticsearch,容器内需要修改内核sysctl参数;默认容器文件系统只读,开启特权模式后拥有宿主机内核操作权限。
apiVersion: v1
kind: Pod
metadata:
name: demo3
spec:
containers:
- name: demo3
image: busybox
command: ['sh','-c','sleep 60m']
securityContext:
privileged: true # 开启特权模式
安全提醒:特权容器权限极高,生产环境尽量少使用。

浙公网安备 33010602011771号