Kubernetes-深入分析集群安全机制

Kubernetes过一系列机制来实现集群的安全机制,包括API Server的认证授权、准入控制机制及保护敏感信息的Secret机制等。集群的安全性必须考虑以下的几个目标:

  1. 保证容器与其所在宿主机的隔离;
  2. 限制容器给基础设施及其他容器带来消极影响的能力;
  3. 最小权限原则,合理限制所有组件权限,确保组件只执行它被授权的行为,通过限制单个组件的能力来限制他所能达到的权限范围;
  4. 明确组件间边界的划分;
  5. 划分普通用户和管理员角色;
  6. 在必要的时候允许将管理员权限赋给普通用户;
  7. 允许拥有Secret数据(Keys、Certs、Passwords)的应用在集群中运行;

下面分别从Authentication、Authorization、Admission Control、Secret和Service Account等方面来说明集群的安全机制。

API Server认证

Kubernetes集群中所有资源的访问和变更都是通过Kubernetes API Server的REST API来实现的,所以集群安全的关键点在于识别认证客户端身份(Authentication)以及访问权限的授权(Authorization)。

Kubernetes提供管理三种级别的客户端身份认证方式:

  1. 最严格的HTTPS证书认证:基于CA根证书签名的双向数字证书认证方式;
  2. HTTP Token认证:通过一个Token来识别合法用户;
  3. HTTP Base认证:通过用户名+密码的方式认证;

SSL双向认证步骤:

  1. HTTPS通信双方的务器端向CA机构申请证书,CA机构是可信的第三方机构,它可以是一个公认的权威的企业,也可以是企业自身。企业内部系统一般都使用企业自身的认证系统。CA机构下发根证书、服务端证书及私钥给申请者;
  2. HTTPS通信双方的客户端向CA机构申请证书,CA机构下发根证书、客户端证书及私钥个申请者;
  3. 客户端向服务器端发起请求,服务端下发服务端证书给客户端。客户端接收到证书后,通过私钥解密证书,并利用服务器端证书中的公钥认证证书信息比较证书里的消息,例如域名和公钥与服务器刚刚发送的相关消息是否一致,如果一致,则客户端认为这个服务器的合法身份;
  4. 客户端发送客户端证书给服务器端,服务端接收到证书后,通过私钥解密证书,获得客户端的证书公钥,并用该公钥认证证书信息,确认客户端是否合法;
  5. 客户端通过随机秘钥加密信息,并发送加密后的信息给服务端。服务器端和客户端协商好加密方案后,客户端会产生一个随机的秘钥,客户端通过协商好的加密方案,加密该随机秘钥,并发送该随机秘钥到服务器端。服务器端接收这个秘钥后,双方通信的所有内容都都通过该随机秘钥加密;

CA认证流程图:

上述是双向SSL协议的具体通信过程,这种情况要求服务器和用户双方都有证书。单向认证SSL协议不需要客户拥有CA证书,对应上面的步骤,只需将服务器端验证客户端证书的过程去掉,以及在协商对称密码方案和对称通话秘钥时,服务器端发送给客户端的是没有加过密的(这并不影响SSL过程的安全性)密码方案。

HTTP Token原理:HTTP Token的认证是用一个很长的特殊编码方式的并且难以被模仿的字符串——Token来表明客户身份的一种方式。在通常情况下,Token是一个复杂的字符串,比如我们用私钥签名一个字符串的数据就可以作为一个Token,此外每个Token对应一个用户名,存储在API Server能访问的一个文件中。当客户端发起API调用请求时,需要在HTTP Header里放入Token,这样一来API Server就能够识别合法用户和非法用户了。

HTTP Base:常见的客户端账号登录程序,这种认证方式是把“用户名+冒号+密码”用BASE64算法进行编码后的字符串放在HTTP REQUEST中的Header Authorization域里发送给服务端,服务端收到后进行解码,获取用户名及密码,然后进行用户身份的鉴权过程。

API Server授权

对合法用户进行授权(Authorization)并且随后在用户访问时进行鉴权,是权限与安全系统的重要一环。授权就是授予不同用户不同访问权限,API Server目前支持一下集中授权策略:

  • AlwaysDeny:拒绝所有请求,该配置一般用于测试;
  • AlwaysAllow:接收所有请求,如果集群不需要授权流程,可以采用该策略,此为Kubernetes默认的策略;
  • ABAC:(Attribute-Base Access Control)为基于属性的访问控制,表示使用用户配置的授权规则去匹配用户的请求;

