Service Account也是一种账号,但它并不是给Kubernetes集群的用户(系统管理员、运维人员、租户用户等)用的,而是给运行在Pod里的进程用的,它为Pod里的进程提供了必要的身份证明。
在继续学习之前,请回顾6.1节API Server认证的内容。
在正常情况下,为了确保Kubernetes集群的安全,API Server都会对客户端进行身份认证,认证失败的客户端无法进行API调用。此外,在Pod中访问Kubernetes API Server服务时,是以Service方式访问名为Kubernetes的这个服务的,而Kubernetes服务又只在HTTPS安全端口443上提供,那么如何进行身份认证呢?这的确是个谜,因为Kubernetes的官方文档并没有清楚说明这个问题。
通过查看官方源码,我们发现这是在用一种类似HTTP Token的新认证方式—Service Account Auth,Pod中的客户端调用Kubernetes API时,在HTTP Header中传递了一个Token字符串,这类似于之前提到的HTTP Token认证方式,但有以下几个不同之处。
◎ 这个Token的内容来自Pod里指定路径下的一个文件(/run/secrets/kubernetes.io/serviceaccount/token),该Token是动态生成的,确切地说,是由Kubernetes Controller进程用API Server的私钥(--service-account-private-key-file指定的私钥)签名生成的一个JWT Secret。
◎ 在官方提供的客户端REST框架代码里,通过HTTPS方式与API Server建立连接后,会用Pod里指定路径下的一个CA证书(/run/secrets/kubernetes.io/serviceaccount/ca.crt)验证API Server发来的证书,验证是否为CA证书签名的合法证书。
◎ API Server在收到这个Token以后,会采用自己的私钥(实际上是使用service-account-key-file参数指定的私钥,如果没有设置此参数,则默认采用tls-private-key-file指定的参数,即自己的私钥)对Token进行合法性验证。
明白了认证原理,我们接下来继续分析在上面的认证过程中所涉及的Pod中的以下三个文件。
◎ /run/secrets/kubernetes.io/serviceaccount/token。
◎ /run/secrets/kubernetes.io/serviceaccount/ca.crt。
◎ /run/secrets/kubernetes.io/serviceaccount/namespace(客户端采用这里指定的namespace作为参数调用Kubernetes API)。
这三个文件由于参与到Pod进程与API Server认证的过程中,起到了类似secret(私密凭据)的作用,所以被称为Kubernetes Secret对象。Secret从属于Service Account资源对象,属于Service Account的一部分,在一个Service Account对象里面可以包括多个不同的Secret对象,分别用于不同目的的认证活动。
下面通过运行一些命令来加深我们对Service Account与Secret的直观认识。
首先,查看系统中的Service Account对象,看到有一个名为default的Service Account对象,包含一个名为default-token-77oyg的Secret,这个Secret同时是Mountable secrets,表明它是需要被挂载到Pod上的:

接下来看看default-token-77oyg都有什么内容:

从上面的输出信息中可以看到,default-token-77oyg包含三个数据项,分别是token、ca.crt、namespace。联想到Mountable secrets的标记,以及之前看到的Pod中的三个文件的文件名,我们恍然大悟:在每个命名空间中都有一个名为default的默认Service Account对象,在这个Service Account里面有一个名为Tokens的可以作为Volume被挂载到Pod里的Secret,Pod启动时,这个Secret会自动被挂载到Pod的指定目录下,用来协助完成Pod中的进程访问API Server时的身份鉴权。
如图6.3所示,一个Service Account可以包括多个Secret对象。

图6.3 Service Account中的Secret
其中,名为Tokens的Secret用于访问API Server的Secret,也被称为Service Account Secret;名为imagePullSecrets的Secret用于下载容器镜像时的认证,镜像库通常运行在Insecure模式下,所以这个Secret为空;用户自定义的其他Secret用于用户的进程中。
如果一个Pod在定义时没有指定spec.serviceAccountName属性,则系统会自动将其赋值为default,即大家都使用同一个命名空间中的默认Service Account。如果某个Pod需要使用非default的Service Account,则需要在定义时指定:

