Kubernetes的认证机制

1.了解认证机制

  API服务器可以配置一到多个认证的插件(授权插件同样也可以)。API服务器接收到的请求会经过一个认证插件的列表,列表中的每个插件都可以检查这个请求和尝试确定谁在发送这个请求。列表中的第一个插件可以提取请求中客户端的用户名、用户ID和组信息,并返回给API服务器。API服务器会停止调用剩余的认证插件并继续进入授权阶段。

  目前有几个认证插件是直接可用的。它们使用下列方法获取客户端的身份认证:

    • 客户端证书
    • 传入在HTTP头中的认证token
    • 基础的HTTP认证
    • 其他

  启动API服务器时,通过命令行选项可以开启认证插件。

 

1.1 用户和组

  认证插件会返回己经认证过用户的用户名和组(多个组)。Kubernetes不会在任何地方存储这些信息,这些信息被用来验证用户是否被授权执行某个操作。

  了解用户

  Kubernetes区分了两种连接到API服务器的客户端。

    • 真实的人(用户)
    • pod(更准确地说是运行在pod中的应用)

  这两种类型的客户端都使用了上述的认证插件进行认证。用户应该被管理在外部系统中,例如单点登录系统(SSO),但是pod使用一种称为server accounts的机制,该机制被创建和存储在集群中作为ServiceAccount资源。相反,没有资源代表用户账户,这也就意味着不能通过API服务器来创建、更新或删除用户。

  这里不会详细讨论如何管理用户,但是会具体地探讨ServiceAccount,因为它们对于运行中的pod很重要。关于如何配置集群来供用户身份认证的更多信息,集群管理员应该参考http://kubernetes.io/docs/admin中的Kubernetes集群管理员指南。

  了解组

  正常用户和ServiceAccount都可以属于一个或多个组。己经讲过认证插件会连同用户名和用户ID返回组。组可以一次给多个用户赋予权限,而不是必须单独给用户赋予权限。

  由插件返回的组仅仅是表示组名称的字符串,但是系统内置的组会有一些特殊的含义。

    • system:unauthenticated组用于所有认证插件都不会认证客户端身份的请求。
    • system:authenticated组会自动分配给一个成功通过认证的用户。
    • system:serviceaccounts 组包含所有在系统中的 ServiceAccount。
    • system:serviceaccounts:<namespace>组包含了所有在特定命名空间中的ServiceAccount。

 

1.2 ServiceAccount介绍

  接下来更详细地探讨ServiceAccount。已经了解API服务器要求客户端在服务器上执行操作之前对自己进行身份认证,并且已经了解了pod是怎么通过发送 /var/run/secrets/kubernetes.io/serviceaccount/token文件内容来进行身份认证的。这个文件通过加密卷挂载进每个容器的文件系统中。

  但是那个文件具体表不了什么呢?每个pod都与一个ServiceAccount相关联,它代表了运行在pod中应用程序的身份证明。token文件持有ServiceAccount的认证token。应用程序使用这个token连接API服务器时,身份认证插件会对ServiceAccount进行身份认证,并将ServiceAccount的用户名传回API服务器内部。ServiceAccount用户名的格式像下面这样:

system:serviceaccount:<namespace>:<service account name>

  API服务器将这个用户名传给己配置好的授权插件,这决定该应用程序所尝试执行的操作是否被ServiceAccount允许执行 。

  ServiceAccount只不过是一种运行在pod中的应用程序和API服务器身份认证的一种方式。如前所述, 应用程序通过在请求中传递ServiceAccount token来实现这一点。

  了解ServiceAccount资源

  ServiceAccount就像Pod、Secret、ConfigMap等一样都是资源,它们作用在单独的命名空间,为每个命名空间自动创建一个默认的ServiceAccount(你的pod会一直使用)。

  可以像其他资源那样查看ServiceAccount列表:

$ kubectl get sa
NAME      SECRETS   AGE
default   1         1d

  如你所见,当前命名空间只包含defaultServiceAccount,其他额外的ServiceAccount可以在需要时添加。每个pod都与一个ServiceAccount相关联,但是多个pod可以使用同一个ServiceAccount。通过图12.1可以了解,pod只能使用同一个命名空间中的ServiceAccount。

  ServiceAccount如何和授权进行绑定

  在pod的manifest文件中,可以用指定账户名称的方式将一个ServiceAccount赋值给一个pod。如果不显式地指定ServiceAccount的账户名称,pod会使用在这个命名空间中的默认ServiceAccount。

  可以通过将不同的ServiceAccount赋值给pod来控制每个pod可以访问的资源。当API服务器接收到一个带有认证token的请求时,服务器会用这个token来验证发送请求的客户端所关联的ServiceAccount是否允许执行请求的操作。API服务器通过管理员配置好的系统级别认证插件来获取这些信息。其中一个现成的授权插件是基于角色控制的插件(RBAC),这个插件会在本章后续进行讨论。从Kubernetes1.6版本开始,RBAC插件是绝大多数集群应该使用的授权插件。

 

1.3 创建ServiceAccount

  己经讲过每个命名空同都拥有一个默认的ServiceAccount,也可以在需要时创建额外ServiceAccount。但是为什么应该费力去创建新的ServiceAccount而不是对所有的pod都使用默认的ServiceAccount ?

  显而易见的原因是集群安全性。不需要读取任何集群元数据的pod应该运行在一个受限制的账户下,这个账户不允许它们检索或修改部署在集群中的任何资源。需要检索资源元数据的pod应该运行在只允许读取这些对象元数据的ServiceAccount下。反之,需要修改这些对象的pod应该在它们自己的ServiceAccount下运行,这些ServiceAccount允许修改API对象。

  下面了解一下如何创建其他的ServiceAccount,它们如何与密钥进行关联,以及如何将它们分配给pod。

  创建ServiceAccount

  得益于kubectl create serviceaccount命令,创建SeiviceAccount非常容易。比如新创建一个名为foo的ServiceAccount:

