Kubernetes 服务Service Account 准入组件和Secret 私密凭据

2019-08-22 0 By admin

ServiceAccount 准入组件实现了Service Account 资源的自动化。
Servuce Account是一种账号,但他并不是给Kubernetes的集群的用户(系统管理员、运维人员、租户用户等),而是给运行在Pod里的进程用的,它为Pod里的进程提供必要的身份证明。

一、Service Account

Pod中访问Kubernetes API Server服务的时候,是以Service方式访问服务名为kubernetes这个服务的,而kubernetes服务又只在HTTPS安全端口443上提供服务,那么如何进行身份认证呢?

通过查看源码获知这是在用一种类似HTTP Token的新的认证方式ServiceAccount Auth,Pod中的客户端调用Kubernetes API的时候,在HTTP Header中传递了一个Token字符串,这类似于之前提到的HTTP Token认证方式,存在以下几个不同点:
1、此处的Token的内容来源于Pod里指定路径下的一个文件(/run/secrets/kubernetes.io/serviceaccount/token),这种token是动态生成的,确切的说,是由KubernetesController进程用API Server的私钥(–service-account-private-key-file指定的私钥)签名生成的一个JWT Secret。
2、官方提供的客户端REST框架代码里,通过HTTPS方式与API Server建立链接后,会用Pod里指定路径下的一个CA证书(/run/secrets/kubernetes.io/serviceaccount/ca.crt)验证API Server发来的证书,验证是否是被CA证书签名的合法证书。
3、API Server收到这个Token以后,采用自己的私钥(实际是使用参数service-account-key-file指定的私钥,如果此参数没有设置,则默认采用tls-private-key-file指定的参数,即自己的私钥),对token进行合法性验证。

1.1、认证文件的生成方式和使用

明白原理之后。接下来分析认证过程中涉及的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从属于ServiceAccount资源对象,属于Service Account的一部分,一个ServiceAccount对象里面可以包括多个不同的Secret对象,分别用于不同目的的认证活动。

1.2、下面通过命令来直观的加深对ServiceAccount的认识

查看系统中ServiceAccount对象,可以看到一个名为 default 的Service Account对象,包含一个名为 default-token-xxx 的Secret,这个Secret同时是“Mountable secrets”类型,表明他是需要被Mount到Pod上的。
#kubectl describe serviceaccounts
#####包含一个 mountable secrets 的资源
kubectl describe secrets default-token-xxx
#####包括三个数据项:token、ca.crt、namespace

1、联想到“Mountable secrets”的标记,以及之前看到的Pod中的三个文件的文件名。
2、每个namespace下有一个名为 default 的默认的ServiceAccount对象。
3、这个ServiceAccount里有一个名为Tokens的可以作为Volume一样被Mount到Pod里的Secret。
4、当Pod启动时这个Secret会被自动Mount到Pod的指定目录下,用来协助完成Pod中的进程访问API Server时的身份鉴权过程。

1.3、Pod创建时指定使用的ServiceAccount

如果一个Pod在定义时没有指定 spec.service.AccountName 属性,则系统会自动为其赋值为“Default”,即用同一 namespace 下默认的ServiceAccount,如果某个Pod需要使用非default的ServiceAccount,需要在定义时指定。
apiVersion:v1
kind:Pod
metadata:
  name:mypod
spec:
  containers:
  - name:mycontainer
   image:
  serviceAccountName:myserviceaccount

一个ServiceAccoun资源中可以包括多个Secrets对象:
1、名为Tokens的Secret用于访问API Server的Secret,也被称为ServiceAccountSecret。
2、名为Image Pull secrets的Secret用于下载容器镜像时的认证过程,通常镜像库运行在Insecure模式下,所以这个Secret为空。
3、用户自定义的其他Secret,用于用户的进程。

1.4、两套独立的账号系统

Kubernetes之所以要创建两套独立的账号系统,原因如下:
1、User账号是给人用的,ServiceAccount是给Pod里的进程使用的,面向对象不同;
2、User账号是全局性的,ServiceAccount则属于某个具体的Namespace;
3、通常来说,User账号是与后端的用户数据库同步的,创建一个新用户通常要走一套复杂的业务流程才能实现,ServiceAccount的创建则需要极轻量级实现方式,集群管理员可以很容易为某些特定任务组创建一个ServiceAccount。
4、对于这两种不同的账户,其审计要求通常不同;
5、对于一个复杂的系统来说,多个组件通常拥有各种账号的配置信息,ServiceAccount是Namespace隔离的,可以针对组件进行一对一的定义,同时具备很好的“便携性”。

1.5、Service Account与Secret相关的一些运行机制

Controller manager创建了ServiceAccountController与Token Controllerl两个安全相关的控制器。

1、其中ServiceAccountController一直监听Service Account和Namespace的事件。
如果一个Namespace中没有default Service Account,那么Service Account Controller就会为该Namespace创建一个默认的(default)的Service Account。
这就是我们之前看到的每个namespace下都有一个名为default的ServiceAccount的原因。

2、如果Controller manager进程在启动时指定了API Server私钥(service-account-private-key-file)参数,那么Controller manager会创建Token Controller。
Token Controller也监听Service Account的事件。
如果发现新建的Service Account里没有对应的Service Account Secret,则会用API Server私钥创建一个Token(JWT Token)。
并用该Token、CA证书Namespace名称等三个信息产生一个新的Secret对象,然后放入刚才的Service Account中。
如果监听到的事件是删除Service Account事件,则自动删除与该Service Account相关的所有Secret。
此外,Token Controller对象同时监听Secret的创建、修改和删除事件,并根据事件的不同做不同的处理。

