Kubernetes 权限控制

环境信息

  • Centos 7
  • Kubernetes 1.24

ServiceAccount

ServiceAccount 就像 Pod、Secret、ConfigMap 等一样,都是资源,属于 namespace 级别,作用在单独的命名空间。默认情况,每个 namespace 都有一个默认的 ServiceAccount。

$ kubectl get serviceaccount
NAME SECRETS AGE
default 0 160d

$ kubectl describe serviceaccount default
Name: default
Namespace: default
Labels: <none>
Annotations: <none>
Image pull secrets: <none>
Mountable secrets: <none>
Tokens: <none>
Events: <none>

namespace 中的每个 Pod 都和一个 ServiceAccount 关联,它代表了运行在 Pod 中的应用程序的身份证明。Pod 中的每个容器都会挂载此 ServiceAccount token

在 Pod 的 manifest 定义文件中,可以通过指定账户名称的方式将一个 ServiceAccount 关联到 Pod。如果不显示指定 ServiceAccount 的账户名称,Pod 会使用 namespace 中默认的 ServiceAccount。可以将不同的 ServiceAccount 关联给不同的 Pod 来控制每个 Pod 可以访问的资源。

当 API 服务接收到一个带有认证 token 的请求时, API 会用这个 token 来验证发送请求的客户端所关联的 ServiceAccount 是否允许执行请求的操作。

查看 ServiceAccount

以 rancher 相关的 ServiceAccount 为例,查看 ServiceAccount 信息

$ kubectl get serviceaccount -n cattle-system
NAME SECRETS AGE
default 0 160d
git-webhook-api-service 0 160d
rancher 0 160d
rancher-webhook 0 160d

$ kubectl describe sa default -n cattle-system
Name: default
Namespace: cattle-system
Labels: <none>
Annotations: <none>
Image pull secrets: <none>
Mountable secrets: <none>
Tokens: <none>
Events: <none>


以下输出名为 rancher 的 ServiceAccount 信息

$ kubectl describe sa rancher -n cattle-system
Name: rancher
Namespace: cattle-system
Labels: app=rancher
app.kubernetes.io/managed-by=Helm
chart=rancher-2.7.0
heritage=Helm
release=rancher
Annotations: meta.helm.sh/release-name: rancher
meta.helm.sh/release-namespace: cattle-system
Image pull secrets: <none>
Mountable secrets: <none>
Tokens: rancher-token
Events: <none>

ServiceAccount 主要包含了密钥(token)信息,客户端 (Pod)请求 API Service 时使用的 token 文件(如 /var/run/secrets/kubernetes.io/serviceaccount/token)持有 ServiceAccount 的 token 。

$ kubectl describe secret rancher-token -n cattle-system
Name: rancher-token
Namespace: cattle-system
Labels: <none>
Annotations: field.cattle.io/projectId: local:p-76mvn
kubernetes.io/service-account.name: rancher
kubernetes.io/service-account.uid: 1e80ce10-2ba3-4bc3-81d9-ccc72001431b

Type: kubernetes.io/service-account-token

Data
====
ca.crt: 1099 bytes
namespace: 13 bytes
token: eyJhbGciOiJSUzI1NiIsImtpZCI6Ik51eFpuNU9MUlp2Qkxm

通过 ServiceAccount 配置镜像拉取密钥

在新建了 ServiceAccount 之后,若要将它赋值给 Pod,通过在 Pod 定义中的 spec.serviceAccountName 字段上配置 ServiceAccount 名称来分配。Pod 的 ServiceAccount 必须在 Pod 创建时进行配置,后续不能被修改

RBAC

从 Kubernetes 1.8.0 开始,RBAC(基于角色的权限控制)授权插件升级为 GA(通用可用性),并在大多数集群上默认开启(比如通过 kubeadm 部署的集群)。RBAC 会阻止未授权的用户查看和修改集群状态,默认的 ServiceAccount 不允许查看集群状态

RBAC 授权规则是通过四种资源对象来进行配置的,他们可以分为 2 个组 [1]

  • RoleClusterRole - 一组代表相关权限的规则,他们指定了在资源上可以执行哪些操作(动词)
  • RoleBindingClusterRoleBinding - 将角色中定义的权限赋予一个或者一组用户。它包含若干主体(用户、组或者 ServiceAccount)的列表和对这些主体所获得的角色的引用。

RoleRoleBinding 属于 namespace 范围的资源,必须在 namespace 中配置

ClusterRoleClusterRoleBinding 是集群作用域的资源

一个 RoleBinding 可以引用某 ClusterRole 并将该 ClusterRole 绑定到 RoleBinding 所在的名字空间。但是,RoleBinding 不能授予主体集群级别的资源的访问权限,即使它引用了一个 ClusterRoleBinding


Role

下面是一个位于 default 名字空间的 Role 的示例,可用来授予对 Pod 的读访问权限

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: default
name: pod-reader
rules:
- apiGroups: [""] # "" 标明 core API 组
resources: ["pods"]
verbs: ["get", "watch", "list"]

RoleBinding