为了简化授权的复杂度,对于ABAC模式的授权策略,Kubernetes仅有下面四个基本属性:

  1. 用户名(代表一个已经被认证的用户的字符型用户名)
  2. 是否是只读请求(REST的GET操作是只读的)
  3. 被访问的是哪一类资源,例如Pod资源/api/v1/namespaces/default/pods
  4. 被访问对象所属的Namespace

当API Server启用ABAC模式时,需要指定授权文件的路径和名字(--authorization_policy_file=SOME_FILENAME),授权策略文件里的每一行都是一个Map类型的JOSN对象,被称为访问策略对象,我们可以通过设置“访问策略对象”中的如下属性来确定具体的授权行为:

  • user:字符串类型,来源于Token文件或基本认证文件中的用户名字段的值;
  • readonly:true时表示该策略允许GET请求通过;
  • resource:来自于URL的资源,例如“Pod”;
  • namespace:表明该策略允许访问某个namespace的资源;

eg:

  • {"user":"alice"}
  • {"user":"kubelet","resource":"Pods","readonly":true}
  • {"user":"kubelet","resource":"events"}
  • {"user":"bob","resource":"Pods","readonly":true,"ns":"myNamespace"}

Admission Control准入控制

通过认证和鉴权之后,客户端并不能得到API Server的真正响应,这个请求还需通过Admission Control所控制的一个“准入控制链”的层层考验,Admission Control配备有一个“准入控制器”的列表,发送给API Server的任何请求都需要通过列表中每个准入控制器的检查,检查不通过API Server拒绝此调用请求。此外,准入控制器还能够修改请求参数以完成一些自动化的任务。比如Service Account这个控制器,当前可配置的准入控制如下:

  • AlwaysAdmit:允许所有请求;
  • AlwaysPullmages:在启动容器之前总去下载镜像,相当于在每个容器的配置项imagePullPolicy=Always
  • AlwaysDeny:禁止所有请求,一般用于测试;
  • DenyExecOnPrivileged:它会拦截所有想在Privileged Container上执行命令的请求,如果你的集群支持Privileged Container,你又希望限制用户在这些Privileged Container上执行命令,强烈推荐你使用它;
  • Service Account:这个plug-in将ServiceAccount实现了自动化,默认启用,如果你想使用ServiceAccount对象,那么强烈你推荐使用它;
  • SecurityContextDeny:这个插件将使用SecurityContext的Pod中的定义全部失效。SecurityContext在Container中定义了操作系统级别的安全设定(uid,gid,capabilityes,SELinux等)
  • ResourceQuota:用于配额管理目的,作用于namespace上,它会观察所有请求,确保在namespace上的配额不会超标。推荐在Admission Control参数列表中这个插件排最后一个;
  • LimitRanger:用于配额管理,作用于Pod与Container,确保Pod与Container上的配额不会超标;
  • NamespaceExists(已过时):对所有请求校验namespace是否已存在,如果不存在则拒绝请求,已合并至NamespaceLifecycle。
  •  NamespaceAutoProvision(已过时):对所有请求校验namespace,如果不存在则自动创建该namespace,推荐使用NamespaceLifecycle。
  • NamespaceLifecycle:如果尝试在一个不存在的namespace中创建资源对象,则该创建请求将被拒绝。当删除一个namespace时,系统将会删除该namespace中所有对象,保存Pod,Service等。

在API Server上设置--admission-control参数,即可定制我们需要的准入控制链,如果启用多种准入控制选项,则建议的设置如下:

  • --admission-control=NamespaceLifecycle,LimitRanger,SecurityContextDeny,ServiceAccount,ResourceQuota

下面着重介绍三个准入控制器:

SecurityContextDeny

Security Context时运用于容器的操作系统安全设置(uid、gid、capabilities、SELinux role等),Admission Control的SecurityContextDeny插件的作用是,禁止创建设置了Security Context的Pod,例如包含以下配置项的Pod:

  • spec.containers.securityContext.seLinuxOptions
  • spec.containers.securityContext.runAsUser

ResourceQuota

ResourceQuota不仅能够限制某个Namespace中创建资源的数量,而且能够限制某个namespace中被Pod所请求的资源总量。该准入控制器和资源对象ResourceQuota一起实现了资源的配额管理;

LimitRanger

准入控制器LimitRanger的作用类似于上面的ResourceQuota控制器,这对Namespace资源的每个个体的资源配额。该插件和资源对象LimitRange一起实现资源限制管理。

Service Account

