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:其定义分为两部分:crdtargetscrd 的确是一个 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])
        }

上面的代码有几个需要注意的:

  1. metadata.name 要和 spec.crd.spec.names.kind 一致
  2. 规则顺序执行,使用 startswith 函数判断输入内容里面的用户名和命名空间是否为前缀关系
  3. 如果一致,则规则停止执行;如果不一致,则输出拒绝信息。

声明了 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])
        }

相对前面的模板,这个模板复杂了一些:

  1. spec.validation 字段中加入了一个字符串类型的属性,用这个属性作为参数,定义允许使用的容器前缀,使用 input.parameters.prefix 的方式来引用参数。
  2. 有一行奇怪的代码 some isome关键字声明了一个名为 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..

Avatar
崔秀龙

简单,是大师的责任;我们凡夫俗子,能做到清楚就很不容易了。

comments powered by Disqus
下一页
上一页