k8s上abac功能实践

k8s提供了2种方式用来权限控制,abac和rbac,abac是基于一堆属性(attribute)描述成的策略(policy)来做的限制。
比如限制某个用户只能对某个namespace下的资源操作,就可以写成类似下面的描述

1
{"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "spec": {"user": "alice", "namespace": "somenamespace", "resource": "*", "apiGroup": "*"}}

可以看到其实就是user namespace resource apiGroup 等几个选项的排列组合

要启用ABAC需要修改apiserver的启动参数 加上–authorization-mode=ABAC 和 –authorization-policy-file=SOME_FILENAME,这里的SOME_FILENAME 就是个填写了类似以上json的文件,可以填多行,来做多个组合限制,点这里看一个具体的文件列子

策略对象的具体解释

  • 版本控制属性:
    • apiVersion,字符串类型: 有效值为”abac.authorization.kubernetes.io/v1beta1”,允许版本控制和转换策略格式。
    • kind,字符串类型: 有效值为 “Policy”,允许版本控制和转换策略格式。
  • spec 配置为具有以下映射的属性:
    • 匹配属性:
      • user,字符串类型; 来自 --token-auth-file 的用户字符串,如果你指定user,它必须与验证用户的用户名匹配。
      • group,字符串类型; 如果指定group,它必须与经过身份验证的用户的一个组匹配,system:authenticated匹配所有经过身份验证的请求。system:unauthenticated匹配所有未经过身份验证的请求。
  • 资源匹配属性:
    • apiGroup,字符串类型; 一个 API 组。
      • 例: extensions
      • 通配符: *匹配所有 API 组。
      • “”空字符串匹配系统默认的资源 比如POD之类, kubectl get -o yaml 出来apiVersion 没有/的资源
    • namespace,字符串类型; 一个命名空间。
      • 例如: kube-system
      • 通配符: * 匹配所有资源请求。
    • resource,字符串类型; 资源类型。
      • 例:pods
      • 通配符: *匹配所有资源请求。
  • 非资源匹配属性:
    • nonResourcePath,字符串类型; 非资源请求路径。
      • 例如:/version/apis
      • 通配符:
        • * 匹配所有非资源请求。
        • /foo/* 匹配/foo/的所有子路径。
  • readonly,键入 boolean,如果为 true,则表示该策略仅适用于 get,list 和 watch 操作。

一个具体的例子,限制用户ads只能操作ads nameespace下的东西,注意需要最后2行,否则kubectl由于列不到资源列表无法使用

1
2
3
{"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "spec": {"user":"ads",  "namespace": "ads", "resource": "*","apiGroup": "*"}}
{"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "spec": {"group":"system:authenticated", "nonResourcePath": "*", "readonly": true}}
{"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "spec": {"group":"system:unauthenticated", "nonResourcePath": "*", "readonly": true}

限制只能访问基础资源pod configmap ,不能访问如deployment之类的资源

1
2
3
{"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "spec": {"group":"system:authenticated",  "namespace": "*", "resource": "*","apiGroup": ""}}
{"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "spec": {"group":"system:authenticated", "nonResourcePath": "*", "readonly": true}}
{"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "spec": {"group":"system:unauthenticated", "nonResourcePath": "*", "readonly": true}

如果是要能访问deployment 得加上下面这个

1
{"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "spec": {"group":"system:authenticated",  "namespace": "*", "resource": "*","apiGroup": "extensions"}}

下面我们扩展下这个功能:实际场景是用户可能会创建一堆namespace,同样的前缀,但是目前k8s官方是只支持*或者完全匹配,不支持前缀匹配

所有相关的代码在pkg/auth/authorizer/abac/abac.go 中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
func (pl policyList) Authorize(a authorizer.Attributes) (bool, string, error) {
//policy就是策略文件解析出来的内容
//auth 是具体的请求解析的结果
for _, p := range pl {
if matches(*p, a) {
return true, "", nil
}
}
return false, "No policy matched.", nil
}

func matches(p api.Policy, a authorizer.Attributes) bool {
if subjectMatches(p, a) { //match 用户 或者 组
if verbMatches(p, a) { //match 操作 主要是看你的请求如果是写操作的但是又设置了readonly就退出
// Resource and non-resource requests are mutually exclusive, at most one will match a policy
if resourceMatches(p, a) { //match 具体的资源
return true
}
if nonResourceMatches(p, a) {
return true
}
}
}
return false
}
func resourceMatches(p api.Policy, a authorizer.Attributes) bool {
// A resource policy cannot match a non-resource request
if a.IsResourceRequest() {
if p.Spec.Namespace == "*" || p.Spec.Namespace == a.GetNamespace() {//这里具体比较了请求的资源,可以看到这里写的==* 不是通配匹配,我们修改下,再加个或者的条件 || strings.HasPrefix(a.GetNamespace(),p.Spec.Namespace) 就可以实现前缀匹配的功能
if p.Spec.Resource == "*" || p.Spec.Resource == a.GetResource() {
if p.Spec.APIGroup == "*" || p.Spec.APIGroup == a.GetAPIGroup() {
return true
}
}
}
}
return false
}

另外abac缺失个根据verb做权限限制的功能,比如我不想让人delete namespace,目前就没办法实现,不过可以通过rbac的clusterrole实现。实际看了下也可以改改,有空再说。