Servuce Account是一种账号,但他并不是给Kubernetes的集群的用户(系统管理员、运维人员、租户用户等),而是给运行在Pod里的进程用的,它为Pod里的进程提供必要的身份证明。

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

通过查看源码获知这是在用一种类似HTTP Token的新的认证方式--ServiceAccount Auht,Pod中的客户端调用Kubernetes API的时候,在HTTP Header中传递了一个Token字符串,这类似于之前提到的HTTP Token认证方式,存在以下几个不同点:

  • 此处的Token的内容来源于Pod里指定路径下的一个文件(/run/secrets/kubernetes.io/serviceaccount/token),这种token是动态生成的,确切的说,是由KubernetesController进程用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从属于ServiceAccount资源对象,属于Service Account的一部分,一个ServiceAccount对象里面可以包括多个不同的Secret对象,分别用于不同目的的认证活动。

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

查看系统中ServiceAccount对象,可以看到一个名为default的Service Account对象,包含一个名为default-token-xxx的Secret,这个Secret同时是“Mountable secrets”,表明他是需要被Mount到Pod上的。

  • kubectl describe serviceaccounts
  • kubectl describe secrets default-token-xxx

default-token-xxx包括三个数据项:

  • token
  • ca.crt
  • namespace

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

一个ServiceAccount可以包括多个Secrets对象:

  • 名为Tokens的Secret用于访问API Server的Secret,也被称为ServiceAccountSecret;
  • 名为Image Pull secrets的Secret用于下载容器镜像时的认证过程,通常镜像库运行在Insecure模式下,所以这个Secret为空;
  • 用户自定义的其他Secret,用于用户的进程;

如果一个Pod在定义时没有指定spec.service.AccountName属性,则系统会自动为其赋值为“Default”,即使用同一namespace下默认的ServiceAccount,如果某个Pod需要使用非default的ServiceAccount,需要在定义时指定:

apiVersion:v1

kind:Pod

metadata:

    name:mypod

spec:

    containers:

    - name:mycontainer

      image:

    serviceAccountName:myserviceaccount

Kubernetes之所以要创建两套独一的账号系统,原因如下:

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

下面分析Service Account与Secret相关的一些运行机制:

 Controller manager创建了ServiceAccountController与Token Controllerl两个安全相关的控制器。其中ServiceAccountController一直监听Service Account和Namespace的事件,如果一个Namespace中没有default Service Account,那么Service Account Controller就会为该Namespace创建一个默认的(default)的Service Account,这就是我们之前看到的每个namespace下都有一个名为default的ServiceAccount的原因。

如果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的创建、修改和删除事件,并根据事件的不同做不同的处理。

当我们在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”,那么该sec.serviceAccount域指定的ServiceAccount的“ImagePullSecrets”会被加入该Pod;
  4. 给Pod添加一个新的Volume,在该Volume中包含ServiceAccountSecret中的Token,并将Volume挂载到Pod中所有容器的指定目录下(/var/run/secrets/kubernetes.io/serviceaccount);

综上所述,ServiceAccount正常运行需要以下几个控制器:

  • Admission Controller
  • Token Controller
  • Service Account Controller

Secret私密凭据

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

  • 创建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被创建,可以通过以下三个方式使用它:

  • 在创建Pod时,通过为Pod指定ServiceAccount来自动使用该Seret;
  • 通过挂载该Secret到Pod来使用它;
  • Docker镜像下载时使用,通过指定Pod的spec.ImagePullSecrets来引用它;
  • 第一种方式主要用在API Server鉴权方面;
  • 第二种方式如下:

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

  • 第三种方式如下:
    • 执行login登录私有registry
    • docker login localhost:5000(输入账户及密码,则会创建新用户,并把相关信息写入~/。dockercfg文件中)
    • 用BASE64编码dockercfg的内容;
    • cat ~/.dockercfg|grep base64
    • 将上一步命令的输出结果作为secret的“data.dockercfg”域的内容,由此来创建一个Secret:

 image-pull-secret.yaml:

apiVersion:v1

kind:Secret

metadata:

    name:myregistrykey

data:

    .dockercfg:xxx

type:kubernetes.io/dockercfg

  • kubectl create -f image-pull-secret.yaml
  • 在创建Pod的时候引用该Secret:

pods.yaml

apiVersion:v1

kind:Pod

metadata:

    name:mypod2

spec:

    containers:

    - name:foo

      image:xxxxxx: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

我们可以通过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是一样的。

原文地址:https://www.cnblogs.com/cf532088799/p/7977083.html