k8s RBAC访问控制(认证、鉴权、审计)流程原理解析
文章目录
Kubernetes自身并没有用户管理能力,无法像操作Pod一样,通过API的方式创建/删除一个用户实例,也无法在etcd中找到用户对应的存储对象。
在Kubernetes的访问控制流程中,用户模型是通过请求方的访问控制凭证(如kubectl使用的kube-config中的证书、Pod中引入的ServerAccount)产生的Kubernetes API的请求从发起到其持久化入库的流程如图:
一、认证阶段(Authentication)
1、RequestHeader认证
--requestheader-username-headers=X-Remote-User --requestheader-group-headers=X-Remote-Group --requestheader-extra-headers-prefix=X-Remote-Extra- --requestheader-client-ca-file:防止头部欺骗 --requestheader-allowed-names:设置允许的CN列表
2、BasicAuth认证
3、clientCA认证
X509认证是Kubernetes组件间默认使用的认证方式,同时也是kubectl客户端对应的kube-config中经常使用到的访问凭证。它是一个比较安全的方式。
首先访问者会使用由集群CA签发的,或是添加在apiserver配置中的授信CA签发的客户端证书去访问apiserver。apiserver在接收到请求后,会进行TLS的握手流程。
除了验证证书的合法性,apiserver还会校验客户端证书的请求源地址等信息,开启双向认证。
进行证书签发的步骤:
(1)创建根CA
其中:
- profiles:指定不同的过期时间、使用场景等参数。文件中可以定义多个,分别后续在签名证书时使用某一个
- signing:表示该证书可用于签名其它证书,生成的ca.pem证书中CA=TRUE
- key encipherment:表示密钥用法为密钥加密
- server auth:表示client可以用该CA 对server提供的证书进行验证
- client auth:表示server可以用该CA对client提供的证书进行验证
其中:
- CN:Common Name,用于从中提取该字段作为请求的用户名
- C:Country, 国家
- ST: State,州,省
- L: Locality,地区,城市
- O: Organization Name, 用于从中提前该字段作为请求用户所属的组
- OU: Organization Unit Name,组织单位名称,公司部门
使用根CA签署证书:
kubelet组件在工作时,采用主动的查询机制,即定期请求apiserver 获取自己所应当处理的任务,如哪些pod分配到了自己身上,从而去处理这些任务;同时kubelet自己还会暴露出两个本身api的端口,用于将自己本身的私有api暴露出去,这两个端口分别是该配置文件中指定的10250与10255。
对于10250端口,kubelet会在其上采用TLS加密以提供适当的鉴权功能;对于10255端口,kubelet会以只读形式暴露组件本身的私有api,并且不做鉴权处理。
因此,kubelet上实际上有两个地方用到证书,一个是用于与 API server通讯所用到的证书,另一个是该配置文件中设置的kubelet的10250私有api端口需要用到的证书。
openssl genrsa -out test.key 2048
②创建对应的x509 csr请求文件(需要在subj字段中指定user和group)
openssl req -new -key test.key -out test.csr -subj "/CN=xxxx/O=xxxx"
③Kubernetes集群本身就提供了证书签发的API certificates.k8s.io/v1beta1。调用后,api-server会根据请求,以csr资源对象的形式创建对应的签发请求。
例如,在集群的创建过程中,像kubeadm这样的集群安装工具,也会基于不同的csr签发请求调用api-server对应接口,创建不同的csr资源对象。
用户可以通过API创建K8s csr实例并等待管理员审批。
PS:request中是base64编码的csr文件
刚开始创建的签发实例都会处于pending状态:
证书以base64存在csr资源对象的.status.certificate中,配合私钥、CA公钥即可制作kubeconfig文件
集群管理员也可以直接读取集群根CA,并通过x509 csr请求文件签发证书。签发示例如下:
# openssl x509 -req -in test.csr -CA CA_LOCATION/ca.crt -Cakey CA_LOCATION/ca.key -Cacreateserial -out test.crt -days 365
命令中需要指明csr和ca.crt的文件路径,以及签发证书的过期时间信息
④查看kubeconfig配置
kubectl默认会从$HOME/.kube目录下查找文件名为config 的文件,也可以通过设置环境变量KUBECONFIG或者通过设置–kubeconfig去指定其它kubeconfig文件。
文件格式为:
{ "apiVersion": "v1", "kind": "Config", "preferences": {}, "clusters": [ { "cluster": { "certificate-authority": "server": "https://ip:6443" }, "name": {cluster-name} } ], "contexts": [ { "context": { "cluster": {cluster-name}, "user": {user-name} }, "name": {context-name} } ], "users": [ { "name": {user-name}, "user": { "client-certificate": "client-key": } } ] "current-context": {context-name}, }
若想要用base64编码数据代替认证文件,需要添加后缀-data,将 certificate-authority、client-certificate、client-key改为certificate-authority-data、client-certificate-data、client-key-data
# grep 'client-key-data' /etc/kubernetes/admin.conf | head -n 1 | awk '{print $2}' | base64 -d # grep 'client-certificate-data' /etc/kubernetes/admin.conf | head -n 1 | awk '{print $2}' | base64 -d
⑤下载集群ca公钥文件ca.pem,使用kubectl添加集群连接信息
# kubectl config set-cluster xxx --certificate-authority=ca.pem --embed-certs=true --server=https://ip:6443
⑥使用kubectl设置kubeconfig的users配置段信息,需要将用户秘钥信息加入kubectl配置中
# kubectl config set-credentials {user-name} --client-certificate=test.crt --client-key=test.key --embed-certs=true
⑦添加新的context入口到kubectl配置中
⑧多集群config的合并和切换
# export KUBECONFIG=file1:file2:file3 # kubectl config view --merge --flatten > ~/.kube/all-config # export KUBECONFIG = ~/.kube/all-config
⑨查看和切换上下文
# kubectl config get-contests # kubectl config use-context {your-contexts}
4、TokenAuth认证
启动apiserver时通过–token-auth-file参数启用TokenAuth认证。
AUTH_FILE(Static Password file)是一个CSV文件,文件格式为:
5、ServiceAccountAuth认证
serviceaccount是k8s中唯一能够通过API方式管理的apiserver访问凭证,通常用于pod中的业务进程与apiserver的交互
ServiceAccount解决Pod在集群里面的身份认证问题,认证使用的授权信息存在secret里面(由SecretAccount Controller自行创建)。
当一个namespace创建完成后,会同时在该namespace下生成名为default的serviceaccount和对应Secret:
对应的Secret里:
data字段有两块数据:ca.crt用于对服务端的校验,token用于Pod的身份认证,它们都是用base64编码过的。
metadata里annotations字段表明了关联的ServiceAccount信息(被哪个ServiceAccount使用)。
type字段表明了该Secret是service-account-token类型
此外,用户也可以通过api创建其它名称的ServiceAccount,并在该namespace的Pod的.spec.ServiceAccount下指定,默认是default。
Pod创建的时候,Admission Controller会根据指定的ServiceAccount把对应secret的ca.crt和token文件挂载到固定目录/var/run/secrets/kubernetes.io/serviceaccount下。
pod要访问集群的时候,默认利用Secret其中的token文件来认证Pod的身份,利用ca.crt校验服务端
默认token的认证信息为:
– Group:system:serviceaccounts:[namespace-name]
– User:system:serviceaccount:[namespace-name]:default
Pod身份被认证合法后,其权限需要通过RBAC来配置,默认只有资源GET权限
PS:如果是在Pod创建过程中,发现指定的ServiceAccount不存在,则该Pod创建过程会被终止。
PS:对于已经创建的Pod,不能更新其已经挂载的ServiceAccount内容。
6、Bootstrap Token认证
如果节点多起来,为每个节点单独签署证书将是一件非常繁琐的事情
TLS bootstrapping的功能就是让kubelet先使用一个预定的低权限用户连接到apiserver,向apiserver申请证书,证书由apiserver动态签署
在配合RBAC授权模型下的工作流程大致如下所示:
TLS bootstrapping下kubelet发起的CSR请求大致分为以下三种:
nodeclient:kubelet以O=system:nodes、CN=system:node:(node name)形式发起的CSR请求(仅在第一次启动时产生)
selfnodeclient:kubelet发起的更新自己的作为client的证书的CSR请求(与上一个证书有相同的O、CN)
selfnodeserver:kubelet发起的更新自己的作为server的证书(即kubelet 10250 api端口证书)的CSR请求
①使用TLS Bootstrapping Token时的配置流程:
(1)创建TLS Bootstrapping Token
(2)修改用户的描述文件token.csv(相当于预设的用户配置),基本格式为Token,user,uid,group:
(3)在apiserver配置中添加–enable-bootstrap-token-auth开启TLS bootstrapping功能,通过–token-auth-file参数指定token.csv文件,apiserver启动时会将其加载,相当于在集群内创建了这个用户。
(4)kubelet-bootstrap用户没有任何权限(包括创建CSR请求),需要创建一个ClusterRoleBinding,将预设用户kubelet-bootstrap用户与内置的ClusterRole system:node-bootstrapper绑定到一起,使其能够发起 CSR 请求
否则kubelet会报401无权访问apiserver的错误
(4)创建kubelet的配置文件bootstrapping.kubeconfig
执行命令:
kubelet-bootstrap用户的Token和apiserver的使用的CA证书被写入了该配置文件中
首次请求时,kubelet使用配置文件中的apiserver CA证书与apiserver建立TLS通讯,使用配置文件中的用户Token向apiserver声明自己的RBAC授权身份。
(5)启动kubelet时需要指定–kubeconfig和–bootstrap-kubeconfig(两种身份),指定–cert-dir用来存放所有证书
(6)在kubelet首次启动后,如果用户Token没问题,并且RBAC也做了相应的设置,那么此时在集群内应该能看到kubelet发起的CSR 请求。出现CSR请求后,可以使用kubectl手动签发kubelet的证书
(7)当成功签发证书后,目标节点的 kubelet 会将证书写入到–cert-dir选项指定的目录中
kubelet-client.crt、kubelet-client.key:kubelet与apiserver通讯所使用的证书
kubelet.crt、kubelet.key:用于kubelet的10250端口做鉴权使用(这个证书是个独立于apiserver CA的自签CA,并且删除后kubelet会重新生成它)
配置controller manager自动签署证书
kubelet发起的CSR请求都是由controller manager来做实际签署的
(1)kubelet启动时增加–feature-gates=RotateKubeletClientCertificate=true,RotateKubeletServerCertificate=true参数,则证书即将到期时会自动发起一个renew自己证书的CSR请求
配置了–feature-gates=RotateKubeletClientCertificate=true后,kubelet首次启动时仍会使用kubelet-client.crt证书与apiserver通信。续期请求被批准后会生成一个kubelet-client-时间戳.pem,kubelet-client-current.pem文件则始终软连接到最新的真实证书文件
配置了–feature-gates=RotateKubeletServerCertificate=true后不再生成kubelet.crt,改为生成kubelet-server-时间戳.pem,kubelet-server-current.pem文件则始终软连接到最新的真实证书文件
(2)controller manager启动时增加–feature-gates=RotateKubeletServerCertificate=true参数,则会在kubelet发起证书请求的时候自动帮助其签署证书
PS:如果不配置该参数,则即使配置了相关的RBAC规则,也只会自动批准kubelet client的更新证书请求
此外,还需要配置RBAC 规则,保证 controller manager只对kubelet发起的特定CSR请求自动批准
(3)针对kubelet发起的3种CSR请求创建3种对应的ClusterRole:
PS:1.8后的apiserver自动创建了前两条ClusterRole
(4)将适当的ClusterRole绑定到 kubelet 自动续期时所所采用的用户或者用户组身上
自动批准kubelet的首次CSR请求(用于与apiserver通信的证书):
自动批准 kubelet 后续 renew 用于与 apiserver 通讯证书的 CSR 请求:
自动批准 kubelet 发起的用于 10250 端口鉴权证书的 CSR 请求(包括后续 renew):
PS:在 1.8 后kubelet需要增加–rotate-certificates参数,kubelet 才会自动重载新证书
PS:在 1.7 版本以后kube-controller-manager可以通过–experimental-cluster-signing-duration参数来设置签署的证书有效时间,默认为8760h0m0s(1年)。
②使用TLS Bootstrapping Token Secret时的配置流程:
(1)生成token
type 必须为 bootstrap.kubernetes.io/token
- name 必须为 bootstrap-token-<token id>
- usage-bootstrap-authentication、usage-bootstrap-signing 必须设置为 true
- expiration 字段是可选的,如果设置则到期后将由 Controller Manager 中的 tokencleaner 自动清理
- auth-extra-groups 也是可选的,令牌的扩展认证组,组必须以system:bootstrappers:开头
(3)引导时,kubelet使用Token发起的请求其用户名为system:bootstrap:<token id>,用户组为system:bootstrappers
创建ClusterRoleBinding时要绑定到这个用户或者组上
允许 system:bootstrappers 组用户创建 CSR 请求:
自动批准 system:bootstrappers 组用户 TLS bootstrapping 首次申请证书的 CSR 请求:
自动批准 system:nodes 组用户更新 kubelet 自身与 apiserver 通讯证书的 CSR 请求:
自动批准 system:nodes 组用户更新 kubelet 10250 api 端口证书的 CSR 请求:
(4)Controller Manager需要添加参数–controllers=*,bootstrapsigner,tokencleaner以启用 tokencleaner 和 bootstrapsigner
(5)kubelet使用的kubeconfig文件也要相应变化:
7、OIDC认证
所谓OIDC(OpenID Connect),就是先向identity provider获取服务器签名的JSON Web Token (JWT)
identity provider会提供access_token、id_token、refresh_token
使用kubectl时通过–token参数添加id_token,或者直接把它添加到kubeconfig文件中,kubectl会把id_token添加到http头里
apiserver会通过证书确认JWT是否有效、确认JWT是否过期、身份是否合法等
--oidc-issuer-url:identity provider的地址 --oidc-client-id:client id,一般配成kubernetes --oidc-username-claim,如sub --oidc-groups-claim,如groups --oidc-ca-file:为identity provider签名的CA公钥
8、Webhook TokenAuth认证
当客户端发送的认证请求到达apiserver时,apiserver回调钩子方法,将验证信息发送给远程的Webhook服务器进行认证,然后根据Webhook服务器返回的状态码来判断是否认证成功
通过指定如下参数启用WebhookTokenAuth认证:
--authentication-token-webhook-config-file:Webhook配置文件描述了如何访问远程Webhook服务 --authentication-token-webhook-cache-ttl:缓存认证时间,默认值为2分钟
9、Anonymous认证
未被其他认证器拒绝的请求都可视为匿名请求,匿名用户权值很低
apiserver通过指定–anonymous-auth参数启用Anonymous认证,默认该参数值为true
认证流程之后,api-server会将请求中凭证中的用户身份转化为对应的User和Groups。在随后的鉴权操作和审计操作流程中,api-server都会使用该用户模型实例。
二、鉴权阶段(Authorization)
采用RBAC判断用户是否有权限进行请求中的操作。如果无权进行操作,api-server会返回403的状态码,并终止该操作
RBAC包含三个要素:
- Subjects:可以是开发人员、集群管理员这样的自然人,也可以是系统组件进程、Pod中的业务进程;
- API Resource:也就是请求对应的访问目标,在Kubernetes集群中指各类资源对象;
- Verbs:对应为请求对象资源可以进行哪些操作,如list、get、watch等。
部分常用操作需要的权限如下:
Role:定义了用户在指定的Kubernetes namespace上可以进行哪些操作
通过RoleBinding进行role和Subject的绑定:
除了定义指定namespace中的权限模型,也可以通过ClusterRole定义一个集群维度的权限模型。以定义集群维度的权限(如PV、Nodes等namespace中不可见的资源)
ClusterRole编排文件几乎和Role一样,删除指定namespace的那行即可。
通过ClusterRoleBinding进行ClusterRole和Subject的绑定。
系统预置的ClusterRole:
system:basic-user:system:unauthenticated组(未认证用户组)默认绑定Role,无任何操作权限
cluster-admin:system:masters组默认绑定的ClusterRole,有集群管理员权限
系统组件(kube-controller-manager、kube-scheduler、kube-proxy……)都绑定了默认的ClusterRole
三、审计阶段(AdmissionControl)
Admission Controller(准入控制器)是一个拦截器,被编译进API Server的可执行文件内部
它以插件的形式运行在apiserver进程中,会在鉴权阶段之后、对象被持久化到etcd之前,拦截apiserver的请求,对请求的资源对象执行自定义(校验、修改或拒绝等)操作。
AC有几十种,大体上分为3类:
- validating(验证型)用于验证k8s的资源定义是否符合规则
- mutating(修改型)用于修改k8s的资源定义,如添加label,一般运行在validating之前
- 既是验证型又是修改型
只要有一个准入控制器拒绝了该请求,则整个请求被拒绝(HTTP 403Forbidden)并返回一个错误给客户端。
可通过–enable-admission-plugins参数指定启用的准入控制器列表,通过–disable-admission-plugins参数指定禁用的准入控制器列表
查看打开的AC:
spec.hard:除了基础的资源,还可以可以限制Pod数量
spec.scopeSelector:定义更丰富的索引能力,包括Terminating/Not Terminating、BestEffort/NotBestEffort、PriorityClass。
创建ResourceQuota后,如果用户用超了资源,在提交Pod时,会收到一个forbidden的403错误,提示exceeded quota。但假如用户提交没有包含在这个ResourceQuota方案里面的资源,还是能成功的。
在某个namespace下创建LimitRange,则会自动为该namespace下的容器添加request和limit
例如创建该LimitRange:
会自动为所在命名空间下的容器添加256M的内存request和512M的内存limit
2、SecurityContextDeny
SecurityContext用于限制容器的一个行为,保证系统和其他容器的安全。从粒度上又分为以下两种:
Container-level Security Context:仅应用到指定的容器
Pod-level Security Context:应用到Pod内所有容器以及Volume
该能力不是Kubernetes或者容器runtime本身的能力,而是kubernetes和runtime通过用户的配置,最后下传到内核里,再通过内核的机制让其生效。
Pod级别和容器级别配置SecurityContext的例子:
SecurityContext设置项主要包括:
(1)通过用户ID和组ID来控制文件访问权限;
(2)SELinux:通过策略配置来控制用户或者进程对文件的访问控制;
(3)特权容器;
(4)Capabilities:给特定进程来配置一个privileged能力;
(5)AppArmor:通过一些配置文件来控制可执行文件的一个访问控制权限(比如说一些端口的读写);
(6)对系统调用的控制;
(7)对子进程能否获取比父亲更多的权限的一个限制
最后其实都是落到内核来控制它的一些权限。
3、PodSecurityPolicy
Pod Security Policies(PSP)是应用到集群内部所有Pod以及Volume的安全策略
PSP的使用:
(1)通过在apiserver的admission-plugin参数中添加PodSecurityPolicy开启:
(2)在集群中创建PSP策略实例,支持的控制项包括:
4、ValidatingAdmissionWebhook和MutatingAdmissionWebhook
webhook就是一个HTTP回调,接收API Server发送的admissionReview请求,处理(验证或修改)并返回admissionResponse。
使用步骤:
(1)创建ValidatingWebhookConfiguration文件和MutatingWebhookConfiguration文件:
其中rules定义了匹配规则,当发给apiserver的请求满足该规则的时候,apiserver就会给clientConfig中配置的service发送Admission请求。
(2)开发的webhook程序需要实现IWebHookServer接口:
例如,定义一个结构体:
type webHookServer struct { server *http.Server }
实现这四个方法:
①开始
func (ws *webHookServer) Start() { ws.server.ListenAndServeTLS("", "") }
在main函数中调用go ws.Start()后,可以以如下方式阻塞,等待退出信号后再调用ws.Stop()
signalChan := make(chan os.Signal, 1) signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM) <-signalChan ws.Stop()
③mutating负责修改
④validating负责审核
可以用一个req := ar.Request接下请求
req.Kind是metav1.GroupVersionKind(GVK结构体)
例如req.Kind.Kind是Service时的反序列化:
var service corev1.Service json.Unmarshal(req.Object.Raw, &service) resourceName, resourceNamespace, objectMeta = service.Name, service.Namespace, &service.ObjectMeta
最后返回结果的数据结构:
返回示例:
webhook可以做到很多事情,例如限制每个namespace使用的端口号、为每个Pod插入sidecar容器等。
参考资料:
[1] https://kubernetes.io/docs/home/
[2] https://edu.aliyun.com/roadmap/cloudnative
[3] https://mritd.me/2018/08/28/kubernetes-tls-bootstrapping-with-bootstrap-token/
[4] https://mritd.me/2018/01/07/kubernetes-tls-bootstrapping-note/
[5] 郑东旭《Kubernetes源码剖析》