下面的例子中的 RoleBindingpod-reader 这个 Role 授予在 default 名字空间中的用户 jane。 这样,用户 jane 就具有了读取 default 名字空间中所有 Pod 的权限

apiVersion: rbac.authorization.k8s.io/v1
# 此角色绑定允许 "jane" 读取 "default" 名字空间中的 Pod
# 你需要在该命名空间中有一个名为 “pod-reader” 的 Role
kind: RoleBinding
metadata:
name: read-pods
namespace: default
subjects:
# 你可以指定不止一个“subject(主体)”
- kind: User
name: jane # "name" 是区分大小写的
apiGroup: rbac.authorization.k8s.io
roleRef:
# "roleRef" 指定与某 Role 或 ClusterRole 的绑定关系
kind: Role # 此字段必须是 Role 或 ClusterRole
name: pod-reader # 此字段必须与你要绑定的 Role 或 ClusterRole 的名称匹配
apiGroup: rbac.authorization.k8s.io

RBAC 配置示例

以下示例演示通过给 namespace 中默认的 ServiceAccount 配置查看 Service 的权限,学习 RBAC 的使用。

本示例中的 namespace 为 ops,示例 Pod 为 ops-centos7-bfc4d75b5-kjcfs,配置信息如下

$ kubectl edit pod -n ops  ops-centos7-bfc4d75b5-kjcfs
apiVersion: v1
kind: Pod
metadata:
labels:
app: ops-centos7
name: ops-centos7-bfc4d75b5-kjcfs
namespace: ops
spec:
containers:
- command:
- ping
- 127.0.0.1
image: centos:centos7.9.2009
name: ops-centos7

volumeMounts:
- mountPath: /var/run/secrets/kubernetes.io/serviceaccount
name: kube-api-access-8ch7r
readOnly: true

volumes:
- name: kube-api-access-8ch7r
projected:
defaultMode: 420
sources:
- serviceAccountToken:
expirationSeconds: 3607
path: token
- configMap:
items:
- key: ca.crt
path: ca.crt
name: kube-root-ca.crt
- downwardAPI:
items:
- fieldRef:
apiVersion: v1
fieldPath: metadata.namespace
path: namespace

登陆到 Pod 中的容器,请求 Kubernetes API Server。默认的 token 没有查询集群信息的权限。

$ curl --cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt https://kubernetes.default.svc.cluster.local
{
"kind": "Status",
"apiVersion": "v1",
"metadata": {},
"status": "Failure",
"message": "forbidden: User \"system:anonymous\" cannot get path \"/\"",
"reason": "Forbidden",
"details": {},
"code": 403
}

$ curl --cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt https://kubernetes.default.sv.cluster.local/api/v1/namespaces/ops/services
{
"kind": "Status",
"apiVersion": "v1",
"metadata": {},
"status": "Failure",
"message": "services is forbidden: User \"system:anonymous\" cannot list resource \"services\" in API group \"\" in the namespace \"ops\"",
"reason": "Forbidden",
"details": {
"kind": "services"
},
"code": 403
}

以上请求中,因为没有提供访问的凭据,认证系统会将请求者的身份标记为 system:anonymous,鉴权系统检查此用户是否有权限请求相应资源,没有权限则返回 403。

使用 Pod 中默认挂载的 token 进行请求,认证系统会识别请求者的身份为对应的 ServiceAccount(默认的 default),鉴权系统检查权限,返回 Unauthorized 表示请求未授权。

$ curl --cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt \
-H "Authorization: Bearer `cat /var/run/secrets/kubernetes.io/serviceaccount/token`" \
https://kubernetes.default.svc.cluster.local
{
"kind": "Status",
"apiVersion": "v1",
"metadata": {},
"status": "Failure",
"message": "Unauthorized",
"reason": "Unauthorized",
"code": 401
}

创建 Role

创建以下配置的 Role,名称为 services-readRole 属于 namespace 资源,因此必须指定 namespace [2]

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: ops
name: services-read
rules:
- apiGroups: [""]
resources: ["services"]
verbs: ["get", "list", "watch"]

创建 RoleBinding

Role 配置了允许的操作(动作),未指定可以执行动作的主体(用户,组,ServiceAccount),必须将 Role 绑定到一个主体,通过创建 RoleBinding,来实现将 Role 绑定到主体

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
namespace: ops
name: services-read
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: services-read
subjects:
- kind: ServiceAccount
name: default
namespace: ops

通过以上配置,namespace 中默认的 ServiceAccount default 的 token 拥有了 services-read 这个 Role 的权限,而默认情况下 default 这个 ServiceAccount 的 token 被挂载到了 Pod 的容器中,可用于容器中的进程和 Kubernetes API Server 通信的身份认证。

通过以下命令访问 Kubernetes API Server,已经可以获取到 ops namespace 中的 Service 资源。

$ curl --cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt -H "Authorization: Bearer `cat /var/run/secrets/kubernetes.io/serviceaccount/token`" https://kubernetes.default.svc.cluster.local/api/v1/namespaces/ops/services
{
"kind": "ServiceList",
"apiVersion": "v1",
"metadata": {
"resourceVersion": "86457226"
},
"items": []
}

