- Kubernetes通过一系列机制来实现集群的安全控制,其中包括API Server的认证授权、准入控制机制及保护敏感信息的Secret机制等。
- 集群的安全性必须考虑如下几个目标。
- (1)保证容器与其所在宿主机的隔离。
- (2)限制容器给基础设施或其他容器带来的干扰。
- (3)最小权限原则——合理限制所有组件的权限,确保组件只执行它被授权的行为,通过限制单个组件的能力来限制它的权限范围。
- (4)明确组件间边界的划分。
- (5)划分普通用户和管理员的角色。
- (6)在必要时允许将管理员权限赋给普通用户。
- (7)允许拥有Secret数据(Keys、Certs、Passwords)的应用在集群中运行。
- 下面分别从Authentication、Authorization、Admission Control、Secret和Service Account等方面来说明集群的安全机制。
1、API Server认证管理
- Kubernetes集群中所有资源的访问和变更都是通过Kubernetes API Server的REST API来实现的,所以集群安全的关键点就在于如何识别并认证客户端身份(Authentication),以及随后访问权限的授权(Authorization)这两个关键问题。
- Kubernetes集群提供了3种级别的客户端身份认证方式:
- 最严格的HTTPS证书认证:基于CA根证书签名的双向数字证书认证方式。
- HTTP Token认证:通过一个Token来识别合法用户。
- HTTP Base认证:通过用户名+密码的方式认证。
1.1、HTTPS证书认证的原理
- HTTPS证书认证需要有一个CA证书,CA是PKI系统中通信双方都信任的实体,被称为可信第三方(Trusted Third Party,TTP)。CA作为可信第三方的重要条件之一就是CA的行为具有非否认性。作为第三方而不是简单的上级,就必须能让信任者有追究自己责任的能力。
- CA通过证书证实他人的公钥信息,证书上有CA的签名。用户如果因为信任证书而有了损失,则证书可以作为有效的证据用于追究CA的法律责任。正是因为CA承担责任的承诺,所以CA也被称为可信第三方。
- 在很多情况下,CA与用户是相互独立的实体,CA作为服务提供方,有可能因为服务质量问题(例如,发布的公钥数据有错误)而给用户带来损失。在证书中绑定了公钥数据和相应私钥拥有者的身份信息,并带有CA的数字签名;在证书中也包含了CA的名称,以便于依赖方找到CA的公钥,验证证书上的数字签名。
- CA认证涉及诸多概念,比如根证书、自签名证书、密钥、私钥、加密算法及HTTPS等,这里大致讲述SSL协议的流程,有助于理解CA认证和Kubernetes CA认证的配置过程。
- 如图6.1所示,CA认证大概包含下面几个步骤。
- (1)HTTPS通信双方的服务器端向CA机构申请证书,CA机构是可信的第三方机构,它可以是一个公认的权威企业,也可以是企业自身。企业内部系统一般都用企业自身的认证系统。CA机构下发根证书、服务端证书及私钥给申请者。
- (2)HTTPS通信双方的客户端向CA机构申请证书,CA机构下发根证书、客户端证书及私钥给申请者。
- (3)客户端向服务器端发起请求,服务端下发服务端证书给客户端。客户端接收到证书后,通过私钥解密证书,并利用服务器端证书中的公钥认证证书信息比较证书里的消息,例如,比较域名和公钥与服务器刚刚发送的相关消息是否一致,如果一致,则客户端认可这个服务器的合法身份。
- (4)客户端发送客户端证书给服务器端,服务端在接收到证书后,通过私钥解密证书,获得客户端证书公钥,并用该公钥认证证书信息,确认客户端是否合法。
- (5)客户端通过随机密钥加密信息,并发送加密后的信息给服务端。在服务器端和客户端协商好加密方案后,客户端会产生一个随机的密钥,客户端通过协商好的加密方案加密该随机密钥,并发送该随机密钥到服务器端。服务器端接收这个密钥后,双方通信的所有内容都通过该随机密钥加密。
- 上述是双向认证SSL协议的具体通信过程,这种情况要求服务器和用户双方都有证书。单向认证SSL协议则不需要客户端拥有CA证书,对于上面的步骤,只需将服务器端验证客户证书的过程去掉,之后协商对称密码方案和对称通话密钥时,服务器发送给客户的密码没被加密即可。
1.2、HTTP Token的认证原理
- HTTP Token的认证是用一个很长的特殊编码方式的并且难以被模仿的字符串—Token来表明客户身份的一种方式。在通常情况下,Token是一个很复杂的字符串,比如我们用私钥签名一个字符串后的数据就可以被当作一个Token。此外,每个Token对应一个用户名,存储在APIServer能访问的一个文件中。当客户端发起API调用请求时,需要在HTTP Header里放入Token,这样一来,API Server就能识别合法用户和非法用户了。
1.3、HTTP Base认证
- HTTP是无状态的,浏览器和Web服务器之间可以通过Cookie来进行身份识别。桌面应用程序(比如新浪桌面客户端、SkyDrive客户端、命令行程序)一般不会使用Cookie,那么它们与Web服务器之间是如何进行身份识别的呢?这就用到了HTTP Base认证,这种认证方式是把“用户名+冒号+密码”用BASE64算法进行编码后的字符串放在HTTP Request中的Header Authorization域里发送给服务端,服务端在收到后进行解码,获取用户名及密码,然后进行用户身份鉴权。
2、API Server授权管理
- 当客户端发起API Server调用时,API Server内部要先进行用户认证,然后执行用户授权流程,即通过授权策略来决定一个API调用是否合法。对合法用户进行授权并且随后在用户访问时进行鉴权,是权限与安全系统的重要一环。简单地说,授权就是授予不同的用户不同的访问权限。
- API Server目前支持以下几种授权策略(通过API Server的启动参数“--authorization-mode”设置)。
- AlwaysDeny:表示拒绝所有请求,一般用于测试。
- AlwaysAllow:允许接收所有请求,如果集群不需要授权流程,则可以采用该策略,这也是Kubernetes的默认配置。
- ABAC(Attribute-Based Access Control):基于属性的访问控制,表示使用用户配置的授权规则对用户请求进行匹配和控制。
- Webhook:通过调用外部REST服务对用户进行授权。
- RBAC:Role-Based Access Control,基于角色的访问控制。
- Node:是一种专用模式,用于对kubelet发出的请求进行访问控制。
- API Server在接收到请求后,会读取该请求中的数据,生成一个访问策略对象,如果在该请求中不带某些属性(如Namespace),则这些属性的值将根据属性类型的不同,设置不同的默认值(例如,为字符串类型的属性设置一个空字符串;为布尔类型的属性设置false;为数值类型的属性设置0)。然后将这个访问策略对象和授权策略文件中的所有访问策略对象逐条匹配,如果至少有一个策略对象被匹配,则该请求被鉴权通过,否则终止API调用流程,并返回客户端的错误调用码。
2.1、ABAC授权模式详解
- 注意:该ABAC授权功能在Kubernetes 1.6版本开始已被弃用!
- 在API Server启用ABAC模式时,需要指定授权策略文件的路径和名称(--authorization-policy-file=SOME_FILENAME),授权策略文件里的每一行都以一个Map类型的JSON对象进行设置,这被称为“访问策略对象”。
- 通过设置访问策略对象中的apiVersion、kind、spec属性来确定具体的授权策略:
- apiVersion当前版本为abac.authorization.kubernetes.io/v1beta1;
- kind被设置为Policy;
- spec指详细的策略设置,包括主题属性、资源属性、非资源属性这三个字段。
- (1)主体属性
- user(用户名):字符串类型,该字符串类型的用户名来源于Token文件(--token-auth-file参数设置的文件)或基本认证文件中用户名称段的值。
- group(用户组):在被设置为“system:authenticated”时表示匹配所有已认证的请求,在被设置为“system:unauthenticated”时表示匹配所有未认证的请求。
- (2)资源属性
- apiGroup(API组):字符串类型,表明匹配哪些API Group,例如extensions或*(表示匹配所有API Group)。
- namespace(命名空间):字符串类型,表明该策略允许访问某个Namespace的资源,例如kube-system或*(表示匹配所有Namespace)。
- resource(资源):字符串类型,API资源对象,例如pods或*(表示匹配所有资源对象)。
- (3)非资源属性
- nonResourcePath(非资源对象类路径):非资源对象类的URL路径,例如/version或/apis,*表示匹配所有非资源对象类的请求路径,也可以设置为子路径,/foo/*表示匹配所有/foo路径下的所有子路径。
- readonly(只读标识):布尔类型,当它的值为true时,表明仅允许GET请求通过。
- 下面对ABAC授权算法、使用kubectl时的授权机制、常见ABAC授权示例、以及如何对Service Account进行授权进行说明。
2.1.1、ABAC授权算法
- API Server进行ABAC授权的算法为:在API Server收到请求之后,首先识别出请求携带的策略对象的属性,然后根据在策略文件中定义的策略对这些属性进行逐条匹配,以判定是否允许授权。如果有至少一条匹配成功,那么这个请求就通过了授权(不过还是可能在后续其他授权校验中失败)。
- 常见的策略配置如下:
- 要允许所有认证用户做某件事,可以写一个策略,将group属性设置为system:authenticated。
- 要允许所有未认证用户做某件事,可以把策略的group属性设置为system:unauthenticated。
- 要允许一个用户做任何事,将策略的apiGroup、namespace、resource和nonResourcePath属性设置为“*”即可。
2.1.2、使用kubectl时的授权机制
- kubectl使用API Server的/api和/apis端点来获取版本信息。要验证kubectl create/update命令发送给服务器的对象,kubectl需要向OpenAPI进行查询,对应的URL路径为/openapi/v2。
- 当使用ABAC授权模式时,下列特殊资源必须显式地通过nonResourcePath属性进行设置。
- API版本协商过程中的/api、/api/*、/apis、和/apis/*。
- 使用kubectl version命令从服务器获取版本时的/version。
- create/update操作过程中的/swaggerapi/*。
- 在使用kubectl操作时,如果需要查看发送到API Server的HTTP请求,则可以将日志级别设置为8,例如:
]# kubectl --v=8 version
2.1.3、常见的ABAC授权示例
- 下面通过几个授权策略文件(JSON格式)示例说明ABAC的访问控制用法。
{"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "spec": {"user": "alice", "namespace": "*", "resource": "*", "apiGroup": "*"}}
{"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "spec": {"user": "kubelet", "namespace": "*", "resource": "pods", "readonly": true}}
{"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "spec": {"user": "kubelet", "namespace": "*", "resource": "events"}}
- (4)用户bob只能读取projectCaribou中的Pod:
{"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "spec": {"user": "bob", "namespace": "projectCaribou", "resource": "pods", "readonly": true}}
{"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "spec": {"group": "system:authenticated", "readonly": true, "nonResourcePath": "*"}}
{"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "spec": {"group": "system:unauthenticated", "readonly": true, "nonResourcePath": "*"}}
- 如果添加了新的ABAC策略,则需要重启API Server以使其生效。
2.1.4、对Service Account进行授权
- Service Account会自动生成一个ABAC用户名(username),用户名按照以下命名规则生成:
system:serviceaccount::
- 创建新的命名空间时,会产生一个如下名称的Service Account:
system:serviceaccount::default
- 如果希望kube-system命名空间中的Service Account“default”具有全部权限,就要在策略文件中加入如下内容:
{"apiVersion":"abac.authorization.kubernetes.io/v1beta1","kind":"Policy","spec":{"user":"system:serviceaccount:kube-system:default","namespace":"*","resource":"*","apiGroup":"*"}}
2.2、Webhook授权模式详解
- Webhook定义了一个HTTP回调接口,实现Webhook的应用会在指定事件发生时,向一个URL地址发送(POST)通知信息。启用Webhook授权模式后,Kubernetes会调用外部REST服务对用户进行授权。
- Webhook模式用参数--authorization-webhook-configfile=SOME_FILENAME来设置远端授权服务的信息。
- 配置文件使用的是kubeconfig文件的格式。"users"代表着API Server的webhook,而"cluster"代表着远程服务。
- 下面的例子为设置一个使用HTTPS客户端认证的配置:
apiVersion: v1
kind: Config
clusters: #clusters代表远程服务。
- name: name-of-remote-authz-service
cluster:
certificate-authority: /path/to/ca.pem #验证远端服务的CA。
server: https://authz.example.com/authorize #远程服务的URL,必须使用'https'。
users: #users代表API服务器的webhook配置
- name: name-of-api-server
user:
client-certificate: /path/to/cert.pem #webhook插件使用证书
client-key: /path/to/key.pem #证书的密钥
current-context: webhook #kubeconfig文件必须有context。需要提供一个给API服务器。
contexts:
- context:
cluster: name-of-remote-authz-service
user: name-of-api-server
name: webhook
- 在授权开始时,API Server会生成一个api.authorization.v1beta1.SubjectAccessReview对象,用于描述操作信息,在进行JSON序列化之后POST出来。在这个对象中包含用户尝试访问资源的请求动作的描述,以及被访问资源的属性。
- Webhook API对象和其他API对象一样,遵循同样的版本兼容性规则,在实现时要注意apiVersion字段的版本,以实现正确的反序列化操作。另外,API Server必须启用authorization.k8s.io/v1beta1 API扩展(--runtime-config=authorization.k8s.io/v1beta1=true)。
{
"apiVersion": "authorization.k8s.io/v1beta1",
"kind": "SubjectAccessReview",
"spec": {
"resourceAttributes": {
"namespace": "kittensandponies",
"verb": "get",
"group": "unicorn.example.org",
"resource": "pods"
},
"user": "jane",
"group": [
"group1",
"group2"
]
}
}
- 远端服务需要填充请求中的SubjectAccessReviewStatus字段,并返回允许或不允许访问的结果。应答报文中的spec字段是无效的,也可以省略。
{
"apiVersion": "authorization.k8s.io/v1beta1",
"kind": "SubjectAccessReview",
"status": {
"allowed": true
}
}
{
"apiVersion": "authorization.k8s.io/v1beta1",
"kind": "SubjectAccessReview",
"status": {
"allowed": false,
"reason": "user does not have read access to the namespace"
}
}
- 非资源的访问请求路径包括/api、/apis、/metrics、/resetMetrics、/logs、/debug、/healthz、/swaggerui/、/swaggerapi/、/ui和/version。通常可以对/api、/api/*、/apis、/apis/*和/version对于客户端发现服务器提供的资源和版本信息给予“允许”授权,对于其他非资源的访问一般可以禁止,以限制客户端对API Server进行没有必要的查询。
- 查询/debug的请求报文示例如下:
{
"apiVersion": "authorization.k8s.io/v1beta1",
"kind": "SubjectAccessReview",
"spec": {
"nonResourceAttributes": {
"path": "/debug",
"verb": "get"
},
"user": "jane",
"group": [
"group1",
"group2"
]
}
}
2.3、RBAC授权模式详解
- RBAC(Role-Based Access Control,基于角色的访问控制)在Kubernetes的1.5版本中引入,在1.6版本时升级为Beta版本,在1.8版本时升级为GA。作为kubeadm安装方式的默认选项,足见其重要程度。
- 相对于其他访问控制方式,新的RBAC具有如下优势。
- 对集群中的资源和非资源权限均有完整的覆盖。
- 整个RBAC完全由几个API对象完成,同其他API对象一样,可以用kubectl或API进行操作。
- 可以在运行时进行调整,无须重新启动API Server。
- 要使用RBAC授权模式,需要在API Server的启动参数中加上--authorization-mode=RBAC。
2.3.1、RBAC的API资源对象说明
- RBAC引入了4个新的顶级资源对象:Role、ClusterRole、RoleBinding和ClusterRoleBinding。同其他API资源对象一样,用户可以使用kubectl或者API调用等方式操作这些资源对象。
1、角色(Role)
- 一个角色就是一组权限的集合,这里的权限都是许可形式的,不存在拒绝的规则。在一个命名空间中,可以用角色来定义一个角色,如果是集群级别的,就需要使用ClusterRole了。
- 角色只能对命名空间内的资源进行授权,在下面例子中定义的角色具备读取Pod的权限:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: default
name: pod-reader
rules:
- apiGroups: [""] #""标明core API组
resources: ["pods"]
verbs: ["get", "watch", "list"]
- rules中的参数说明如下。
- apiGroups:支持的API组列表,例如“apiVersion:batch/v1”“apiVersion: extensions:v1beta1”“apiVersion: apps/v1beta1”等,详细的API组说明参见第9章的说明。
- resources:支持的资源对象列表,例如pods、deployments、jobs等。
- verbs:对资源对象的操作方法列表,例如get、watch、list、delete、replace、patch等,详细的操作方法说明参见第9章的说明。
2、集群角色(ClusterRole)
- 集群角色除了具有和角色一致的命名空间内资源的管理能力,因其集群级别的范围,还可以用于以下特殊元素的授权。
- 集群范围的资源,例如Node。
- 非资源型的路径,例如“/healthz”。
- 包含全部命名空间的资源,例如pods(用于kubectl get pods --all-namespaces这样的操作授权)。
- 下面的集群角色可以让用户有权访问任意一个或所有命名空间的secrets(视其绑定方式而定):
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
#"namespace"被忽略,因为ClusterRoles不受名字空间限制
name: secret-reader
rules:
- apiGroups: [""]
#在HTTP层面,用来访问Secret对象的资源的名称为"secrets"
resources: ["secrets"]
verbs: ["get", "watch", "list"]
3、角色绑定(RoleBinding)和集群角色绑定(ClusterRoleBinding)
- 角色绑定或集群角色绑定用来把一个角色绑定到一个目标上,绑定目标可以是User(用户)、Group(组)或者Service Account。使用RoleBinding为某个命名空间授权,使用ClusterRoleBinding为集群范围内授权。
- RoleBinding可以引用Role进行授权。下面的例子中的RoleBinding将在default命名空间中把pod-reader角色授予用户jane,这一操作可以让jane读取default命名空间中的Pod:
apiVersion: rbac.authorization.k8s.io/v1
#此角色绑定允许"jane"读取"default"名字空间中的Pods
kind: RoleBinding
metadata:
name: read-pods
namespace: default
subjects:
#你可以指定不止一个“subject(主体)”
- kind: User
name: jane #"name"是区分大小写的
apiGroup: rbac.authorization.k8s.io
roleRef:
#"roleRef"指定与某Role或ClusterRole的绑定关系
kind: Role #此字段必须是Role或ClusterRole
name: pod-reader #此字段必须与你要绑定的Role或ClusterRole的名称匹配
apiGroup: rbac.authorization.k8s.io
- RoleBinding也可以引用ClusterRole,对属于同一命名空间内ClusterRole定义的资源主体进行授权。一种常见的做法是集群管理员为集群范围预先定义好一组ClusterRole,然后在多个命名空间中重复使用这些ClusterRole。
- 例如,在下面的例子中,虽然secret-reader是一个集群角色,但是因为使用了RoleBinding,所以dave只能读取development命名空间中的secret:
apiVersion: rbac.authorization.k8s.io/v1
#此角色绑定使得用户"dave"能够读取"development"名字空间中的Secrets
#你需要一个名为"secret-reader"的ClusterRole
kind: RoleBinding
metadata:
name: read-secrets
#RoleBinding 的名字空间决定了访问权限的授予范围。
#这里隐含授权仅在 "development" 名字空间内的访问权限。
namespace: development
subjects:
- kind: User
name: dave #'name'是区分大小写的
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: secret-reader
apiGroup: rbac.authorization.k8s.io
- 集群角色绑定中的角色只能是集群角色,用于进行集群级别或者对所有命名空间都生效的授权。下面的例子允许manager组的用户读取任意Namespace中的secret:
apiVersion: rbac.authorization.k8s.io/v1
#此集群角色绑定允许“manager”组中的任何人访问任何名字空间中的secrets
kind: ClusterRoleBinding
metadata:
name: read-secrets-global
subjects:
- kind: Group
name: manager #'name'是区分大小写的
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: secret-reader
apiGroup: rbac.authorization.k8s.io
- 图6.2展示了上述对Pod的get/watch/list操作进行授权的Role和RoleBinding的逻辑关系。
2.3.2、对资源的引用方式
- 多数资源可以用其名称的字符串来表达,也就是Endpoint中的URL相对路径,例如pods。然而,某些Kubernetes API包含下级资源,例如Pod的日志(logs)。Pod日志的Endpoint是GET /api/v1/namespaces/{namespace}/pods/{name}/log。
- 在这个例子中,Pod是一个命名空间内的资源,log就是一个下级资源。要在一个RBAC角色中体现,就需要用斜线“/”来分隔资源和下级资源。若想授权让某个主体同时能够读取Pod和Pod log,则可以配置resources为一个数组:
- 资源还可以通过名称(ResourceName)进行引用。在指定ResourceName后,使用get、delete、update和patch动词的请求,就会被限制在这个资源实例范围内。例如,下面的声明让一个主体只能对一个ConFigmap进行get和update操作:可想而知,resourceName这种用法对list、watch、create或deletecollection操作是无效的,这是因为必须要通过URL进行鉴权,而资源名称在list、watch、create或deletecollection请求中只是请求Body数据的一部分。下面对常见的角色和角色绑定给出示例,提供参考用法。
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: default
name: pod-and-pod-logs-reader
rules:
- apiGroups: [""]
resources: ["pods", "pods/log"]
verbs: ["get", "list"]
# #