$ kubectl create serviceaccount foo
serviceaccount "foo" created

  然后如下面的代码清单所示的那样,可以使用describe命令来查看SeiviceAccount。

#代码 12.1使用 kubectl describe命令查看ServiceAccount
$ kubectl describe sa foo
Name:               foo
Namespace:          default
Labels:             <none>
Image pull secrets: <none>               #这些会被自动添加到使用这个ServiceAccount的所有pod中
Mountable secrets:  foo-token-qzq7j      #如果强制使用可挂载的密钥,那么使用这个ServiceAccount的pod只能挂载这些密钥
Tokens:             foo-token-qzq7j      #认证token,第一个token挂载在容器内

  可以看到,己经创建了自定义的token密钥,并将它和ServiceAccount相关联。如果通过kubectl describe secret foo-token-qzq7j查看密钥里面的数据,如下面的代码清单所示,就会发现它包含了和默认的ServiceAccount相同的条目(CA证书、命名空间和token),当然这两个token本身显然是不相同的。

#代码12.2 查看自定义的ServiceAccount密钥
$ kubectl describe secret foo-token-qzq7j
ca.crt:   1066 bytes 
namespace:    
token:   eyJhbGciOiJSUzI1NiisinRScCI6IkpXVCJ9...

  注意: 你可能已经了解过JSON Web Token (JWT)。ServiceAccount中使用的身份认证token就是JWT token。

  了解ServiceAccount上的可挂载密钥

  通过使用kubectl describe命令查看SeviceAccount时,token会显示在可挂载密钥列表中。下面来解释一下这个列表代表什么。在讲解configmap和secret的时候如何创建密钥并且把它们挂载进一个pod里。在默认情况下,pod可以挂载任何它需要的密钥。但是我们可以通过对ServiceAccount进行配置,让pod只允许挂载ServiceAccount中列出的可挂载密钥。为了开启这个功能,ServiceAccount必须包含以下注解:kubernetes.io/enforce-mountable-secrets="true"。

  如果ServiceAccount被加上了这个注解,任何使用这个ServiceAccount的pod只能挂载进ServiceAccount的可挂载密钥-这些pod不能使用其他的密钥。

  了解ServiceAccount的镜像拉取密钥

  ServiceAccount也可以包含镜像拉取密钥的list。

  下面的代码清单中显示了 ServiceAccount定义的一个例子。

#代码12.3 带有镜像拉取密钥的ServiceAccount:sa-image-pull-secrets.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: my-service-account
imagePullSecrets:
- name: my-dockerhub-secret

  ServiceAccount的镜像拉取密钥和它的可挂载密钥表现有些轻微不同。和可挂载密钥不同的是,ServiceAccount中的镜像拉取密钥不是用来确定一个pod可以使用哪些镜像拉取密钥的。添加到ServiceAccount中的镜像拉取密钥会自动添加到所有使用这个ServiceAccount的pod中。向ServiceAccount中添加镜像拉取密钥可以不必对每个pod都单独进行镜像拉取密钥的添加操作。

 

1.4 将ServiceAccount分配给pod

  在创建另外的ServiceAccount之后,需要将它们赋值给pod。通过在pod定义文件中的spec.serviceAccountName字段上设置ServiceAccount的名称来进行分配。

  注意:pod的ServiceAccount必须在pod创建时进行设置,后续不能被修改。

  创建使用自定义ServiceAccount的pod

  现在用基于tuturn/curl镜像的容器上的pad,并在其旁边放置了一个ambassador容器。使用它来查看API服务器的REST接口。这个ambassador容器会运行kubectl proxy进程,这个进程会使用pod的ServiceAccount的token和API服务器进行身份认证。

  现在可以修改pod,让pod使用几分钟前创建的foo ServiceAccount。接下来的代码清单展示了这个pod的定义。

#代码12.4 使用一个非默认ServiceAccount的pod:curl-custom-sa.yaml
apiVersion: v1
kind: Pod
metadata:
  name: curl-custom-sa
spec:
  serviceAccountName: foo
  containers:
  - name: main
    image: tutum/curl
    command: ["sleep", "9999999"]
  - name: ambassador
    image: luksa/kubectl-proxy:1.6.2

  为了确认自定义的ServiceAccount token已经挂载进这两个容器中, 如下面的代码清单所示的那样,可以打印出这个token的内容。

#代码12.5 查看挂载pod容器内的token
$ kubectl exec -it curl-cuetom-sa -c main 
cat /var/run/secrets/kubernetes.io/serviceaccount/token 
eyJhbGciOiJSUzI1NiisinR5cCI6IkpXVCJ9 ... 

  通过对比代码清单12.5和12.2中token的字符串,会发现这个token来自foo ServiceAccount的token。

  使用自定义的ServiceAccounttoken和API服务器进行通信

  来看看是否可以使用这个token和API服务器进行通信。前面提到过,ambassador容器在使用这个token和服务器进行通信,因此可以通过ambassador来测试这个token,这个ambassador监听在localhost:8001上,如下面的代码清单所示。