1.6、Service Account准入控制器工作规则

当我们在API Server的鉴权过程中启用了Service Account类型的准入控制器,即在kube-apiserver的启动参数中包括下面的内容时:
--admission_control=ServiceAccount
则针对Pod新增或修改的请求,Service Account准入控制器会验证Pod里Service Account是否合法。
1、如果spec.serviceAccount域没有被设置,则Kubernetes默认为其制定名字为default的Serviceaccount;
2、如果Pod的spec.serviceAccount域指定了default以外的ServiceAccount,而该ServiceAccount没有事先被创建,则该Pod操作失败;
3、如果在Pod中没有指定“ImagePullSecrets”,那么该spec.serviceAccount域指定的ServiceAccount的“ImagePullSecrets”会被加入该Pod;
4、给Pod添加一个新的Volume,在该Volume中包含ServiceAccountSecret中的Token,并将Volume挂载到Pod中所有容器的指定目录下(/var/run/secrets/kubernetes.io/serviceaccount);
综上所述,ServiceAccount正常运行需要以下几个控制器:

  1. Admission Controller
  2. Service Account Controller
  3. Token Controller

二、Secret私密凭据

Secret主要作用是保管私密数据,比如密码、OAuth Tokens、SSH Keys等信息。将这些私密信息放在Secret对象中比直接放在Pod或Docker Image中要更安全,也便于使用和分发。

2.1、创建一个Secret

secret.yaml
apiVersion:v1
kind:Secret
metadata:
  name:mysecret
type: Opaque
data:
  password:dmfsdWUtMg0k
  username:dmfsdWUtMg0k
kubectl create -f secret.yaml

在上面的data域中的各子域的值必须为BASE64编码值,其中password域和username域BASE64编码前的值分别为value-1和value-2。
一旦Secret被创建,可以通过以下三个方式使用它:
1、在创建Pod时,通过为Pod指定ServiceAccount来自动使用该Seret;
2、通过挂载该Secret到Pod来使用它;
3、Docker镜像下载时使用,通过指定Pod的spec.ImagePullSecrets来引用它;

2.2、Pod 上挂载Secret资源

第一种方式主要用在API Server鉴权方面;
下面的例子展示了第二种使用方式:将一个Secret通过挂载的方式添加到Pod的Volume中。
apiVersion:v1
kind:Pod
metadata:
  name:mypod
  namespace:myns
spec:
  containers:
  - name:mycontainer
   image:redis
   volumeMounts:
   - name:foo
    mountPath:“/etc/foo”
    readOnly:true
volumes:
- name:foo
 secret:
   secretName:mysecret

2.3、镜像下载密码

1、执行login命令,登录私有Registry
#docker login localhost:5000(输入账户及密码,如果是第1次登录则会创建新用户,并把相关信息写入~/.dockercfg文件中)
2、用BASE64编码dockercfg的内容
#cat ~/.dockercfg|base64
3、将上一步命令的输出结果作为Secret的“data.dockercfg”域的内容,由此来创建一个Secret
image-pull-secret.yaml:
apiVersion:v1
kind:Secret
metadata:
  name:myregistrykey
data:
  .dockercfg:oiu09joiu09ujlih8hkjh98...
type:kubernetes.io/dockercfg
# kubectl create -f image-pull-secret.yaml

4、在创建Pod的时候引用该Secret
pods.yaml
apiVersion:v1
kind:Pod
metadata:
  name:mypod2
spec:
  containers:
    - name:foo
    image:janedoe/awexomeapp:v1
  imagePullSecrets:
    - name:myregistrykey
# kubectl create -f pods.yaml

每个单独的Secret大小不能超过1M,Kubernetes不鼓励创建大尺寸的Secret,因为如果使用大尺寸的Secret,则将大量占用API Server和kubelet的内存。
当然创建许多小的Secret也能耗尽API Server和kubelet的内存。

在使用Mount方式挂载Secret时,Container中Secret的“data”域的各个域的key值作为目录中的文件,Value值被BASE64编码后存储在相应的文件中。
前面的例子中创建的Secret,被挂载到一个叫做mycontainer的container中。
在该container中可以通过命令查看所生产的文件和文件中的内容:
# ls /etc/foo
username
password
# cat /etc/foo/username
value-1
# cat /etc/foo/password
value-2

2.4、关于Secret的一些知识点

我们可以通过Secret保管其他系统的敏感信息(比如数据库用户名和密码),并以Mount的方式将Secret挂载到Container中,然后通过访问目录中的文件的方式获取该敏感信息。
当Pod被API Server创建时,API Server不会校验该Pod引用的Secret是否存在。
一旦这个Pod被调度,则Kubelet将试着获取Secret的值。如果Secret不存在或暂时无法连接到API Server,则kubelet将按一定的时间间隔定期重试获取该Secret,并发送一个Event来解释Pod没有启动的原因。一旦Secret被Pod获取,则Kubelet将创建并Mount包含Secret的Volume。
只有所有的Volume被Mount后,Pod中的Container才会被启动。
在kubelet启动Pod中container后,Container中和Secret相关的Volume将不会被改变,即使Secret本身被修改了。
为了使用更新后的Secret,必须删除旧的Pod,并重新创建一个新的Pod,因此更新Secret的流程和部署一个新的Image是一样的。