K8S 安全与RBAC

少年恃险若平地,独倚长剑凌清秋。这篇文章主要讲述K8S 安全与RBAC相关的知识,希望能为你提供帮助。
一、简介



K8S作为一个分布式集群的管理工具,保证集群的安全性是其一个重要的任务。API Server是集群内部各个组件通信的中介,也是外部控制的入口。所以K8S的安全机制基本就是围绕保护APIServer来设计的。K8S使用了认证(Authentication)、鉴权(Authorization)、准入控制(Admission Crontrol)三步来保证API Server的安全。


1.1、认证Authentication
1.1.1、认证的方法有:
1. HTTP Token 认证:通过一个 Token 来识别合法用户:HTTP Token 的认证是用一个很长的特殊编码方式的并且难以被模仿的字符串 - Token 来表达客户的一种方式。Token 是一个很长的很复杂的字符串,每一个 Token 对应一个用户名存储在 API Server 能访问的文件中。当客户端发起 API 调用请求时,需要在 HTTP Header 里放入 Token。
2. HTTP Base 认证:通过 用户名+密码 的方式认证。用户名+:+密码 用 BASE64 算法进行编码后的字符串放在 HTTP Request 中的 Heather;Authorization 域里发送给服务端,服务端收到后进行编码,获取用户名及密码
3. 最严格的 HTTPS 证书认证:基于 CA 根证书签名的客户端身份认证方式
1.1.2、认证包括两种类型
? Kubenetes组件对API Server的访问:kubectl、Controller
Manager、Scheduler、kubelet、kubeproxy
1. Controller Manager、Scheduler 与 API Server 在同一台机器,所以直接使用API Server的非安全端口访问, --insecure-bind-address=127.0.0.1
2. kubectl、kubelet、kube-proxy 访问 API Server 就都需要证书进行 HTTPS 双向认证
3. 实现方式:kubeconfig 文件包含集群参数(CA证书、API Server地址),客户端参数(上面生成的证书和私钥),集群context 信息(集群名称、用户名)。Kubenetes 组件通过启动时指定不同的 kubeconfig 文件可以切换到不同的集群
? Kubernetes 管理的 Pod 对容器的访问:Pod(dashborad 也是以 Pod 形式运行)。
?Pod中的容器访问API Server。因为Pod的创建、销毁是动态的,所以要为它手动生成证书就不可行了。Kubenetes使用了Service Account解决Pod 访问API Server的认证问题
?Kubernetes 设计了一种资源对象叫做 Secret,分为两类,一种是用于 ServiceAccount 的 service-accounttoken,另一种是用于保存用户自定义保密信息的 Opaque。ServiceAccount 中用到包含三个部分:Token、ca.crt、namespace
1. token是使用 API Server 私钥签名的 JWT。用于访问API Server时,Server端认证
2. ca.crt,根证书。用于Client端验证API Server发送的证书
3. namespace, 标识这个service-account-token的作用域名空间
认证总体上如下图:
1.2、鉴权Authorization
 
鉴权是确定请求方有哪些资源的权限。API Server 目前支持以下几种授权策略 (通过 API Server 的启动参数 “—authorization-mode” 设置)
? AlwaysDeny:表示拒绝所有的请求,一般用于测试
? AlwaysAllow:允许接收所有请求,如果集群不需要授权流程,则可以采用该策略
? ABAC(Attribute-Based Access
Control):基于属性的访问控制,表示使用用户配置的授权规则对用户请求进行匹配和控制
? Webbook:通过调用外部 REST 服务对用户进行授权
? RBAC(Role-Based Access Control):基于角色的访问控制,现行默认规则。相对其它访问控制方式,拥有以下优势:
?对集群中的资源和非资源均拥有完整的覆盖
?整个 RBAC 完全由几个 API 对象完成,同其它 API 对象一样,可以用 kubectl 或 API 进行操作
?可以在运行时进行调整,无需重启 API Server
 
1.3、准入控制Admission Control
 
