OPA Gatekeeper 几条入门策略

Gatekeeper 是基于 OPA(Open Policy Agent) 的一个 Kubernetes 策略解决方案。在之前的文章中说过,在 PSP/RBAC 等内置方案之外,在 Kubernetes 中还可以通过策略来实现一些额外的管理、安全方面的限制,本文将会从安装开始,介绍几条实用的小策略。
安装篇
安装可以通过 kubectl
来进行:
$ kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper/master/deploy/gatekeeper.yaml
namespace/gatekeeper-system created
......
gatekeeper-validating-webhook-configuration created
或者也可以使用 Helm(目前只支持 Helm 2):
helm repo add gatekeeper https://raw.githubusercontent.com/open-policy-agent/gatekeeper/master/charts/gatekeeper
helm install gatekeeper/gatekeeper --devel
策略简介
Gatekeeper 的策略通常是由两个资源对象组成的:Template 和 Constraint。
Template:其定义分为两部分:crd
和 targets
,crd
的确是一个 CRD 定义,也就是说生成一个 Template CR 对象,会随之生成一个 CRD;targets
则是一组 rego
为主体的代码包——个人表示很反对这种 YAML 中加代码的粗暴行径。
Contsraint:这个对象的定义来自于 Template 生成的 CRD,它负责为模板输出两种内容:其一是对 Kubernetes 资源对象的过滤,其二就是根据 CRD 定义,为 Template 提供输入参数。
只允许特定用户名操作特定命名空间
在 cluster-admin
成为缺省用户的情况下,我们希望限制特定用户在 Namespace 中的能力,例如下面的规则,会检查用户名前缀是否为命名空间名称:
apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
name: ns-user
spec:
crd:
spec:
names:
kind: ns-user
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package nsuser
violation[{"msg": msg}] {
user_name = input.review.userInfo.username
ns = input.review.object.metadata.namespace
not startswith(user_name, ns)
msg = sprintf("User %v is denied.", [user_name])
}
上面的代码有几个需要注意的:
metadata.name
要和spec.crd.spec.names.kind
一致规则顺序执行,使用
startswith
函数判断输入内容里面的用户名和命名空间是否为前缀关系如果一致,则规则停止执行;如果不一致,则输出拒绝信息。
声明了 Template 之后,使用 kubectl apply -f
提交到集群。
然后创建一个 constraints
:
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: ns-user
metadata:
name: ns-user
spec:
match:
kinds:
- apiGroups: [""]
kinds: ["ServiceAccount"]
这里的 kind
字段使用的就是前面模板生成的 CRD(所以 template 和 contsrint 同时创建的话,后者的创建过程可能失败)。在 match
字段中,我们限制面向的是 ServiceAccount 对象,接下来测试一下:
$ kubectl create sa ab
Error from server ([denied by ns-user] User dustise@gmail.com is denied.): admission webhook "validation.gatekeeper.sh" denied the request: [denied by ns-user] User dustise@gmail.com is denied.
$ kubectl create sa sbac --kubeconfig=kubeconfig-defaultsa -n default
serviceaccount/sbac created
$ kubectl create deployment nginx --image=nginx
deployment.apps/nginx created
上面可以看到,策略成功发挥作用,使用缺省用户无法创建 sa
,但是可以创建 deployment
,换用名为 defaultsa
的用户,则能够创建成功。
这里如果多做一点测试,会发现
DELETE
操作是不受限制的,原因是 Gatekeeper 的 Webhook 配置去掉了对DELETE
的反应,可以kubectl edit ValidatingWebhookConfiguration gatekeeper-validating-webhook-configuration
进行编辑,在operations
字段中加入DELETE
元素。
只允许特定镜像前缀
如果在某集群中,我们要求仅适用内网仓库中的镜像,可以使用如下策略:
apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
name: imagecheck
spec:
crd:
spec:
names:
kind: imagecheck
validation:
openAPIV3Schema:
properties:
prefix:
type: string
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package image
violation[{"msg": msg}] {
containers = input.review.object.spec.template.spec.containers
some i
image := containers[i].image
not startswith(image, input.parameters.prefix)
msg := sprintf("Image '%v' is not allowed.", [image])
}
相对前面的模板,这个模板复杂了一些:
在
spec.validation
字段中加入了一个字符串类型的属性,用这个属性作为参数,定义允许使用的容器前缀,使用input.parameters.prefix
的方式来引用参数。有一行奇怪的代码
some i
,some
关键字声明了一个名为i
的变量,规则会使用变量i
对数组进行轮询,查找前缀不符合参数要求的镜像名称。
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: imagecheck
metadata:
name: imagecheck
spec:
match:
kinds:
- apiGroups: ["apps"]
kinds:
- "Deployment"
- "DaemonSet"
- "StatefulSet"
parameters:
prefix: "dustise/"
Constraints
中注明,对 Deployment
等三种对象进行校验,要求其镜像前缀为 dustise/
,下面我们进行一个测试:
$ kubectl create deployment sleep --image=nginx
Error from server ([denied by imagecheck] Image 'nginx' is not allowed.): admission webhook "validation.gatekeeper.sh" denied the request: [denied by imagecheck] Image 'nginx' is not allowed.
$ kubectl create deployment sleep --image=dustise/sleep
deployment.apps/sleep created
Nginx 镜像被禁止,而 dustise/sleep 镜像则成功创建。
Pod 必须具备资源限制
我们建议所有 Pod 都配置资源限制和请求,便于调度,也能预防系统资源滥用。下面的模板会遍历 Pod 定义,并对资源限制不完整的容器发出警告。
apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
name: resource-limit
spec:
crd:
spec:
names:
kind: resource-limit
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package limit
resources_defined(x) {
x.resources; x.resources.limits; x.resources.requests
}
violation[{"msg": msg}] {
ctr_list = input.review.object.spec.template.spec.containers
some i
ctr = ctr_list[i]
not resources_defined(ctr)
msg = sprintf("%v containers without 'resource' fields", [ctr.name])
}
模板文件中,我们定义了一个函数,分号分割的三个判断构成了逻辑与的关系,缺乏任何一个字段都会导致返回 false
。
接下来创建类似的 Constraint 对象:
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: resource-limit
metadata:
name: resource-limit
spec:
match:
kinds:
- apiGroups: ["apps"]
kinds:
- "Deployment"
- "DaemonSet"
- "StatefulSet"
再次创建 Deployment,会看到新的拒绝信息:
$ kubectl create deployment sleep2 --image=dustise/sleep
Error from server ([denied by resource-limit] sleep containers without 'resource' fields): admission webhook "validation.gatekeeper.sh" denied the request: [denied by resource-limit] sleep containers without 'resource' fields
如果创建下列代码所包含的 Deployment 对象,则会成功:
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: sleep
name: sleep
spec:
selector:
matchLabels:
app: sleep
template:
metadata:
labels:
app: sleep
spec:
containers:
- image: dustise/sleep
imagePullPolicy: Always
name: sleep
resources:
limits:
cpu: 100m
requests:
cpu: 100m
dnsPolicy: ClusterFirst
小结
Rego 语法还是有点烦人的,好在官方源码中提供了一些样例和基本用途的代码库可以参考。另外也可以用 Rego Playground 进行在线调试,来编写稍微复杂一点的策略。
我还是喜欢 Kyverno..
Subscribe to my newsletter
Read articles from 崔秀龙 directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