#代码 12.6 使用自定义的ServiceAccount和API服务器进行通信
$ kubectl exec -it curl-custom-sa -c main curl localhost:8001/api/v1/pods
{
  "kind": "PodList",
  "apiVersion": "v1",
  "metadata": {
    "selfLink": "/api/v1/pods",
    "resourceVersion": "433895"
  },
  "items": [
  ...

  好的,从服务器得到了正确的响应,也就意味着自定义的ServiceAccount可以允许列出pod。这可能是因为集群没有使用RBAC授权插件,或者你给了所有的ServiceAccount全部的权限。

  如果集群没有使用合适的授权,创建和使用额外的ServiceAccomt并没有多大意义,因为即使默认的ServiceAccount也允许执行任何操作。在这种情况下,使用ServiceAccount的唯一原因就是前面讲过的加强可挂载密钥,或者通过ServiceAccount提供镜像拉取密钥。

  如果使用RBAC授权插件,创建额外的ServiceAccount实际上是必要的,会在后面讨论RBAC授权插件的使用。

 

2.通过基于角色的权限控制加强集群安全

  从Kubernetes1.6.0版本开始,集群安全性显著提高。在早期版本中,如果你设法从集群中的一个pod获得了身份认证token,就可以使用这个token在集群中执行任何你想要的操作。如果在谷歌上搜索,可以找到演示如何使用path traversal(或者directory traversal)攻击的例子(客户端可以检索位于Web服务器的Web根目录之外的文件)。通过这种方式可以获取token,并用这个token在不安全的Kubernetes集群中运行恶意的pod。

  但是在Kubernetes1.8.0版本中,RBAC授权插件升级为GA(通用可用性), 并且在很多集群上默认开启(例如,通过kubadm部署的集群)。RBAC会阻止未授权的用户查看和修改集群状态。除非授予默认的ServiceAccount额外的特权,否则默认的ServiceAccount不允许查看集群状态,更不用说以任何方式去修改集群状态。要编写和KubernetesAPI服务器通信的APP,需要了解如何通过RBAC具体的资源管理授权。

  注意:除了RBAC插件,Kubernetes也包含其他的授权插件比如基于属性的访问控制插件(ABAC)、WebHook插件和自定义插件实现。但是,RBAC插件是标准的。

 

2.1 介绍RBAC授权插件

  Kubernetes API服务器可以配置使用一个授权插件来检查是否允许用户请求的动作执行。因为API服务器对外暴露了REST接口,用户可以通过向服务器发送HTTP请求来执行动作,通过在请求中包含认证凭证来进行认证(认证token、用户名和密码或者客户端证书)。

  了解动作

  但是有什么动作?REST客户端发送GET、POST、PUT、DELETE和其他类型的HTTP请求到特定的URL路径上,这些路径表示特定的REST资源。在Kubernetes中,这些资源是Pod、Service、Secret,等等。以下是Kubernetes请求动作的一些例子:

    • 获取pod
    • 创建服务
    • 更新密钥

  这些示例中的动词(get、create、update)映射到客户端请求的HTTP方法(GET、POST、PUT)上(完整的映射如表12.1所示)。名词(Pod、Service、Secret)显然是映射到Kubernetes上的资源。

  例如RBAC这样的授权插件运行在API服务器中,它会决定一个客户端是否允许在请求的资源上执行请求的动词。

表12.1 认证动词和HTTP方法之间的映射关系
HTTP方法 单一资源的动词 集合的动词
GET、HEAD get(以及watch用于监听) list(以及watch)
POST create n/a
PUT update n/a
PATCH patch n/a
DELETE Delete deletecollection

  除了可以对全部资源类型应用安全权限,RBAC规则还可以应用于特定的资源实例(例如,一个名为myservice的服务),并且后面会看到权限也可以应用于non-resource(非资源)URL路径,因为并不是API服务器对外暴露的每个路径都映射到一个资源(例如/api路径本身或服务器健康信息在的路径/healthz)。

  了解RBAC插件

  顾名思义,RBAC授权插件将用户角色作为决定用户能否执行操作的关键因素。 主体(可以是一个人、一个ServiceAccount,或者一组用户或ServiceAccount)和一个或多个角色相关联,每个角色被允许在特定的资源上执行特定的动词。  

  如果一个用户有多个角色,他们可以做任何他们的角色允许他们做的事情。如果用户的角色都没有包含对应的权限,例如,更新密钥,API服务器会阻止用户3个“他的”对密钥执行PUT或PATCH请求。

  通过RBAC插件管理授权是简单的,这一切都是通过创建四种RBAC特定的Kubernetes资源来完成的,会在下面介绍这个过程。

 

2.2 介绍RBAC资源

  RBAC授权规则是通过四种资源来进行配置的,它们可以分为两个组:

    • Role(角色)和ClusterRole(集群角色),它们指定了在资源上可以执行哪些动词。
    • RoleBinding(角色绑定)和ClusterRoleBinding(集群角色绑定),它们将上述角色绑定到特定的用户、组或ServiceAccounts上。

  角色定义了可以做什么操作,而绑定定义了谁可以做这些操作(如图12.2所示)。

  角色和集群角色,或者角色绑定和集群角色绑定之间的区别在于角色和角色绑定是命名空间的资源,而集群角色和集群角色绑定是集群级别的资源(不是命名空间的),如图12.3所示。 

  从图中可以看到,多个角色绑定可以存在于单个命名空间中(对于角色也是如此)。同样地,可以创建多个集群绑定和集群角色。图中显示的另外一件事情是,尽管角色绑定是在命名空间下的,但它们也可以引用不在命名空间下的集群角色。学习这四种资源及其影响的最好方法就是在实践中尝试。可以现在就开始尝试。

  开始练习

  在研究RBAC资源是怎样通过API服务器影响你可以执行什么操作之前,需要确定RABC在集群中己经开启。首先,确保使用的Kubernetes在1.6版本以上,并且RBAC插件是唯一的配置生效的授权插件。可以同时并行启用多个插件,如果其中一个插件允许执行某个操作,那么这个操作就会被允许。

  注意:如果正在使用GKE1.6或1.7,需要在创建集群时使用--no-enable-legacy-authcrization选项来显式地禁用老版本遗留的授权,如果正在使用Minikube,可能还需要在启动Minikube时使用--extra-config=apiserver.Authorization.Mode=RBAC选项来启用RBAC。

  关于如果给了所有权限(匿名用户也可以访问)如何禁用RBAC的指令进行操作,现在通过运行下面的命令可以重新启用RBAC:

$ kubectl delete clusterrolebinding permissive-binding 

  为了尝试RBAC,可以运行一个pod,通过它尝试和API服务器进行通信。但是这次,要在不同的命名空间运行两个pod,用来观察每个命名空间表现出来的安全性。

  现在要运行一个容器(基于kubectl proxy的镜像),并且直接在容器中使用kubectl exec运行curl命令。代理会负责验证和HTTPS,因此可以关注API服务器的安全性授权。

  创建命名空间和运行pod

  创建一个在命名空间foo中的pod和另一个在命名空间bar中的pod,如下面的代码清单所示。

#代码12.7 在不同的命名空间中运行测试pod
$ kubectl create ns foo
namespace ”foo" created   #集群范围(资源不属于某个命名空间)
$ kubectl run test --image=luksa/kubectl-proxy -n foo 
deployment "test" created
$ kubectl create ns bar 
namespace "bar" created
$ kubectl run test --image=luksa/kubectl-proxy -n bar
deployment "best" created

  现在打开两个命令行终端,并使用kubectl exec在两个pod中分别(每个终端对应一个pod的shell)运行一个shell。例如,要在命名空间foo中运行pod中的shell,首先要获得pod的名称:

$ kubectl get po -n foo
NAME                   READY     STATUS    RESTARTS   AGE
test-145485760-ttq36   1/1       Running   0          1m

  然后在 kubectl exec命令中使用这个名称:

$ kubectl exec -it test-145485760-ttq36 -n foo sh
/ #

  对于在bar命名空间中的pod,在另外一个命令行终端执行同样的操作。

  列出pod中的服务

  为了验证RBAC是否己经幵启并且阻止pod读取集群状态,可以使用curl命令来列出foo命名空间中的服务:

/# curl localhost:8001/api/v1/namespaces/foo/services
User "system:serviceaccount:foo:default" cannot list services in the namespace "foo".

  正在连接到localhost:8001,这是kubectl proxy进程监听的地址。这个进程接收到请求并将其发送到API服务器,同时以foo命名空间中默认的ServiceAccount进行身份认证(从API服务器的响应中可以明显看出这一点)。

  API服务器响应表明ServiceAccount不允许列出foo命名空间中的服务,即使pod就运行在同一个命名空间中。可以看到RBAC插件己经起作用了。ServiceAccount的默认权限不允许它列出或修改任何资源。接下来让ServiceAccount做到这一点。首先,需要创建一个Role资源。

 

2.3 使用Role和RoleBinding

  Role资源定义了哪些操作可以在哪些资源上执行(或者如前面讲过的,哪种类型的HTTP请求可以在哪些RESTful资源上执行)。下面的代码清单定义了一个Role,它允许用户获取并列出foo命名空间中的服务。

#代码12.8 一个Role的定义文件:service-reader.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: foo                   #Role所在的命名空间(如果没有填写命名空间则使用当前的命名空间)
  name: service-reader
rules:
- apiGroups: [""]                     #Service是核心apiGroup的资源,所以没有apiGroup名,就是“”
  verbs: ["get", "list"]              #获取独立的Servcie(通过名字)并且列出所有予许的服务
  resources: ["services"]         #这条规则和服务有关(必须使用复数的名字!)

  警告:在指定资源时必须使用复数的形式。

  这个Role资源会在foo命名空间中创建出来。应该了解每个资源类型属于一个API组,在资源清单(manifest)的apiVersion字段中指定API组(以及版本)。在角色定义中,需要为定义包含的每个规则涉及的资源指定apiGroup。如果允许访问属于不同API组的资源,可以使用多种规则。

  注意:在本例中,允许访问所有服务资源,但是也可以通过额外的resourceNames字段指定服务实例的名称来限制对服务实例的访问。

  图12.4中显示了角色,以及它的动词和资源,还有它的命名空间。

  创建角色

  现在在foo命名空间中创建先前讲的角色:

$ kubectl create -f service-reader.yaml -n foo 
role "service-reader" created 

  注意:如果你正在使用GKE, 先前的命令可能因为没有集群管理员权限而发生错误。运行下面的命令来获取这些权限

$ kubectl create clusterrolebinding cluster-admin-binding 
--clusterrole=cluster-admin --user=your.email@address.com 

  可以使用特殊的kubectl create role命令创建service-reader角色,而不是通过YAML文件来创建。使用这个方法来创建bar命名空间中的角色.

$ kubectl create role service-reader --verb=get --verb=list
--resource=services -n bar 
role "service-reader" created

  这两个角色会允许在两个pod(分别在foo和bar命名空间中运行)中列出foo和bar命名空间中的服务。但是创建了两个角色还不够(可以执行curl命令来再次检查),你需要将每个角色绑定到各自命名空间中的ServiceAccount上。

  绑定角色到ServiceAccount

  角色定义了哪些操作可以执行,但没有指定谁可以执行这些操作。要做到这一点,必须将角色绑定一个到主体,它可以是一个user(用户)、一个ServiceAccount或一个组(用户或ServiceAccount的组)。

  通过创建一个RoleBinding资源来实现将角色绑定到主体。运行以下命令,可以将角色绑定到default ServiceAccount:

$ kubectl create rolebinding test --role=service-reader --serviceaccount=foo:default -n foo
rolebinding "test" created

  命令应该是不言自明的。你会创建一个RoleBinding资源,它将service-reader角色绑定到命名空间foo中的default ServiceAccount上。这个RoleBinding资源会被创建在命名空间foo中。RoleBinding资源和ServiceAccount和角色的引用如图12.5所示。

  注意:如果要绑定一个角色到一个user (用户)而不是ServiceAccount上,使用--user作为参数来指定用户名。如果要绑定角色到组,可以使用--group参数。

   下面的代码清单显示了你创建的RoleBinding的YAML格式。

#代码12.9 一个RoleBinding引用一个Role
$ kubectl get rolebinding test -n foo -o yaml
apiVersion: rbac.authorization.k8s.io/v1 
kind: RoleBinding 
metadata: 
  name: test 
  namespace:foo
roleRef:
  apiGroup: rbac.authorization.k8s.io 
  kind:Role                                                #这个Rolebinding引用了service-reader Role
  name: service-reader 
subjects:
-kind: ServiceAccount                              #并且将它绑定到foo命名空间中的default ServiceAccount上
  name:default
  namespace: foo

  如你所见,RoleBinding始终引用单个角色(从roleRef属性中可以看出),但是可以将角色绑定到多个主体(例如,一个或多个ServiceAccount和任意数量的用户或组)上。因为这个RoleBinding将角色绑定到一个ServiceAccount上,这个ServiceAccount运行在foo命名空间中的pod上,所以现在可以列出来自pod中的服务。

#代码12.10 从API服务器中获取服务
/ # curl localhost:8001/api/v1/namespaces/foo/services
{
  "kind""ServiceList",
  "apiVersion": "v1",
  "metadata": {
    "selfLink""/api/v1/namespaces/foo/services", 
    "resourceVersion": "24906"
    },
    "items": []                         #item列表是空的,因为没有服务存在
} 

  在角色绑定中使用其他命名空间的ServiceAccount

  bar命名空间中的pod不能列出自己命名空间中的服务,显然也不能列出foo命名空间中的服务。但是可以修改在foo命名空间中的RoleBinding并添加另一个pod的ServiceAccount,即使这个ServiceAccount在另一个不同的命名空间中。运行下面的命令:

$ kubectl edit rolebinding test -n foo 

  然后对于列出的subjects增加下面几行,如下面的代码清单所示。

#代码 12.11 从另一个命名空间引用ServiceAccount
subjects:
- kind: ServiceAccount
  name: default                #引用来自bar命名空间中的default ServiceAccount
  namespace: bar   

  现在,就可以从运行在bar命名空间里的pod中列出foo命名空间中的服务。运行和代码清单12.10中相同的命令,但是在另一个终端执行,这个终端运行另一个pod的shell。

  在探讨ClusterRole和ClusterRoleBinding之前,总结一下当前拥有的RBAC资源。在foo命名空间中有一个RoleBinding,它引用service-reader角色(也在foo命名空间中),并且绑定foo和bar命名空间中的default ServiceAccount,如图 12.6 所示。

 

2.4 使用 ClusterRole和ClusterRoleBinding

  Role和RoleBinding都是命名空间的资源,这意味着它们属于和应用在一个单一的命名空间资源上。但是,RoleBinding可以引用来自其他命名空间中的ServiceAccount。

  除了这些命名空间里的资源,还存在两个集群级别的RBAC资源:ClusterRole和ClusterRoleBinding,它们不在命名空间里。看看为什么需要它们。

  一个常规的角色只允许访问和角色在同一命名空间中的资源。如果希望允许跨不同命名空间访问资源,就必须要在每个命名空间中创建一个Role和RoleBinding。如果想将这种行为扩展到所有的命名空间(集群管理员可能需要)需要在每个命名空间中创建相同的Role和RoleBinding。当创建一个新的命名空间时,必须记住也要在新的命名空间中创建这两个资源。

  但是,一些特定的资源完全不在命名空间中(包括Node、PersistentVolume、Namespace,等等)。之前也提到过API服务器对外暴露一些不表示资源的URL路径(例如/healthz)。常规角色不能对这些资源或非资源型的URL进行授权,但是ClusterRole可以。

  ClusterRole是一种集群级资源,它允许访问没有命名空间的资源和非资源型的URL,或者作为单个命名空间内部绑定的公共角色,从而避免必须在每个命名空间中重新定义相同的角色。

  允许访问集群级别的资源

  己经提到过,可以使用ClusterRole来允许集群级别的资源访问。现在了解一下如何允许pod列出集群中的PersistentVolume。首先,创建一个叫作pv-reader的ClusterRole:

$ kubectl create clusterrole pv-reader --verb=get,list --resource=persistentvolumes
clusterrole "pv-reader" created

  这个ClusterRole的YAML格式内容展示在下面的代码清单中。

#代码12.12 —个ClusterRole的定义
$ kubectl get clusterrole pv-reader -o yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:                                       
  name: pv-reader                               
  resourceVersion: "39932"                      
  selfLink: ...                                 
  uid: e9ac1099-30e2-11e7-955c-080027e6b159     
rules:
- apiGroups:                                    
  - ""                                          
  resources:                                    
  - persistentvolumes                           
  verbs:                                        
  - get                                         
  - list                                        

  在将这个ClusterRole绑定到pod的ServiceAccount之前,请验证pod是否可以列出PersistentVolume。在第一个命令行终端上运行下面的命令,这个终端正在运行一个shell,这个shell在foo命名空间下的pod内:

/ # curl localhost:8001/api/v1/persistentvolumes
User "system:serviceaccount:foo:default" cannot list persistentvolumes at the cluster scope.

  注意:这个URL没有包含命名空间,因为PersistentVolume不在命名空间里。

  和预期的一样,默认ServiceAccount不能列出PersistentVolume,需要将ClusterRole绑定到ServiceAccount来允许它这样做。ClusterRole可以通过常规的RoleBinding(角色绑定)来和主体绑定,因此要创建一个RoleBinding:

$ kubectl create rolebinding pv-test --clusterrole=pv-reader --serviceaccount=foo:default -n foo
rolebinding "pv-test" created

  现在尝试列出PersistentVolume

/ # curl localhost:8001/api/v1/persistentvolumes
User "system:serviceaccount:foo:default" cannot list persistentvolumes at the cluster scope.

  还是不行,查看一下RoleBinding的代码。

#代码 12.13 一个RoleBinding引用一个CLusterRole
$ kubectl get rolebindings pv-test -o yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: pv-test
  namespace: foo
  ...
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole                           #这个绑定引用了pv-reader CLusterRole
  name: pv-reader                        
subjects:
- kind: ServiceAccount                    #这个绑定的主体是在foo命名空间中的默认ServiceAccount
  name: default                          
  namespace: foo                         

  这个YAML内容看起来相当正确。它正在引用正确的ClusterRole和正确的ServiceAccount,如图12.7所示。那么有什么问题呢?

  尽管可以创建一个RoleBinding并在想开启命名空间资源的访问时引用一个ClusterRole,但是不能对集群级别(没有命名空间的)资源使用相同的方法。必须始终使用ClusterRoleBinding来对集群级别的资源进行授权访问。

  幸运的是,创建一个ClusterRoleBinding和创建一个RoleBinding并没有什么太大区别,但是首先要清理和删除RoleBinding:

$ kubectl delete rolebinding pv-test
rolebinding "pv-test" deleted

  接下来创建ClusterRoleBinding:

$ kubectl create clusterrolebinding pv-test --clusterrole=pv-reader --serviceaccount=foo:default
clusterrolebinding "pv-test" created

  上面在命令中使用clusterrolebinding替换了rolebinding,并且不(需要)指定命名空间。图12.8显示你现在有的资源。

  现在看一下,现在是否可以列出PersistentVolume:

/ # curl localhost:8001/api/v1/persistentvolumes
{
"kind""PersistentVolumeList",
"apiVersion""v1",
}

  

  可以了!这表明在授予集群级别的资源访问权限时,必须使用一个ClusterRole和一个ClusterRoleBinding。

  提示:记住一个RoleBinding不能授予集群级别的资源访问权限,即使它引用了一个ClusterRoleBinding。

  允许访问非资源型的URL

  己经提过,API服务器也会对外暴露非资源型的URL。访问这些URL也必须要显式地授予权限;否则,API服务器会拒绝客户端的请求。通常,这个会通 过system:discovery ClusterRole和相同命名的ClusterRoleBinding自动完成,它出现在其他预定义的ClusterRoles和ClusterRoleBindings中(后面章节会讨论)。

  现在查看一下下面的代码清单中的system:discovery ClusterRole。

#代码12.14 默认system:discovery CLusterRole
$ kubectl get clusterrole system:discovery -o yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: system:discovery
  ...
rules:
- nonResourceURLs:           #这条规则指向了非资源型的URL而不是资源
  - /api                
  - /api/*              
  - /apis               
  - /apis/*             
  - /healthz            
  - /swaggerapi         
  - /swaggerapi/*       
  - /version            
  verbs:                       #对于这些URL只有HTTPGET方法是被予许的
  - get                 

  可以发现,ClusterRole引用的是URL路径而不是资源(使用的是非资源URL字段而不是资源字段)。verbs字段只允许在这些URL上使用GET HTTP方法。

  注意:对于非资源型URL,使用普通的HTTP动词,如post、put和patch,而不是create或update。动词需要使用小写的形式指定。

  和集群级别的资源一样,非资源型的URLClusterRole必须与ClusterRoleBinding结合使用。把它们和RoleBinding绑定不会有任何效果。system:discovery ClusterRole 有一个与之对应的systenrdiscovery ClusterRoleBinding,所以现在用下面的代码清单来看看它们里面有什么。

#代码12.15 默认的system:discovery CLusterRoleBinding
[root@master01 ~]# kubectl get clusterrolebinding system:discovery -o yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: system:discovery
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole                                          #ClusterRoleBinding引用了system:discovery ClusterRole
  name: system:discovery
subjects:
- apiGroup: rbac.authorization.k8s.io
  kind: Group                                                   #它将ClusterRole绑定到所有认证过和没有认证过的用户上
  name: system:authenticated
- apiGroup: rbac.authorization.k8s.io
  kind: Group
  name: system:unauthenticated

  YAML内容显示ClusterRoleBinding正如预期的那样指向system:discovery ClusterRole。它绑定到了两个组,分别是system:authenticated和system:unauthenticated,这使得它和所有用户绑定在一起。这意味着每个人都绝对可以访问列在ClusterRole中的URL。

  注意:组位于身份认证插件的域中。API服务器接收到一个请求时,它会调用身份认证插件来获取用户所属组的列表,之后授权中会使用这些组的信息。

  可以通过在一个pod内和本地机器访问/api URL路径来进行确认(通过 kubectl proxy,意味着你会使用pod的ServiceAccount来进行身份认证),访问URL时不要指定任何认证的token(这会让你成为一个未认证的用户)。

$ curl https://$(minikube ip):8443/api -k
{
  "kind": "APIVersions", 
  "versions":[
  ...

  现在我们使用ClusterRole和ClusterRoleBinding授权访问集群级别的资源和非资源型的URL。现在来了解一下ClusterRole怎么和命名空间中的RoleBinding—起来授权访问RoleBinding的命名空间中的资源。

  使用ClusterRole来授权访问指定命名空间中的资源

  ClusterRole不是必须一直和集群级别的ClusterRoleBinding捆绑使用。它们也可以和常规的有命名空间的RoleBinding进行捆绑。己经研究过预先定义好的ClusterRole,所以让我们了解另一个名为view的ClusterRole,如下面的代码清单所示。

#代码清单12.16 默认的view CLusterRole
[root@master01 ~]# kubectl get clusterrole view -o yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: view
  ...
rules:
- apiGroups:
  - ""
  resources:                                       #这条规则引用于浙西诶资源上(注意:都是命名空间的资源)
  - configmaps
  - endpoints
  - persistentvolumeclaims
  - pods
  - replicationcontrollers
  - replicationcontrollers/scale
  - serviceaccounts
  - services
  verbs:                                             #如ClusterRole名称表述的那样,它只予许读操作,不能对列出的资源进行写操作
  - get
  - list
  - watch
  ...

  这个ClusterRole有很多规则。只有第一条会展示在这个代码清单中。这个规则允许get、list和watch资源,这些资源就像ConfigMap、Endpoint、PersistentVolumeClaim,等等。这些资源是有命名空间的,即使正在了解的是一个ClusterRole(它不是一个常规的、有命名空间的角色)。这个ClusterRole到底做了什么?

  这取决于它是和ClusterRoleBinding还是和RoleBinding绑定(可以和其中的一个进行绑定)。如果创建了一个ClusterRoleBinding并在它里面引用了ClusterRole, 在绑定中列出的主体可以在所有命名空间中查看指定的资源。相反,如果创建的是一个RoleBinding,那么在绑定中列出的主体只能查看在RoleBinding命名空间中的资源。现在可以尝试使用这两个选项。

  可以看到这两种方式如何影响测试pod列出pod的能力。首先,了解这些绑定生效之前会发生什么:

/# curl localhost:8001/api/v1/pods
User "system:serviceaccount:foo:default" cannot list pods at the cluster scope./#
/ #curl localhost:8001/api/v1/namespaces/foo/pods
User "system:serviceaccount:foo:default" cannot list pods in the namespace "foo"

  通过使用第一个命令,可以试着列出所有命名空间的pod。使用第二个命令,可以试着列出在foo命名空间中的pod。服务器都不允许你执行这些操作。

  现在,了解一下创建一个ClusterRoleBinding并且把它绑定到pod的ServiceAccount上时发生了什么:

$ kubectl create clusterrolebinding view-test --clusterrole=view --serviceaccount=foo:default
clusterrolebinding "view-test" created

  现在这个pod能列出在foo命名空间中的pod了吗?

/ # curl localhost:8001/api/v1/namespaces/foo/pods
{
  "kind""PodList",
  "apiVersion": "v1",

  可以了!因为创建了一个ClusterRoleBinding,并且它应用在所有的命名空间上。通过它命名空间foo中的pod也可以列出bar命名空间中的pod:

/# curl localhost: 8001/api/v1/namespaces/bar/pods
{
  "kind""PodList",
  "apiVersion""v1",

  这个pod现在允许列出一个不同的命名空间中的pod。 它也可以使用/api/v1/pods URL路径来检索所有命名空间中的pod。

/# curl localhost:8001/api/v1/pods
{
  "kind""PodList",
  "apiVersion""v1",

  正如预期的那样,这个pod可以获取集群中所有pod的列表。总之,将ClusterRoleBinding和ClusterRole结合指向命名空间的资源,允许pod访问任何命名空间中的资源,如图12.9所示。

  现在,了解如果用一个RoleBinding替换ClusterRoleBinding会发生什么。 首先,删除ClusterRoleBinding:

$ kubectl delete clusterrolebinding view-test
clusterrolebinding "view-test" deleted

  接下来创建一个RoleBinding作为替代。因为RoleBinding使用了命名空间,所以需要指定希望RoleBinding创建在里面的命名空间。在foo命名空间中创建RoleBinding:

$ kubectl create rolebinding view-test --clusterrole=view --serviceaccount=foo:default -n foo
rolebinding "view-test" created

  现在在foo命名空间中有一个RoleBinding,它将在同一个命名空间中的default ServiceAccount绑定到view ClusterRole。现在pod可以访问什么资源?

/# curl localhost:8001/api/v1/namespaces/foo/pods
{
  "kind""PodList",
  "apiVersion": "v1",
/# curl localhost:8001/api/v1/namespaces/bar/pods
User "system:serviceaccount:foo:default" cannot list pods in the namespace "bar".
/# curl localhost:8001/api/v1/pods
User "system:serviceaccount:foo:default" cannot list pods at the cluster scope.

  可以看到,这个pod可以列出foo命名空间中的pod,但并不是其他特定的命名空间或者所有的命名空间都能做到,如图12.10所示。

  总结Role、ClusterRole、 Rolebinding和ClusterRoleBinding的组合

  己经介绍了许多不同的组合,可能很难记住何时去使用对应的每个组合。 来看看如果对所有组合按每个特定的用例进行分类会不会对记忆有帮助,参考表12.2。

表12.2 何时使用具体的role和binding的组合
访问的资源 使用的角色类型 使用的绑定类型
集群级别的资源(Nodes、 Pers1stentVolumes、 ...) ClusterRole ClusterRoleBinding
非资源型URL(/api、/healthz、...) ClusterRole ClusterRoleBinding
在任何命名空间中的资源(和跨所有命名空间的资源) ClusterRole ClusterRoleBinding
在具体命名空间中的资源(在多个命名空间中重用这个相同的ClusterRole) ClusterRole RoleBinding
在具体命名空间中的资源(Role必须在每个命名空间中定义好) Role RoleBinding

 

2.5 了解默认的 ClusterRole和ClusterRoleBinding

  Kubernetes提供了一组默认的ClusterRole和ClusterRoleBinding,每次API服务器启动时都会更新它们。这保证了在错误地删除角色和绑定,或者Kubernetes的新版本使用了不同的集群角色和绑定配置时,所有的默认角色和绑定都会被重新创建。

  可以在下面的代码清单中看到默认的集群角色和绑定。

#代码12.17 列出所有ClusterRoleBinding和ClusterROle
$ kubectl get clusterrolebindings
NAME                                                   AGE
cluster-admin                                          713d
cluster-system=anonymous                               713d
...
system:controller:ttl-controller                       713d
system:coredns                                         713d
system:discovery                                       713d
system:kube-controller-manager                         713d
system:kube-dns                                        713d
system:kube-scheduler                                  713d
system:metrics-server                                  572d
system:node                                            713d
system:node-proxier                                    713d
system:volume-scheduler                                713d
 
$ kubectl get clusterroles
NAME                                                                   AGE
admin                                                                  713d
cluster-admin                                                          713d
edit                                                                   713d
...
system:controller:ttl-controller                                       713d
system:coredns                                                         713d
system:csi-external-attacher                                           713d
system:csi-external-provisioner                                        713d
system:discovery                                                       713d
system:heapster                                                        713d
system:kube-aggregator                                                 713d
system:kube-controller-manager                                         713d
system:kube-dns                                                        713d
system:kube-scheduler                                                  713d
system:kubelet-api-admin                                               713d
system:metrics-server                                                  572d
system:node                                                            713d
system:node-bootstrapper                                               713d
system:node-problem-detector                                           713d
system:node-proxier                                                    713d
system:persistent-volume-provisioner                                   713d
system:volume-scheduler                                                713d
view                                                                   713d

  view、edit、adrnin和cluster-admin ClusterRole是最重要的角色,它们应该绑定到用户定义pod中的ServiceAccount上。

  用view ClusterRole允许对资源的只读访问

  在前面的例子中,己经使用了默认的viewClusterRole。它允许读取一个命名空间中的大多数资源,除了Role、RoleBinding和Secret。你可能会想为什么Secrets不能被读取?因为Secrets中的某一个可能包含一个认证token,它比定义在viewClusterRole中的资源有更大的权限,并且允许用户伪装成不同的用户来获取额外的权限(权限扩散)。

  用edit ClusterRole允许对资源的修改

  接下来是editClusterRole,它允许你修改一个命名空间中的资源,同时允许读取和修改Secret。但是,它也不允许查看或修改Role和RoleBinding,这是为了防止权限扩散。

  用admin ClusterRole赋予一个命名空间全部的控制权

  一个命名空间中的资源的完全控制权是由admin ClusterRole赋予的。有这个ClusterRole的主体可以读取和修改命名空间中的任何资源,除了ResourceQuota和命名空间资源本身。edit和adminClusterRole之间的主要区别是能否在命名空间中查看和修改Role和RoleBinding。

  注意:为了防止权限扩散,API服务器只允许用户在已经拥有一个角色中列出的所有权限(以及相同范围内的所有权限)的情况下,创建和更新这个角色。

  用cluster-admin ClusterRole得到完全的控制

  通过将cluster-adminClusterRole赋给主体,主体可以获得Kubernetes集群完全控制的权限。正如前面了解的那样,adminClusterRole不允许用户修改命名空间的ResourceQuota对象或者命名空间资源本身。如果想允许用户这样做,需要创建一个指向cluster-adminClusterRole的RoleBinding。这使得RoleBinding中包含的用户能够完全控制创建RoleBinding所在命名空间上的所有方面。

  如果留心观察,可能己经知道如何授予用户一个集群中所有命名空间的完全控制权。就是通过在ClusterRoleBinding而不是RoleBinding中引用clutter-admin ClusterRole。

  了解其他默认的ClusterRole

  默认的ClusterRole列表包含了大量其他的ClusterRole,它们以system: 为前缀。这些角色用于各种Kubernetes组件中。在它们之中,可以找到如system:kube-scheduler之类的角色,它明显是给调度器使用的,syttem:node是给Kubelets组件使用的,等等。

  虽然Controller Manager作为一个独立的pod来运行,但是在其中运行的每个控制器都可以使用单独的ClusterRole和ClusterRoleBinding (它们以system:Controller:为前缀)。

  这些系统的每个ClusterRole都有一个匹配的ClusterRoleBinding,它会绑定到系统组件用来身份认证的用户上。例如,system:kube-scheduler ClusterRoleBinding将名称相同的ClusterRole分配给system:kube-scheduler用户,它是调度器作为身份认证的用户名。

 

2.6 理性地授予授权权限

  在默认情况下,命名空间中的默认ServceAccount除了未经身份验证的用户没有其他权限(你可能记得前面的示例之一,system:discovery ClusterRole和相关联的绑定允许任何人对一些非资源型的URL发送GET请求)。因此,在默认情况下,pod甚至不能查看集群状态。应该授予它们适当的权限来做这些操作。

  显然,将所有的SeviceAccounts赋予cluster-admin ClusterRole是一个坏主意。和安全问题一样,最好只给每个人提供他们工作所需要的权限,一个单独权限也不能多(最小权限原则)。

  为每个pod创建特定的ServiceAccount

  一个好的想法是为每一个pod(或一组pod的副本)创建一个特定ServiceAccount,并且把它和一个定制的Role(或ClusterRole)通过RoleBinding联系起来(不是ClusterRoleBinding因为这样做会给其他命名空间的pod对资源的访问权限,这可能不是你想要的)。

  如果你的一个pod(应用程序在它内部运行)只需要读取pod,而其他的pod也需要修改它们,然后创建两个不同的ServceAccount,并且让这些pod通过指定在pod spec中的serviceAccountName属性来进行使用,和在本章的第一部了解的那样。不要将这两个pod所需的所有必要权限添加到命名空间中的默认ServceAccount上。

posted @ 2021-05-24 22:33  小家电维修  阅读(322)  评论(0编辑  收藏  举报