准入控制是API Server的插件集合,通过添加不同的插件,实现额外的准入控制规则。甚至于API Server的一些主要的功能都需要通过 Admission Controllers 实现,比如 ServiceAccount。
列举几个插件的功能:
? NamespaceLifecycle: 防止在不存在的 namespace 上创建对象,防止删除系统预置 namespace,删除namespace 时,连带删除它的所有资源对象。
? LimitRanger:确保请求的资源不会超过资源所在
Namespace 的 LimitRange 的限制。
? ServiceAccount: 实现了自动化添加 ServiceAccount。
? ResourceQuota:确保请求的资源不会超过资源的 ResourceQuota 限制。
 
二、ServiceAccount
Service account是为了方便Pod里面的进程调用Kubernetes API或其他外部服务而设计的。它与User account不同
? User account是为人设计的,而service account则是为Pod中的进程调用Kubernetes API而设计;
? User account是跨namespace的,而service account则是仅局限它所在的namespace;
? 每个namespace都会自动创建一个default service account
? Token controller检测service account的创建,并为它们创建??secret??
? 开启ServiceAccount Admission Controller后
?每个Pod在创建后都会自动设置spec.serviceAccount为default(除非指定了其他ServiceAccout)
?验证Pod引用的service account已经存在,否则拒绝创建
?如果Pod没有指定ImagePullSecrets,则把service account的ImagePullSecrets加到Pod中
?每个container启动后都会挂载该service account的token和ca.crt到/var/run/secrets/kubernetes.io/serviceaccount/
当创建 pod 的时候,如果没有指定一个 service account,系统会自动在与该pod 相同的 namespace 下为其指派一个default service account。而pod和apiserver之间进行通信的账号,称为serviceAccountName。
当创建 pod 的时候,如果没有指定一个 service account,系统会自动在与该pod 相同的 namespace 下为其指派一个default service account。而pod和apiserver之间进行通信的账号,称为serviceAccountName。如下:




C#
[root@k8s-master top]# kubectl get pods nginx-demo -o yaml |grepserviceAccountName
  serviceAccountName: default
[root@k8s-master top]# kubectl describe  pods nginx-demo |egrep "Mounts|from"
      Mounts:
          /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-7qkm9(ro)
      Type:                                      Projected (a volume thatcontains injected data from multiple sources) 


从上面可以看到每个Pod无论定义与否都会有个存储卷,这个存储卷为default-token-***的token令牌,这就是pod和serviceaccount认证信息。通过secret进行定义,由于认证信息属于敏感信息,所以需要保存在secret资源当中,并以存储卷的方式挂载到Pod当中。查看sa以及




Fortran
[root@k8s-master top]# kubectl get secret
NAME                                  TYPE                                                                  DATA    AGE
db-user-pass                  Opaque                                                              2          6d23h
default-token-kmq6s    kubernetes.io/service-account-token    3          52d
mysecret                          Opaque                                                              1          6d22h
[root@k8s-master top]# kubectl get sa
NAME          SECRETS    AGE
default    1                52d
[root@k8s-master top]# kubectl get secret default-token-kmq6s
NAME                                  TYPE                                                                  DATA    AGE
default-token-kmq6s    kubernetes.io/service-account-token    3          52d


而默认的service account 仅仅只能获取当前Pod自身的相关属性,无法观察到其他名称空间Pod的相关属性信息。
从上可以得知,SA是会挂载到POD里面的,那具体是哪个位置呢?如下:




Haskell
[root@k8s-master top]# kubectl exec -it nginx-demo -- bash
root@nginx-demo:/# ls -l /run/secrets/kubernetes.io/serviceaccount/
total 0
lrwxrwxrwx 1 root root 13 May 11 11:34 ca.crt -> ..data/ca.crt
lrwxrwxrwx 1 root root 16 May 11 11:34 namespace -> ..data/namespace
lrwxrwxrwx 1 root root 12 May 11 11:34 token -> ..data/token
root@nginx-demo:/# cat/run/secrets/kubernetes.io/serviceaccount/namespace |xargs
default


创建方法
SA的创建方法非常简单,直接 kubectl create
serviceaccount admin 即可。我们在POD创建时可以使用spec.serviceAccountName字段中将name设置为您想要用的 service account 名字即可。在 pod 创建之初 service account 就必须已经存在,否则创建将被拒绝。需要注意的是不能更新已创建的 pod 的 service account。




YAML
[root@k8s-master serviceaccount]# kubectl create serviceaccount admin
serviceaccount/admin created
[root@k8s-master serviceaccount]# kubectl get sa
NAME          SECRETS    AGE
admin        1                10s
default    1                52d
[root@k8s-master serviceaccount]# kubectl describe sa admin
Name:                              admin
Namespace:                    default
Labels:                          < none>
Annotations:                < none>
Image pull secrets:  < none>
Mountable secrets:    admin-token-84mdr
Tokens:                          admin-token-84mdr
Events:                          < none>


三、RBAC
 
RBAC 引入了 4 个新的顶级资源对象:Role、ClusterRole、RoleBinding、ClusterRoleBinding,4 种对象类型均可以通过 kubectl 与 API 操作。
在RBAC API中,一个角色包含了一套表示一组权限的规则。 权限以纯粹的累加形式累积(没有”否定”的规则)。 角色可以由命名空间(namespace)内的Role对象定义,而整个Kubernetes集群范围内有效的角色则通过ClusterRole对象实现。
 
3.1、Role与ClusterRole
 
在RBAC API中,一个角色包含了一套表示一组权限的规则。 权限以纯粹的累加形式累积(没有”否定”的规则)。 角色可以由名字空间(namespace)内的Role对象定义,而整个Kubernetes集群范围内有效的角色则通过ClusterRole对象实现。
一个Role对象只能用于授予对某一单一名字空间中资源的访问权限。
以下示例描述了”default”名字空间中的一个Role对象的定义,用于授予对pod的读访问权限:




CSS
[root@k8s-master rbac]# cat defatlt.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: default
  name: pod-reader
rules:
- apiGroups: [""] # "" indicates the core API group
  resources: ["pods"]
  verbs: ["get","watch", "list"]
 
[root@k8s-master rbac]# kubectl apply -f defatlt.yaml
role.rbac.authorization.k8s.io/pod-reader created
[root@k8s-master rbac]# kubectl get role
NAME                        CREATED AT
pod-reader            2022-05-16T09:00:13Z
prometheus-k8s    2022-04-21T10:10:06Z 


ClusterRole对象可以授予与Role对象相同的权限,但属于集群级别的。可以用于:
? 集群级别的资源控制,如node的访问权限
? 非资源型的访问权限,如endpoints “/healthz”
? 所有命名空间的资源控制。即控制所有的namespace
下面示例中的ClusterRole定义可用于授予用户对某一特定名字空间,或者所有名字空间中的secret的读访问权限:




CSS
[root@k8s-master rbac]# cat clusterrole.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  # 鉴于ClusterRole是集群范围对象,所以这里不需要定义"namespace"字段
  name: secret-reader
rules:
- apiGroups: [""]
  resources: ["secrets"]
  verbs: ["get","watch", "list"]


 
3.2、RoleBinding与ClusterRoleBinding
 
角色绑定将一个角色中定义的各种权限授予一个或者一组用户。角色绑定包含了一组相关主体(即subject, 包括用户——User、用户组——Group、或者服务账户——Service Account)以及对被授予角色的引用。 在名字空间中可以通过RoleBinding对象授予权限,而集群范围的权限授予则通过ClusterRoleBinding对象完成。
RoleBinding可以引用在同一名字空间内定义的Role对象。下面示例中定义的RoleBinding对象在”default”名字空间中将”pod-reader”角色授予用户”jane”。 这一授权将允许用户”jane”从”default”名字空间中读取pod。




YAML
[root@k8s-master rbac]# cat rolebinding.yaml
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: read-pods
  namespace: default
subjects:
- kind: User
  name: jane
  apiGroup:rbac.authorization.k8s.io
roleRef:
  kind: Role
    name: pod-reader
  apiGroup:rbac.authorization.k8s.io


RoleBinding对象也可以绑定一个ClusterRole对象。例如,尽管下面示例中的RoleBinding引用的是一个ClusterRole对象,但是用户”dave”(即角色绑定主体)还是只能读取”development” 名字空间中的secret(即RoleBinding所在的名字空间)。




YAML
## 以下角色绑定允许用户"dave"读取"development"名字空间中的secret。
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: read-secrets
  namespace: development # 这里表明仅授权读取"development"名字空间中的资源。
subjects:
- kind: User
  name: dave
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole
  name: secret-reader
  apiGroup:rbac.authorization.k8s.io


如果dave想要控制整个集群,那就上面的namespace删除掉就有了整个集群 secret-reader这个clusterRole的权限。
 
3.3、Subject角色绑定主体
 
RoleBinding或者ClusterRoleBinding将角色绑定到角色绑定主体(Subject)。 角色绑定主体可以是用户组(Group)、用户(User)或者服务账户(Service Accounts)。
? 用户名由字符串表示。可以是纯粹的用户名,例如”alice”、电子邮件风格的名字,如 “bob@example.com” 或者是用字符串表示的数字id。对于用户名,RBAC授权系统不要求任何特定的格式。然而,前缀system:是 为Kubernetes系统使用而保留的,所以管理员应该确保用户名不会意外地包含这个前缀。
? Kubernetes中的用户组信息由授权模块提供。用户组与用户一样由字符串表示。Kubernetes对用户组 字符串没有格式要求,但前缀system:同样是被系统保留的。
? Service Accounts拥有包含 system:serviceaccount:前缀的用户名,并属于拥有system:serviceaccounts:前缀的用户组。
一个名为”alice@example.com”的用户:




YAML
subjects:
- kind: User
  name:"alice@example.com"
  apiGroup:rbac.authorization.k8s.io   


3.4、对资源的控制引用
 
大多数资源由代表其名字的字符串表示,例如”pods”,就像它们出现在相关API endpoint的URL中一样。然而,有一些Kubernetes API还 包含了”子资源”,比如pod的logs。在Kubernetes中,pod logs endpoint的URL格式为:




Go
GET /api/v1/namespaces/namespace/pods/name/log


在这种情况下,”pods”是名字空间资源,而”log”是pods的子资源。为了在RBAC角色中表示出这一点,我们需要使用斜线来划分资源 与子资源。如果需要角色绑定主体读取pods以及pod log,您需要定义以下角色:




CSS
kind: Role
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  namespace: default
  name: pod-and-pod-logs-reader
rules:
- apiGroups: [""]
  resources: ["pods","pods/log"]
  verbs: ["get","list"]


这里的意思是rules的规则设置。
? apiGroups定义可以使用api的版本,为空表示核心版本,如 ["extensions", "apps"]允许读写在”extensions”和”apps”的API。
? resources表示对哪些资源进行控制,可以为pods、deployments、nodes、secrets。。。。
? verbs表示允许的动作。如"get",
"list", "watch", "create", "update",
"patch", "delete"等。
? resourceNames表示一个资源的实例名,如一个pod的名字为abc,那加上这个,那这个rule就表示只对这个pod生效。
 
四、实例
4.1、普通用户管理集群
在实际生产环境中,有很可能是多个部门共用同一套K8S的环境,这就需要做权限的隔离,以下以devuser为例,创建一个普通用户只能管理namespace=dev这个环境




PowerShell
# 下载证书生成工具
wget https://pkg.cfssl.org/R1.2/cfssl_linux-amd64
mv cfssl_linux-amd64 /usr/local/bin/cfssl
wget https://pkg.cfssl.org/R1.2/cfssljson_linux-amd64
mv cfssljson_linux-amd64 /usr/local/bin/cfssljson
wget https://pkg.cfssl.org/R1.2/cfssl-certinfo_linux-amd64
mv cfssl-certinfo_linux-amd64 /usr/local/bin/cfssl-certinfo


cat > /root/devuser-csr.json < < EOF

  "CN":"devuser",
  "hosts": [],
  "key":
        "algo":"rsa",
        "size": 2048
  ,
  "names": [
 
        "C": "CN",
        "ST":"BeiJing",
        "L":"BeiJing",
        "O":"k8s",
        "OU":"System"
 
  ]

EOF


cd /etc/kubernetes/pki/
cfssl gencert -ca=ca.crt -ca-key=ca.key -profile=kubernetes/root/devuser-csr.json | cfssljson -bare devuser


# 设置集群参数
export KUBE_APISERVER="https://192.168.4.169:6443"
kubectl config set-cluster kubernetes--certificate-authority=/etc/kubernetes/pki/ca.crt --embed-certs=true--server=$KUBE_APISERVER --kubeconfig=devuser.kubeconfig


