apache/casbin-k8s-gatekeeper
GitHub: apache/casbin-k8s-gatekeeper
一个基于Casbin策略引擎的Kubernetes准入控制器,允许通过自定义的访问控制规则来拦截并决定是否允许对集群资源的操作请求。
Stars: 35 | Forks: 3
# K8s-gatekeeper
## [](https://github.com/casbin/k8s-gatekeeper/actions/workflows/ci.yml)
[](https://coveralls.io/github/casbin/k8s-gatekeeper?branch=master)
[](https://goreportcard.com/report/github.com/casbin/k8s-gatekeeper)
[](https://godoc.org/github.com/casbin/k8s-gatekeeper)
## 1. 概述
### 1.1 什么是 K8s-gatekeeper
K8s-gatekeeper 是一个 k8s 的 admission webhook,它使用 [Casbin](https://casbin.org/docs/overview) 来应用任意用户自定义的访问控制规则,以帮助阻止管理员不希望在任何对 k8s 进行的操作。
Casbin 是一个强大且高效的开源访问控制库。它支持基于各种访问控制模型的授权执行。有关 Casbin 的更多细节,请参阅 。
K8s 中的 Admission webhook 是接收“准入请求”并对其进行处理的 HTTP 回调。特别的是,K8s-gatekeeper 是一种特殊类型的 admission webhook:“ValidatingAdmissionWebhook”,它可以决定是接受还是拒绝该准入请求。至于准入请求,它们是描述对 K8s 指定资源操作(例如,创建/删除 deployment)的 HTTP 请求。有关 admission webhook 的更多信息,请参阅 K8s 官方文档
### 1.2 说明其工作原理的示例。

例如,当有人想要创建一个包含运行 nginx 的 pod 的 deployment 时(使用 kubectl 或 k8s 客户端),K8s 会生成一个准入请求,该请求(如果转换为 yaml 格式)可能类似于以下内容。
```
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
selector:
matchLabels:
app: nginx
replicas: 1
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.14.1
ports:
- containerPort: 80
```
该请求将经过图中所示的所有中间件的处理,包括我们的 K8s-gatekeeper。K8s-gatekeeper 可以检测到存储在 K8s 的 etcd 中的所有 Casbin enforcer,这些 enforcer 由用户创建和维护(通过我们提供的 kubectl 或 go-client)。每个 enforcer 都包含一个 Casbin model 和一个 Casbin policy。准入请求将被每个 enforcer 逐一处理,只有通过所有 enforcer,请求才会被此 K8s-gatekeeper 接受。
(如果您不了解什么是 Casbin enforcer、model 或 policy,请参阅此文档 )
例如,出于某种原因,管理员想要禁止出现镜像 'nginx:1.14.1',同时允许 'nginx:1.3.1',可以创建一个包含以下规则和 policy 的 enforcer:(我们将在后续章节中解释如何创建 enforcer,这些 model 和 policy 是什么以及如何编写它们。)
model:
```
[request_definition]
r = obj
[policy_definition]
p = obj,eft
[policy_effect]
e = !some(where (p.eft == deny))
[matchers]
m = r.obj.Request.Namespace == "default" && r.obj.Request.Resource.Resource =="deployments" && \
access(r.obj.Request.Object.Object.Spec.Template.Spec.Containers , 0, "Image") == p.obj
```
policy:
```
p, "nginx:1.13.1",allow
p, "nginx:1.14.1",deny
```
通过创建包含上述 model 和 policy 的 enforcer,之前的准入请求将被此 enforcer 拒绝,这意味着 K8s 将不会创建此 deployment。
## 2 安装 K8s-gatekeeper
提供了三种安装 K8s-gatekeeper 的方法:外部 webhook、内部 webhook 和 helm。
*注意:这些方法仅供用户体验 K8s-gatekeeper,并不安全。如果您想在生产环境中使用它,请确保在安装前阅读第 5 章. 高级设置 并在必要时进行相应修改*
### 2.1 内部 webhook
#### 2.1.1 第 1 步:构建镜像
内部 webhook 意味着 webhook 本身将作为 k8s 内部的一个 service 来实现。创建 service 和 deployment 需要 K8s-gatekeeper 的镜像。您应该构建自己的镜像。
运行
```
docker build --target webhook -t k8s-gatekeeper .
```
然后会有一个名为 'k8s-gatekeeper:latest' 的本地镜像。
*注意:如果您使用的是 minikube,请在运行 docker build 之前执行 `eval $(minikube -p minikube docker-env)`*
#### 2.1.2 第 2 步:为 K8s-gatekeeper 设置 service 和 deployment
运行以下命令
```
kubectl apply -f config/rbac.yaml
kubectl apply -f config/webhook_deployment.yaml
kubectl apply -f config/webhook_internal.yaml
```
很快 K8s-gatekeeper 就会运行起来,您可以使用 `kubectl get pods` 来确认。
#### 2.1.3 第 3 步:为 K8s-gatekeeper 安装 Crd 资源
运行以下命令
```
kubectl apply -f config/auth.casbin.org_casbinmodels.yaml
kubectl apply -f config/auth.casbin.org_casbinpolicies.yaml
```
### 2.2 外部 webhook
外部 webhook 意味着 K8s-gatekeeper 将在 K8s 外部运行,K8s 访问 K8s-gatekeeper 就像访问普通网站一样。K8s 强制要求 admission webhook 必须是 HTTPS。为了照顾用户体验尝试 K8s-gatekeeper,我们为您提供了一套证书以及私钥(尽管这不安全)。如果您希望使用自己的证书,请参阅第 5 章. 高级设置 以对证书和私钥进行调整。
我们提供的证书是为 'webhook.domain.local' 签发的,因此请修改 host(如 /etc/hosts),将 webhook.domain.local 指向运行 K8s-gatekeeper 的 IP 地址。
然后执行
```
go mod tidy
go mod vendor
go run cmd/webhook/main.go
kubectl apply -f config/auth.casbin.org_casbinmodels.yaml
kubectl apply -f config/auth.casbin.org_casbinpolicies.yaml
kubectl apply -f config/webhook_external.yaml
```
### 2.3 通过 helm 安装 K8s-gatekeeper
#### 2.3.1 第 1 步:构建镜像
参见第 2.1.1 节
#### 2.3.2 helm 安装
运行
`helm install k8sgatekeeper ./k8sgatekeeper `
## 3. 试用 K8s-gatekeeper
### 3.1 创建 Casbin Model 和 Policy
您有 2 种方法来创建 model 和 policy:通过 kubectl 或通过我们提供的 go-client。
#### 3.1.1 通过 kubectl 创建/更新 Casbin Model 和 Policy
在 K8s-gatekeeper 中,Casbin model 存储在一种名为 'CasbinModel' 的 CRD 资源中。其定义位于 config/auth.casbin.org_casbinmodels.yaml
在 example/allowed_repo/model.yaml 中有示例。您应该注意以下字段:
- metadata.name:model 的名称。此名称必须与此 model 关联的 CasbinPolicy 对象的名称相同,以便 K8s-gatekeeper 可以将它们配对并创建一个 enforcer。
- spec.enable:如果此字段设置为 "false",则此 model(以及与此 model 关联的 CasbinPolicy 对象)将被忽略。
- spec.modelText:一个包含 casbin model 文本的字符串。
Casbin Policy 存储在另一种名为 'CasbinPolicy' 的 CRD 资源中,其定义可在 config/auth.casbin.org_casbinpolicies.yaml 中找到
在 example/allowed_repo/policy.yaml 中有示例。您应该注意以下字段:
- metadata.name:policy 的名称。此名称必须与此 policy 关联的 CasbinModel 对象的名称相同,以便 K8s-gatekeeper 可以将它们配对并创建一个 enforcer。
- spec.policyItem:一个包含 casbin model policy 文本的字符串。
创建您自己的 CasbinModel 和 CasbinPolicy 文件后,使用
```
kubectl apply -f
```
使它们生效。
一旦创建了一对 CasbinModel 和 CasbinPolicy,K8s-gatekeeper 最多在 5 秒内就能检测到它。
#### 3.1.2 通过我们提供的 go-client 创建/更新 Casbin Model 和 Policy
已经考虑到可能存在不方便直接在 K8s 集群的节点上使用 shell 执行命令的情况,例如,当您正在为您的企业构建自动化云平台时。因此我们开发了一个 go-client 来创建和维护 CasbinModel 和 CasbinPolicy。
go-client 库位于 pkg/client。
在 client.go 中我们提供了创建 client 的函数。
```
func NewK8sGateKeeperClient(externalClient bool) (*K8sGateKeeperClient, error)
```
参数 externalClient 表示 K8s-gatekeeper 是否运行在 K8s 集群内部。
在 model.go 中我们提供了各种用于创建/删除/修改 CasbinModel 的函数。您可以在 model_test.go 中找到如何使用这些接口。
在 policy.go 中我们提供了各种用于创建/删除/修改 CasbiPolicy 的函数。您可以在 policy_test.go 中找到如何使用这些接口。
### 3.1.2 测试 K8s-gatekeeper 是否有效
假设您已经完全按照 example/allowed_repo 中的 model 和 policy 进行了创建,现在试试这个
```
kubectl apply -f example/allowed_repo/testcase/reject_1.yaml
```
您会发现 k8s 将拒绝此请求,并提到此 webhook 是请求被拒绝的原因。但是,当您尝试 apply example/allowed_repo/testcase/approve_2.yaml 时,它将被接受。
## 4. 如何编写 K8s-gatekeeper 的 Model 和 Policy
首先,您应该了解 Casbin Models 和 Policies 的基本语法。如果您还未了解,请先阅读 。在本章中,我们将假设您已经知道什么是 Casbin Models 和 Policies。
### 4.1 Model 的请求定义
当 K8s-gatekeeper 在对请求进行授权时,输入总是一个对象:Admission Request 的 go 对象。这意味着 enforcer 的使用方式总是像这样
```
ok, err := enforcer.Enforce(admission)
```
其中 admission 是一个由 K8s 官方 go api `"k8s.io/api/admission/v1"` 定义的 `AdmissionReview` 对象。您可以在此仓库 中查看此结构体的定义。或者查看 了解更多信息
因此,对于 K8s-gatekeeper 使用的任何 model,request_definition 的定义应始终如下所示
```
[request_definition]
r = obj
```
名称 'obj' 不是强制性的,只要该名称与 `[matchers]` 部分中使用的名称保持一致即可。
### 4.2 Model 的匹配器
您应该使用 Casbin 的 ABAC 功能来编写您的规则。然而,Casbin 中集成的表达式求值器既不支持在 map 或数组(切片)中进行索引,也不支持数组展开。因此 K8s-gatekeeper 提供了各种“Casbin 函数”作为扩展来实现这些功能。如果您仍然发现这些扩展无法满足您的需求,欢迎发起一个 issue,或直接提交 pr。
如果您不知道什么是 casbin function,请参阅 获取更多信息。
以下是扩展函数
### 4.2.1 扩展函数
#### 4.2.1.1 access
Access 用于解决 Casbin 不支持在 map 或 array 中进行索引的问题。`example/allowed_repo/model.yaml` 是此函数的示例
```
[matchers]
m = r.obj.Request.Namespace == "default" && r.obj.Request.Resource.Resource =="deployments" && \
access(r.obj.Request.Object.Object.Spec.Template.Spec.Containers , 0, "Image") == p.obj
```
在此 matcher 中,access(r.obj.Request.Object.Object.Spec.Template.Spec.Containers , 0, "Image") 等同于 `r.obj.Request.Object.Object.Spec.Template.Spec.Containers[0].Image`,其中 `r.obj.Request.Object.Object.Spec.Template.Spec.Containers` 显然是一个切片。
Access 还能够调用没有参数且只有一个返回值的简单函数。`example/container_resource_limit/model.yaml` 就是一个示例。
```
[matchers]
m = r.obj.Request.Namespace == "default" && r.obj.Request.Resource.Resource =="deployments" && \
parseFloat(access(r.obj.Request.Object.Object.Spec.Template.Spec.Containers , 0, "Resources","Limits","cpu","Value")) >= parseFloat(p.cpu) && \
parseFloat(access(r.obj.Request.Object.Object.Spec.Template.Spec.Containers , 0, "Resources","Limits","memory","Value")) >= parseFloat(p.memory)
```
在此 matcher 中,`access(r.obj.Request.Object.Object.Spec.Template.Spec.Containers , 0, "Resources","Limits","cpu","Value")` 等同于 `r.obj.Request.Object.Object.Spec.Template.Spec.Containers[0].Resources.Limits["cpu"].Value()`,其中 `r.obj.Request.Object.Object.Spec.Template.Spec.Containers[0].Resources.Limits` 是一个 map,而 `Value()` 是一个没有参数且只有一个返回值的简单函数。
#### 4.2.1.2 accessWithWildcard
有时自然会有这样的需求:数组中的所有元素都必须具有前缀 "aaa"。然而,Casbin 不支持 `for` 循环。但是借助 `accessWithWildcard` 和“map/slice 展开”功能,可以轻松实现此类需求。
例如,假设 `a.b.c` 是一个数组 `[aaa,bbb,ccc,ddd,eee]`,那么 `accessWithWildcard(a,"b","c","*")` 的结果将是一个切片 `[aaa,bbb,ccc,ddd,eee]`。看到了吗?使用通配符 `*` 这个切片被展开了。
同样地,通配符可以使用多次。例如,`accessWithWildcard(a,"b","c","*","*")` 的结果将是 `[a.b.c[0][0], a.b.c[0][1]... a.b.c[1][0], a.b.c[1][1]...]`
### 4.2.1.3 支持可变长度参数的函数
在 Casbin 的表达式求值器中,当一个参数是数组时,它将自动作为可变长度参数展开。利用此功能来支持 array/slice/map 展开,我们还集成了几个接受 array/slice 作为参数的函数。
- contain(),接受多个参数,并返回除最后一个参数之外是否有一个参数等于最后一个参数
- split(a,b,c...,sep,index),它返回一个包含 `[splits(a,sep)[index], splits(b,sep)[index], splits(a,sep)[index]...]` 的切片
- len() 返回可变长度参数的长度
- matchRegex(a,b,c...regex) 返回 a,b,c... 是否全部匹配给定的 regex以下是 `example/disallowed_tag/model.yaml` 中的一个示例
```
[matchers]
m = r.obj.Request.Namespace == "default" && r.obj.Request.Resource.Resource =="deployments" && \
contain(split(accessWithWildcard(r.obj.Request.Object.Object.Spec.Template.Spec.Containers , "*", "Image"),":",1) , p.obj)
```
假设 `accessWithWildcard(r.obj.Request.Object.Object.Spec.Template.Spec.Containers , "*", "Image")` 返回 `["a:b", "c:d", "e:f", "g:h"]`,那么由于 splits 支持可变长度参数,splits 操作将应用于每个元素,并最终选择 index 为 1 的元素并返回,因此 `split(accessWithWildcard(r.obj.Request.Object.Object.Spec.Template.Spec.Containers , "*", "Image"),":",1)` 返回 `["b","d","f","h"]`。并且 `contain(split(accessWithWildcard(r.obj.Request.Object.Object.Spec.Template.Spec.Containers , "*", "Image"),":",1) , p.obj)` 返回 `p.obj` 是否包含在 `["b","d","f","h"]` 中
#### 4.2.1.2 类型转换函数
- ParseFloat():将一个整数解析为 float。(这是因为比较中的任何数字都必须转换为 float)。
- ToString():将一个 object 转换为 string。此 object 必须具有 string 的基本类型。(例如,当存在语句 `type XXX string` 时的 `XXX` 类型的 object)
- IsNil():返回参数是否为 nil
## 5. 高级设置
### 5.1 关于证书
在 k8s 中,强制要求 webhook 使用 HTTPS。有两种方法可以实现这一点:
- 使用自签名证书(本仓库中的示例使用此方法)
- 使用正规证书
#### 5.1.1 自签名证书
使用自签名证书意味着签发该证书的 CA 不是知名的 CA 之一,因此您必须让 k8s 知道此 CA。
当前本仓库中的示例使用了一个自制的 CA,其私钥和证书存储在 `config/certificate/ca.crt` 和 `config/certificate/ca.key` 中。Webhook 的证书为 `config/certificate/server.crt`,由自制的 CA 签发。此证书的域名为 "webhook.domain.local"(用于外部 webhook)和 "casbin-webhook-svc.default.svc"(用于内部 webhook)
有关 CA 的信息通过 webhook 配置文件传递给 k8s。`config/webhook_external.yaml` 和 `config/webhook_internal.yaml` 都有一个名为 "CABundle" 的字段,其内容是 CA 证书的 base64 编码字符串。
如果您需要更改证书/域(例如,可能您想在使用内部 webhook 的同时将此 webhook 放入 k8s 的另一个 namespace 中;或者您想在使用外部 webhook 时更改域),应采取以下步骤:
1. 生成新的 CA
为伪 CA 生成私钥
```
openssl genrsa -des3 -out ca.key 2048
```
移除私钥的密码保护。
```
openssl rsa -in ca.key -out ca.key
```
2. 为 webhook server 生成私钥
```
openssl genrsa -des3 -out server.key 2048
openssl rsa -in server.key -out server.key
```
3. 使用自生成的 CA 为 webhook 签名证书
复制系统的 openssl 配置文件以供临时使用。您可以使用 `openssl version -a` 找出配置文件的位置。
找到 \[req\] 段落并添加以下行:`req_extensions = v3_req`
找到 \[v3_req\] 段落并添加以下行:`subjectAltName = @alt_names`
将以下行追加到文件中:
```
[alt_names]
DNS.2=
```
'casbin-webhook-svc.default.svc' 应该替换为您自己 service 的真实 service 名称(如果您决定修改 service 名称)
使用修改后的配置文件生成证书请求文件
```
openssl req -new -nodes -keyout server.key -out server.csr -config openssl.cnf
```
使用自制的 CA 响应请求并签名证书
```
openssl x509 -req -days 3650 -in server.csr -out server.crt -CA ca.crt -CAkey ca.key -CAcreateserial -extensions v3_req -extensions SAN -extfile openssl.cnf
```
3. 替换 'CABundle' 字段
`config/webhook_external.yaml` 和 `config/webhook_internal.yaml` 都有一个名为 "CABundle" 的字段,其内容是 CA 证书的 base64 编码字符串。
4. 如果您使用的是 helm,则需要对 helm charts 进行类似的更改。
#### 5.1.2 合法证书
如果您使用合法的证书,您就不需要所有这些过程。删除 `config/webhook_external.yaml` 和 `config/webhook_internal.yaml` 中的 "CABundle" 字段,并将这些文件中的域更改为您拥有的域。
标签:ABAC, Admission Controller, Casbin, EVTX分析, Golang, Go语言, k8s, RBAC, Streamlit, ValidatingAdmissionWebhook, Webhook, Web截图, 准入控制器, 力导向图, 子域名突变, 安全策略, 安全编程, 容器安全, 拦截器, 提示词设计, 日志审计, 权限控制, 程序破解, 策略即代码, 策略引擎, 网络安全挑战, 聊天机器人安全, 访问控制, 请求拦截