修改 Role 为以下配置,使默认的 ServiceAccount default 可以查看 namespace 中的所有资源

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: ops
name: services-read
rules:
- apiGroups: [""]
resources: ["*"]
verbs: ["get", "list", "watch"]

获取 Pods 资源

$ curl --cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt \
-H "Authorization: Bearer `cat /var/run/secrets/kubernetes.io/serviceaccount/token`" \
https://kubernetes.default.svc.cluster.local/api/v1/namespaces/ops/pods
{
"kind": "PodList",
"apiVersion": "v1",
"metadata": {
"resourceVersion": "86457516"
},
"items": [
{
"metadata": {
"name": "ops-centos7-bfc4d75b5-bc8xm",
"generateName": "ops-centos7-bfc4d75b5-",
"namespace": "ops",
"uid": "b35919d7-a227-4fe5-abd7-c7f426d97ab8",
"resourceVersion": "86425875",
"creationTimestamp": "2023-05-18T03:20:24Z",
"labels": {
"app": "ops-centos7",
"pod-template-hash": "bfc4d75b5"
},
"ownerReferences": [
{
"apiVersion": "apps/v1",
"kind": "ReplicaSet",
"name": "ops-centos7-bfc4d75b5",
"uid": "1d7a428d-8004-493b-b04c-2c4f996acf0c",
"controller": true,
"blockOwnerDeletion": true
}
],
"managedFields": [
...
]
},
"spec": {
"volumes": [
{
"name": "kube-api-access-vsh5j",
"projected": {
"sources": [
{
"serviceAccountToken": {
"expirationSeconds": 3607,
"path": "token"
}
},
{
"configMap": {
"name": "kube-root-ca.crt",
"items": [
{
"key": "ca.crt",
"path": "ca.crt"
}
]
}
},
{
"downwardAPI": {
"items": [
{
"path": "namespace",
"fieldRef": {
"apiVersion": "v1",
"fieldPath": "metadata.namespace"
}
}
]
}
}
],
"defaultMode": 420
}
}
],
"containers": [
{
"name": "ops-centos7",
"image": "centos:centos7.9.2009",
"command": [
"ping",
"127.0.0.1"
],
"resources": {},
"volumeMounts": [
{
"name": "kube-api-access-vsh5j",
"readOnly": true,
"mountPath": "/var/run/secrets/kubernetes.io/serviceaccount"
}
],
"terminationMessagePath": "/dev/termination-log",
"terminationMessagePolicy": "File",
"imagePullPolicy": "IfNotPresent"
}
],
"restartPolicy": "Always",
"terminationGracePeriodSeconds": 30,
"dnsPolicy": "ClusterFirst",
"serviceAccountName": "default",
"serviceAccount": "default",
"nodeName": "fm-k8s-c1-worker2",
"securityContext": {},
"schedulerName": "default-scheduler",
"tolerations": [
{
"key": "node.kubernetes.io/not-ready",
"operator": "Exists",
"effect": "NoExecute",
"tolerationSeconds": 300
},
{
"key": "node.kubernetes.io/unreachable",
"operator": "Exists",
"effect": "NoExecute",
"tolerationSeconds": 300
}
],
"priority": 0,
"enableServiceLinks": true,
"preemptionPolicy": "PreemptLowerPriority"
},
"status": {
"phase": "Running",
"conditions": [
{
"type": "Initialized",
"status": "True",
"lastProbeTime": null,
"lastTransitionTime": "2023-05-18T03:20:24Z"
},
{
"type": "Ready",
"status": "True",
"lastProbeTime": null,
"lastTransitionTime": "2023-05-18T03:20:25Z"
},
{
"type": "ContainersReady",
"status": "True",
"lastProbeTime": null,
"lastTransitionTime": "2023-05-18T03:20:25Z"
},
{
"type": "PodScheduled",
"status": "True",
"lastProbeTime": null,
"lastTransitionTime": "2023-05-18T03:20:24Z"
}
],
"hostIP": "172.31.22.159",
"podIP": "10.244.3.159",
"podIPs": [
{
"ip": "10.244.3.159"
}
],
"startTime": "2023-05-18T03:20:24Z",
"containerStatuses": [
{
"name": "ops-centos7",
"state": {
"running": {
"startedAt": "2023-05-18T03:20:25Z"
}
},
"lastState": {},
"ready": true,
"restartCount": 0,
"image": "centos:centos7.9.2009",
"imageID": "docker-pullable://centos@sha256:be65f488b7764ad3638f236b7b515b3678369a5124c47b8d32916d6487418ea4",
"containerID": "docker://4e689d3e4f48ad0a7b6829a155421d8425fb023a46aec1b71e6277dbfd557fb6",
"started": true
}
],
"qosClass": "BestEffort"
}
}
]

在不能使用 curl 的情况下,可以使用 wget 命令

$ wget --no-check-certificate --header "Authorization: Bearer `cat /var/run/secrets/kubernetes.io/serviceaccount/token`" https://172.31.30.123:10250/metrics
Connecting to 172.31.30.123:10250 (172.31.30.123:10250)
saving to 'metrics'
metrics 100% |***************************************************************************************| 135k 0:00:00 ETA
'metrics' saved

脚注