# 设置客户端认证参数
kubectl config set-credentials devuser--client-certificate=/etc/kubernetes/pki/devuser.pem--client-key=/etc/kubernetes/pki/devuser-key.pem --embed-certs=true--kubeconfig=devuser.kubeconfig


# 设置上下文参数
kubectl create namespace dev
kubectl config set-context kubernetes --cluster=kubernetes --user=devuser--namespace=dev --kubeconfig=devuser.kubeconfig


# 设置默认上下文
kubectl create rolebinding devuser-admin-binding --clusterrole=admin--user=devuser --namespace=dev


# 创建用户
useradd devuser
cp -f ./devuser.kubeconfig /home/devuser/.kube/config
chown devuser:devuser /home/devuser/.kube/config


#使用devuser用户登陆系统
kubectl config use-context kubernetes


查看默认配置




YAML
[root@k8s-master rbac]# kubectl config view
apiVersion: v1
clusters:
- cluster:
      certificate-authority-data:DATA+OMITTED
      server:https://172.16.4.169: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:REDACTED
      client-key-data: REDACTED


4.2、创建一个只能访问某个namespace的用户
4.2.1、创建用户凭证




C#
# 给用户 fdm 创建一个私钥
[root@master user_login_kube-systecm]# openssl genrsa -out fdm.key 2048
Generating RSA private key, 2048 bit long modulus
...........................................................................................................................................................................+++
.........................................................................................................................................................+++
e is 65537 (0x10001)
[root@master user_login_kube-systecm]# ll
total 4
-rw-r--r-- 1 root root 1675 Feb 29 23:13 fdm.key


# 使用我们刚刚创建的私钥创建一个证书签名请求文件fdm.csr。
# -subj参数中指定用户名和组,CN表示用户名,O表示组
[root@master user_login_kube-systecm]# openssl req -new -key fdm.key -outfdm.csr -subj "/CN=fdm/O=wangsu"
[root@master user_login_kube-systecm]# ll
total 8
-rw-r--r-- 1 root root  907 Feb 2923:15 fdm.csr
-rw-r--r-- 1 root root 1675 Feb 29 23:13 fdm.key


# 使用k8s的CA证书,生成最终的证书文件fdm.crt,我们这里设置证书的有效期为3650天
[root@master user_login_kube-systecm]# openssl x509 -req -in fdm.csr -CA/etc/kubernetes/pki/ca.crt -CAkey /etc/kubernetes/pki/ca.key -CAcreateserial-out fdm.crt -days 3650
Signature ok
subject=/CN=fdm/O=wangsu
Getting CA Private Key
[root@master user_login_kube-systecm]# ll
total 12
-rw-r--r-- 1 root root  993 Feb 2923:16 fdm.crt
-rw-r--r-- 1 root root  907 Feb 2923:15 fdm.csr
-rw-r--r-- 1 root root 1675 Feb 29 23:13 fdm.key


# 查看证书的有效期
[root@master user_login_kube-systecm]# openssl x509 -in fdm.crt -text|grep Not
                      Not Before: Feb 2915:16:49 2020 GMT
                      Not After : Feb 2615:16:49 2030 GMT


使用刚刚创建的证书文件和私钥文件在集群中创建新的凭证和上下文(Context)




CoffeeScript
[root@master user_login_kube-systecm]# kubectl config set-credentials fdm--client-certificate=fdm.crt --client-key=fdm.key
User "fdm" set.
# 创建context
[root@master user_login_kube-systecm]# kubectl config set-contextfdm-context --cluster=kubernetes --namespace kube-system --user=fdm
Context "fdm-context" created.


# 测试报错,因为没有为该用户定义任何操作的权限
[root@master user_login_kube-systecm]# kubectl get pods--context=fdm-context
Error from server (Forbidden): pods is forbidden: User "fdm"cannot list resource "pods" in API group "" in thenamespace "kube-system"


4.2.2、创建角色
 
【K8S 安全与RBAC】用户创建完成后,接下来就需要给该用户添加操作权限,我们来定义一个YAML文件,创建一个

    推荐阅读