Kubernetes 作为一个分布式集群管理的工具,保证集群的安全性是其一个重要的任务。API Server 是集群内部各个组件通信的中介,也是外部控制的入口,所以 K8s 的安全机制就是围绕保护 API Server 来设计的。K8s 使用了认证(Authentication)、鉴权(Authorization)、准入控制(Admission Control)三步来保证 API Service 的安全。
认证和授权的过程中最重要的一个概念是用户。所以在正式讲解之前,我们需要了解清楚 k8s 里面的用户的怎样的。
kubernetes 有两种用户:一是 集群内部的 Service Account,二是 外部的用户账户。
为什么 kubernetes 中会有 service account 和用户账户这两种形式呢?回答这个问题之前我们要先搞清楚一个问题:谁发起的请求? 回答是:与 kubernetes 交互的实际上有两类东西,一类是真实的人类,另一类就是程序。真实的人类使用的就是用户账户,而某一个程序访问 kubernetes 时就需要使用 service acocunt 了。
用户账户
就是个人用户,比如某个运维人员或外部应用的账户。但是 kubernetes 没有相应的资源对象或者 api 来支持常规的个人账户,拥有 kubernetes 集群的 CA 证书签名的有效证书,个人用户就可以访问 kubernetes 集群了,在这种情况下,证书中的 Subject(主题)会被 api-server 解析成为一个用户。 举个例子,X509 证书中 Subject 的内容如下:O = system:masters, CN = kubernetes:admin
,其中,kubernetes:admin 会被解析为 User,system:masters 会被解析为 User 所在的 Group。
Service Account
一个 Pod 必须要以某一个 Service Account 的身份去运行。一个 Service Account 对应着一个 Secret,一个 Secret 保存着一个 Token 和公钥文件,而这些信息会被挂载到 Pod 中。
不管是通过外部还是集群内部访问 kubernetes 集群,api-server 都是一个入口,所以,k8s 的认证和授权机制几乎都是 api-server 来实现的。
通过上述对用户的阐述,我们总结如下:任一 kubernetes api 的访问都是以下三种方式之一:
认证方面,kubernetes 提供了如下的认证方式:
HTTPS 证书认证
kubernetes 需要 PKI(public key infrastructure,公钥基础设施)证书来基于 TLS 的安全的认证。 如果你使用 kubeadm 来初始化的集群,则 kubeadm 会帮助你自动生成集群所需要的各类证书。 kubeadm 会将证书放置在 /etc/kubernetes/pki 目录下,而管理员(用户账户)的证书会放置到 /etc/kubernetes 目录下。
k8s 使用 x509 证书中 CN(Common Name) 以及 O(Organization) 字段对应 k8s 中的 user 和 group,将 Authentication 和 RBAC Authorization 结合到了一起,巧妙地将 Control Plane 中的各个核心 User 和 Group、与操作权限(ClusterRole)进行了绑定(ClusterRoleBinding)。
其实每一个 kubectl 命令背后都有一个 kubeconf 文件在支持:
kubectl get pods --kubeconfig=file1
kubectl get pods --kubeconfig=file2
复制代码
kubectl get nodes \--server https://localhost:6443 \--user docker-for-desktop \--client-certificate my.cert \--client-key my.key \--insecure-skip-tls-verify
复制代码
当用户通过认证(Authentication)后,下一步就是授权(Authorization)了。api-server 目前支持以下授权策略:
通过 APIServer 的启动参数--authorization-mode
可配置多种授权策略,用逗号分隔即可。在通常情况下,我们会设置授权策略为 Node,RBAC,APIServer 在收到请求后,会读取该请求中的数据,生成一个访问策略对象,APIServer 会将这个访问策略对象和配置的授权模式逐条进行匹配,第一个被满足或拒绝的授权策略决定了该请求的授权结果,如果匹配的结果是禁止访问,则 APIServer 会终止 API 调用流程,并返回客户端的错误调用码。
Node 授权策略用于对 kubelet 发出的请求进行访问控制,与用户的应用授权无关,属于 Kubernetes 自身安全的增强功能。简单来说,就是限制每个 Node 只访问它自身运行的 Pod 及相关的 Service、Endpoints 等信息;也只能受限于修改自身 Node 的一些信息,比如 Label;也不能操作其他 Node 上的资源。而之前用 RBAC 这种通用权限模型其实并不能满足 Node 这种特殊的安全要求,所以将其剥离出来定义为新的 Node 授权策略。
因为 AlwaysDeny、AlwaysAllow 这两个基本不会出现在生产环境,而 ABAC 已经被 RBAC 所替代,所以,我们直接从 RBAC 开始。
RBAC 在 k8s 1.8 版本时升级为 GA 稳定版本,并作为 kubeadm 安装方式下的默认选项。
RBAC具有如下优势:
在 RBAC 管理体系中,Kubernetes 引入了4个资源对象:Role、ClusterRole、RoleBinding 和ClusterRoleBinding。同其他 API 资源对象一样,用户可以使用 kubectl 或者 API 调用等方式操作这些资源对象。
角色(Role)和集群角色(ClusterRole)
一个角色就是一组权限的集合,在 Role 中设置的权限都是许可(Permissive)形式的,不可以设置拒绝(Deny)形式的规则。Role 设置的权限将会局限于命名空间(namespace)范围内,如果需要在集群级别设置权限,就需要使用 ClusterRole 了。
Role示例
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:name: pod-readernamespace: defaultlabels:role: pod-readerannotation:
rules:
- apiGroups: [""]resources: ["pods"]verbs: ["get","list","watch"]
复制代码
上述定义的 Role 的 name 是 pod-reader,这个 Role 的权限是可以对所有 apigruop 下面的资源名为“pod” 的资源进行 get、list 以及 watch 的操作。但是需要注意的是限定在 default 的 namespace 范围内。
Role 资源对象主要通过 rules 字段来描述它的功能,rules 字段是一个 rule 的 list,每一个 rule 包含如下几个关键字段:
GROUP_NAME/VERSION
,所以,这里的 api 组就是 apps。ClusterRole示例
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:# ClusterRole不受命名空间限制,所以不必绑定namespacesname: secret-reader
rules:
- apiGroups: [""]resources: ["secrets"]verbs: ["get","watch","list"]
复制代码
从命名上来看,ClusterRole 的处理范围要比 Role 大,因为 Role 的范围是 namespace,而 ClusterRole的范围是 Cluster。
ClusterRole 主要适用以下场景:
上述 ClusterRole 定义的是一个 name 为 secret-reader,拥有对所有 apiGroup 下的资源类型为 secrets 的资源进行 get、watch 和 list 的操作。并且他没有限定 namespace。
角色绑定(RoleBinding)和集群角色绑定(ClusterRoleBinding)
RoleBinding示例
RoleBinding 可以与属于相同命名空间的 Role 或者某个集群级别的 ClusterRole 绑定,完成对某个主体的授权。
这里的主体包括用户(User)、组(Group)以及 Service Account
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:name: read-podsnamespace: defaultlabels:roleBinding: read-podsannotation:
subjects:
- kind: Username: JackeyapiGroup: rbac.authorization.k8s.io
roleRef:kind: Rolename: pod-readerapiGroup: rbac.authorization.k8s.io
复制代码
RoleBinding 有两个比较重要的根节点,一个是 subjects,描述了需要绑定的主体,有 user、group 和 service account;另一个是 roleRef,描述了要绑定的 Role。
授权目标主体(Subject)的命名规范
在 RBAC 系统中,通过角色绑定(RoleBinding或ClusterRoleBinding)的定义,将在角色(Role或ClusterRole)中设置的授权规则与某个目标主体(Subject)绑定。授权的目标主体可以是用户(User)、用户组(Group)和Service Account三者之一。
- 用户名由字符串进行标识,例如人名(alice)、Email地址(bob@example.com)、用户ID(1001)等,通常应该在客户端CA证书中进行设置。
- 需要注意的是,Kubernetes内置了一组系统级别的用户/用户组,以“system:”开头,用户自定义的名称不应该使用这个前缀。
- 用户组与用户名类似,由字符串进行标识,通常也应该在客户端CA证书中进行设置,并且要求不以“system:”为前缀。
- Service Account在Kubernetes系统中的用户名会被设置成以“system:serviceaccount:”为前缀的名称,其所属的组名会被设置成以“system:serviceaccounts:”为前缀的名称。
RoleBinding 本身会被 namespace 所影响,用于某个 namespace 内的授权,如果它适合 Role 进行绑定,就需要保持一致的 namespace;而 RoleBinding 除了能够和 Role 绑定,也能和 ClusterRole 绑定,这个操作的含义是:对目标主体在其所在的命名空间授予在 ClusterRole 中定义的权限。
如下这个例子展示了这个情况:
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:name: rolebinding-with-clusterolenamespace: development
subjects:
- kind: Username: JackeyapiGroup: rbac.authorization.k8s.io
roleRef:kind: ClusterRolename: secret-readerapiGroup: rbac.authorization.k8s.io
复制代码
secret-reader 这个 ClusterRole 的定义在前面已给出。虽然 secret-reader 是一个集群角色,但因为 RoleBinding 的作用范围为命名空间 development,所以用户 Jackey 只能读取命名空间 development 中的 secret 资源对象,而不能读取其他命名空间中的 secret 资源对象。
ClusterRoleBinding绑定示例
ClusterRoleBinding 用于进行集群级别或者对所有命名空间都生效的授权。下面的例子允许 manager 组的用户读取任意命名空间中的 secret 资源对象:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:name: read-secret-global
subjects:
- kind: Groupname: managerapiGroup: rbac.authorization.k8s.io
roleRef:kind: ClusterRolename: secret-readerapiGroup: rbac.authorization.k8s.io
复制代码
在集群角色绑定(ClusterRoleBinding)中引用的角色只能是集群级别的角色(ClusterRole),而不能是命名空间级别的Role。
一旦通过创建 RoleBinding 或 ClusterRoleBinding 与某个 Role 或 ClusterRole 完成了绑定,用户就无法修改与之绑定的 Role 或 ClusterRole 了。只有删除了 RoleBinding 或 ClusterRoleBinding,才能修改 Role 或 ClusterRole。Kubernetes 限制 roleRef 字段中的内容不可更改,主要有以下两个原因:
在 RBAC 中引用资源的方式就是资源对象的名称,如 pods、services、deloys 等等。但是有一些资源是具有字资源的,例如 pod 的日志。对于这种资源,RBAC 的引用方式是“资源/子资源”。例如 pods/log。
此外,默认情况下 RBAC 引用的资源能够包含所有该资源的实例,如果只是想针对某个资源的某个特定的实例,可以通过 resourcesName 来指定:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:# ClusterRole不受命名空间限制,所以不必绑定namespacesname: secret-reader
rules:
- apiGroups: [""]resources: ["secrets"]resourceName: "my-secrets"verbs: ["update","get"]
复制代码
resourceName 有一个限制,它对 list、watch、create 或者 deletecollection 这些操作是无效的。这是因为必须要通过 URL 进行鉴权,而资源名称在 list、watch、create 或 deletecollection 请求中只是请求 Body 数据的一部分。
ClusterRole 的聚合
某些情况下需要多个 ClusterRole 合并适用,这种情况下适用聚合 ClusterRole 能够有效的减轻管理员的操作。
这个功能是通过 aggregationRule 这个字段来完成的。aggregationRole 使用 LABEL SElECTOR 选择多个 ClusterRole,由相关的控制器(Controller)来保证能够将多个适合的 ClusterRole 进行整合。
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:name: clusterrole-with-aggregation
aggregationRule:clusterRoleSelectors:- matchLabels:rbac.example.com/aggregate-to-monitoring: "true"
rules: [] #系统自动填充合并的结果
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:name: clusterole-demolabels: rbac.example.com/aggregate-to-monitoring: "true"
rules:
- apiGroups: [""]resources: ["services","endpoints","pods"]verbs: ["get","list","watch"]
复制代码
clusterrole-with-aggregation 会由系统进行控制,形成一个 ClusterRole 的聚合,我们可以通过 kubectl describe clusterrole/clusterrole-with-aggregation 来进行验证。
RBACAPI 防止用户通过编辑 Role 或者 RoleBinding 获得权限的提升。这一限制是在 API 级别生效的,因此即使没有启用 RBAC,也仍然有效。
(一) 创建或更新 Role 或 ClusterRole 的限制用户要对角色(Role 或 ClusterRole)进行创建或更新操作,需要满足下列至少一个条件:
例如,用户 user-1 没有列出集群中所有 Secret 资源的权限,就不能创建具有这一权限的集群角色。要让一个用户能够创建或更新角色,则需要:
- 隐式:为用户授予这些权限。用户如果尝试使用尚未被授予的权限来创建或修改 Role 或 ClusterRole,则该 API 请求将被禁止。
- 显式:为用户显式授予 rbac.authorization.k8s.ioAPIGroup 中的 Role 或 ClusterRole 的提权(Escalate)操作权限。
(二) 创建或更新 RoleBinding 或 ClusterRoleBinding 的限制仅当我们已经拥有被引用的角色( Role 或 ClusterRole)中包含的所有权限(与角色绑定的作用域相同)或已被授权对被引用的角色执行绑定(bind)操作时,才能创建或更新角色绑定(RoleBinding 或 ClusterRoleBinding)。
例如,如果用户 user-1 没有列出集群中所有 Secret 资源的权限,就无法为一个具有这样权限的角色创建 ClusterRoleBinding。要使用户能够创建或更新角色绑定,则需要进行以下操作。
- 隐式:授予其该角色中的所有权限。
- 显式:授予在特定角色或集群角色中执行绑定(bind)操作的权限。
例如,通过下面的 ClusterRole 和 RoleBinding 设置,将允许用户 user-1 为其他用户在 user-1-namespace 命名空间中授予 admin、edit 及 view 角色的权限:
apiVersion: rbac.authorization.k8s.io/v1
Kind: ClusterRole
metadata:name: role-grantor
rules:
- apiGroups: ["rbac.authorization.k8s.io"]resources: ["rolebindings"]verbs: ["create"]
- apiGroups: ["rbac.authorization.k8s.io"]resources: ["clusterroles"]verbs: ["bind"]resourceNames: ["edit","admin","view"]---apiVersion: rbac.authorization.k8s.iokind: RoleBindingmetadata:name: role-grantor-bindingnamespace: user-1-namespacesubjects:- kind: Username: user-1apiGroup: rbac.authorization.k8s.ioroleRef:kind: ClusterRolename: role-grantorapiGroup: rbac.authorization.k8s.io
复制代码
在系统初始化过程中启用第1个角色和角色绑定时,必须让初始用户具备其尚未被授予的权限。要进行初始的角色和角色绑定设置,有以下两种办法。
突破了之前所说的认证和鉴权两道关卡之后,客户端的调用请求就能够得到 APIServer 的真正响应了吗?答案是:不能!这个请求还要通过 AdmissionControl(准入控制)所控制的一个准入控制链的层层考验,才能获得成功的响应。
AdmissionControl 配备了一个准入控制器的插件列表,发送给 APIServer 的任何请求都需要通过列表中每个准入控制器的检查,检查不通过,APIServer 就会拒绝此调用请求。此外,准入控制器插件能够修改请求参数以完成一些自动化任务,比如 Service Account 这个控制器插件。
ServiceAccount 也是一种账号,但它并不是给 Kubernetes 集群的用户(系统管理员、运维人员、租户用户等)用的,而是给运行在 Pod 里的进程用的,它为 Pod 里的进程提供了必要的身份证明。
在正常情况下,为了确保 Kubernetes 集群的安全,api-server 都会对客户端进行身份认证,认证失败的客户端无法进行 API 调用。此外,在 Pod 中访问 Kubernetes api-server 服务时,是以 Service 方式访问名为 kubernetes 的这个服务的,而 kubernetes 服务又只在 HTTPS 安全端口 443 上提供。
认证原理是在用一种类似 HTTP Token 的新认证方式 --Service Account Auth
,Pod 中的客户端调用 Kubernetes API 时,在 HTTP Header 中传递了一个 Token 字符串,这类似于之前提到的 HTTP Token 认证方式,但有以下几个不同之处:
每个命名空间中都有一个名为 default 的默认 Service Account对象,在这个 Service Account 里面有一个名为 Tokens 的可以作为 Volume 被挂载到 Pod 里的 Secret,Pod 启动时,这个 Secret 会自动被挂载到 Pod 的指定目录下,用来协助完成 Pod 中的进程访问 api-server 时的身份鉴权。
一个 Service Account 可以包含多个 secret。其中,名为 Tokens 的 Secret 用于访问 APIServer 的 Secret,也被称为 ServiceAccountSecret;名为 imagePullSecrets 的 Secret 用于下载容器镜像时的认证,镜像库通常运行在 Insecure 模式下,所以这个 Secret 为空;用户自定义的其他 Secret 用于用户的进程中。
Service Account 的正常工作离不开以下控制器:Service Account Controller、Token Controller、Admission Controller。
Service Account Controller
Service Account Controller 的工作相对简单,它会监听 Service Account 和 Namespace 这两种资源对象的事件,如果在一个 Namespace 中没有默认的 Service Account,那么它会为该 Namespace 创建一个默认的 ServiceAccount 对象,这就是在每个 Namespace 下都有一个名为 default 的 Service Account 的原因。
Token Controller
Token Controller 也监听 Service Account 的事件,如果发现在新建的 Service Account 里没有对应的 Service Account Secret,则会用 APIServer 私钥(--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
接下来就是 Admission Controller 的重要作用了,当我们在 api-server 的准入控制链中启用了 Service Account 类型的准入控制器时(这也是默认的设置),则针对 Pod 新增或修改的请求,Admission Controller 会验证 Pod 里的 Service Account 是否合法,并做出如下控制操作: