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 定义,并对资源限制不完整的容器发出警告。
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..