Kubernetes之所以要创建两套独立的账号系统,原因如下。
◎ User账号是给人用的,Service Account是给Pod里的进程用的,面向的对象不同。
◎ User账号是全局性的,Service Account则属于某个具体的命名空间。
◎ 通常来说,User账号是与后端的用户数据库同步的,创建一个新用户时通常要走一套复杂的业务流程才能实现,Service Account的创建则需要极轻量级的实现方式,集群管理员可以很容易地为某些特定任务创建一个Service Account。
◎ 对于这两种不同的账号,其审计要求通常不同。
◎ 对一个复杂的系统来说,多个组件通常拥有各种账号的配置信息,Service Account是在命名空间级别隔离的,可以针对组件进行一对一的定义,同时具备很好的“便携性”。
接下来深入分析Service Account与Secret相关的一些运行机制。
Service Account的正常工作离不开以下控制器:Service Account Controller、Token Controller、Admission Controller。
Service Account Controller的工作相对简单,它会监听Service Account和Namespace这两种资源对象的事件,如果在一个Namespace中没有默认的Service Account,那么它会为该Namespace创建一个默认的Service Account对象,这就是在每个Namespace下都有一个名为default的Service Account的原因。
Token Controller也监听Service Account的事件,如果发现在新建的Service Account里没有对应的Service Account Secret,则会用API Server私钥(--service-account-private-key-file指定的文件)创建一个Token,并用该Token、API Server的CA证书等三个信息产生一个新的Secret对象,然后放入刚才的Service Account中。如果监听到的事件是Service Account删除事件,则自动删除与该Service Account相关的所有Secret。此外,Token Controller对象也会同时监听Secret的创建和删除事件,确保与对应的Service Account的关联关系正确。
接下来就是Admission Controller的重要作用了,当我们在API Server的准入控制链中启用了Service Account类型的准入控制器时(这也是默认的设置),即在kube-apiserver启动参数中包括下面的内容时:

则针对Pod新增或修改的请求,Admission Controller会验证Pod里的Service Account是否合法,并做出如下控制操作:
◎ 如果spec.serviceAccount域没有被设置,则Kubernetes默认为其指定名称为default的Service accout。
◎ 如果Pod的spec.serviceAccount域指定了不存在的Service Account,则该Pod操作会被拒绝。
◎ 如果在Pod中没有指定ImagePullSecrets,那么这个spec.serviceAccount域指定的Service Account的ImagePullSecrets会被加入该Pod中。
◎ 给Pod添加一个特殊的volumeSource,在该Volume中包含Service Account Secret中的Token。
◎ 给Pod里的每个容器都增加对应的VolumeSource,将包含Secret的Volume挂载到Pod中所有容器的指定目录下(/var/run/secrets/kubernetes.io/serviceaccount)。
在Kubernetes 1.6版本以后,我们可以禁止自动创建Service Account对应的Secret了,在Service Account的YAML文件中增加automountServiceAccountToken:false属性即可,同时可以在某个Pod的YAML文件中增加此属性,以实现同样的效果。
在6.1节提到Kubernetes中用户的鉴权可采用基于OAuth 2.0的ODIC来实现,即由外部的OIDC Server(Identity Provider)提供Jwt格式的加密Token,而Service Account Token是由Kubernetes自身生成的符合Jwt格式的加密Token,因此Kubernetes也可以被视为具备了ODIC Server身份认证功能的服务。所以我们也可以把Kubernetes作为一个ODIC Server,与外部其他第三方的ODIC Server组成联邦,实现相互认证。这样一来,Kubernetes也可能凭借完善的BRAC用户权限机制,成为整个企业内部用户鉴权和授权的基础服务设施了。因此,Kubernetes从1.18版本开始便增加了一个名为Service Account Issuer Discovery的新特性(目前为Alpha阶段),允许Kubernetes集群作为一个ODIC Server发布出去,与外部的第三方可信系统组成联邦,第三方可信系统可以调用Kubernetes验证Service Account Token的合法性。