2

使用 Gatekeeper 进行 OPA 策略管理

 2 years ago
source link: https://www.51cto.com/article/704990.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
使用 Gatekeeper 进行 OPA 策略管理-51CTO.COM
使用 Gatekeeper 进行 OPA 策略管理
作者:阳明 2022-03-28 07:33:13
Gatekeeper(v3.0) 准入控制器集成了 OPA Constraint Framework,以执行基于 CRD 的策略,并允许声明式配置的策略可靠地共享,使用 kubebuilder 构建,它提供了验证和修改准入控制和审计功能。

918cb58746ad255cdda73484d497633f16c472.png

前面我们介绍了使用 kube-mgmt 这个 sidecar 容器来完成 OPA 策略的自动同步,此外还有另外一个更加高级的工具 Gatekeeper,相比于之前的模式,Gatekeeper(v3.0) 准入控制器集成了 OPA Constraint Framework,以执行基于 CRD 的策略,并允许声明式配置的策略可靠地共享,使用 kubebuilder 构建,它提供了验证和修改准入控制和审计功能。这允许为 Rego 策略创建策略模板,将策略创建为 CRD,并在策略 CRD 上存储审计结果,这个项目是谷歌、微软、红帽和 Styra 一起合作实现的。

2961b4643dc08aeca696315516d06330551f5b.png

直接使用下面的命令即可安装 Gatekeeper:

➜ kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper/release-3.7/deploy/gatekeeper.yaml

默认会将 Gatekeeper 安装到 gatekeeper-system 命名空间下面,同样会安装几个相关的 CRD:

➜ kubectl get pods -n gatekeeper-system
NAME                                             READY   STATUS    RESTARTS   AGE
gatekeeper-audit-5cf4b9686-glndv                 1/1     Running   0          2m2s
gatekeeper-controller-manager-77b7dc99fb-dvkvp   1/1     Running   0          2m2s
gatekeeper-controller-manager-77b7dc99fb-gk4gr   1/1     Running   0          2m2s
gatekeeper-controller-manager-77b7dc99fb-mt5wn   1/1     Running   0          2m2s
➜ kubectl get crd |grep gate
assign.mutations.gatekeeper.sh                       2022-03-27T06:47:24Z
assignmetadata.mutations.gatekeeper.sh               2022-03-27T06:47:24Z
configs.config.gatekeeper.sh                         2022-03-27T06:47:24Z
constraintpodstatuses.status.gatekeeper.sh           2022-03-27T06:47:24Z
constrainttemplatepodstatuses.status.gatekeeper.sh   2022-03-27T06:47:24Z
constrainttemplates.templates.gatekeeper.sh          2022-03-27T06:47:24Z
modifyset.mutations.gatekeeper.sh                    2022-03-27T06:47:24Z
mutatorpodstatuses.status.gatekeeper.sh              2022-03-27T06:47:25Z
providers.externaldata.gatekeeper.sh                 2022-03-27T06:47:25Z

Gatekeeper 使用 OPA 约束框架来描述和执行策略,在定义约束之前必须首先定义一个 ConstraintTemplate 对象,它描述了强制执行约束的 Rego 和约束的模式。约束的模式允许管理员对约束的行为进行微调,就像函数的参数一样。

如下所示是一个约束模板,描述了验证的对象必须要有标签存在:

# k8srequiredlabels_template.yaml
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
  name: k8srequiredlabels
spec:
  crd:
    spec:
      names:
        kind: K8sRequiredLabels
      validation:
        openAPIV3Schema:  # Schema for the `parameters` field
          type: object
          description: Describe K8sRequiredLabels crd parameters
          properties:
            labels:
              type: array
              items:
                type: string
                description: A label string
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package k8srequiredlabels

        violation[{"msg": msg, "details": {"missing_labels": missing}}] {
          provided := {label | input.review.object.metadata.labels[label]}
          required := {label | label := input.parameters.labels[_]}
          missing := required - provided
          count(missing) > 0
          msg := sprintf("you must provide labels: %v", [missing])
        }

直接应用上面的 ConstraintTemplate 资源清单:

➜ kubectl apply -f k8srequiredlabels_template.yaml
constrainttemplate.templates.gatekeeper.sh/k8srequiredlabels created
➜ kubectl get ConstraintTemplate
NAME                AGE
k8srequiredlabels   68s

上面我们的定义的 ConstraintTemplate 对象就是一个模板,其中的 crd 部分描述了我们定义的 CRD 模板,比如类型叫 K8sRequiredLabels,需要和模板的名称保持一致,然后通过下面的 validation 定义了我们的 CRD 的属性 Schema,比如有一个 labels 的属性参数,类似是字符串数据类型:

crd:
  spec:
    names:
      kind: K8sRequiredLabels
    validation:
      openAPIV3Schema:  # Schema for the `parameters` field
        type: object
        description: Describe K8sRequiredLabels crd parameters
        properties:
          labels:
            type: array
            items:
              type: string
              description: A label string

然后下面的 targets 部分就是定义的约束目标,使用 Rego 进行编写。

  • 首先通过 provided := {label | input.review.object.metadata.labels[label]} 获取到创建对象的所有 label 标签。
  • 然后通过 required := {label | label := input.parameters.labels[_]} 获取到需要提供的 label 标签。
  • 将上面两个标签集合相减(rego语言支持该操作),得到未满足的 label。
  • 断言未满足的label数量>0,如果大于0,说明条件满足,violation 为 true,说明违反了约束,返回错误。

上面的约束模板创建完成后,实际上相当于创建了一个名为的 K8sRequiredLabels 对象,我们定义的属性位于 spec.parameters 属性下面:

➜ kubectl get K8sRequiredLabels
No resources found
➜ kubectl explain K8sRequiredLabels.spec.parameters.labels
KIND:     K8sRequiredLabels
VERSION:  constraints.gatekeeper.sh/v1beta1
FIELD:    labels <[]string>
DESCRIPTION:
     A label string

现在我们就可以使用上面的 K8sRequiredLabels 这个约束模板来定义策略了,比如我们要求在所有命名空间上都定义一个 gatekeeper 的标签,则可以创建如下所示的对象:

# all_ns_must_have_gatekeeper.yaml
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredLabels
metadata:
  name: ns-must-have-gk
spec:
  match:
    kinds:
      - apiGroups: [""]
        kinds: ["Namespace"]  # 表示这个约束会在创建命名空间的时候被应用,可以使用 namespaceSelector、namespaces等进行过滤
  parameters:
    labels: ["gatekeeper"]  # 根据schema规范定义

注意 match 字段,它定义了将应用给定约束的对象的范围,其中 kinds: ["Namespace"] 表示这个约束会在创建命名空间的时候被应用,此外它还支持其他匹配器:

  • kind 接受带有 apiGroups 和 kind 字段的对象列表,这些字段列出了约束将应用到的对象的组/种类。如果指定了多个组/种类对象,则资源在范围内只需要一个匹配项。
  • scope 接受 、Cluster 或 Namespaced 决定是否选择集群范围和/或命名空间范围的资源。(默认为)
  • namespaces 是命名空间名称的列表。如果已定义,则约束仅适用于列出的命名空间中的资源。命名空间还支持基于前缀的 glob。例如,namespaces: [kube-*] 匹配 kube-system 和 kube-public。
  • excludeNamespaces 是命名空间名称的列表。如果已定义,则约束仅适用于不在列出的命名空间中的资源。ExcludedNamespaces 还支持基于前缀的 glob,例如,excludedNamespaces: [kube-*] 匹配 kube-system 和 kube-public。
  • labelSelector 是标准的 Kubernetes 标签选择器。
  • namespaceSelector 是针对对象的包含名称空间或对象本身的标签选择器,如果对象是名称空间。name 是对象的名称。如果已定义,则匹配具有指定名称的对象。Name 还支持基于前缀的 glob。例如,名称:pod-* 匹配 pod-a 和 pod-b。

下面的 parameters.labels 就是根据上面的 CRD 规范定义的属性,该值是传递给 opa 的参数,此处表示一个 key 为 labels,value 为一个列表的字典,与 ConstraintTemplate 里的 properties 要匹配上,此处表示要创建的对象需要含有 gatekeeper 的 label。

直接应用上面的这个资源对象即可:

➜ kubectl apply -f all_ns_must_have_gatekeeper.yaml
k8srequiredlabels.constraints.gatekeeper.sh/ns-must-have-gk created

创建完成后可以查看到这个 constraints 对象:

➜ kubectl get k8srequiredlabels
NAME              AGE
ns-must-have-gk   73s
➜ kubectl get constraints  # 和上面对象一样
NAME              AGE
ns-must-have-gk   81s

由于 Gatekeeper 具有审计功能,可以根据集群中执行的约束条件对资源进行定期评估,以检测预先存在的错误配置,Gatekeeper 将审计结果存储为相关约束条件的 status 字段中列出违规行为。我们可以查看 K8sRequiredLabels 对象的 status 字段来查看不符合约束的行为:

➜ kubectl get constraints ns-must-have-gk -o yaml
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredLabels
......
status:
  auditTimestamp: "2022-03-27T07:42:38Z"
  ......
  totalViolations: 11
  violations:
  - enforcementAction: deny
    kind: Namespace
    message: 'you must provide labels: {"gatekeeper"}'
    name: apisix
  - enforcementAction: deny
    kind: Namespace
    message: 'you must provide labels: {"gatekeeper"}'
    name: default
  ......

比如现在我们创建一个如下所示的 Namespace:

# test-namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: ns-test
  labels:
    a: b
    #gatekeeper: abc

此时不给命名空间添加 key 为 gatekeeper 的 label,创建的时候就会报错:

Error from server ([ns-must-have-gk] you must provide labels: {"gatekeeper"}): error when creating "test-namespace.yaml": admission webhook "validation.gatekeeper.sh" denied the request: [ns-must-have-gk] you must provide labels: {"gatekeeper"}

然后把 gatekeeper: abc 这行的注释打开,则能成功创建了,这就是 Gatekeeper 的基本用法。

从上面我们可以知道定义约束模板的策略会经常从 input 对象中获取数据,但是如果需要创建自己的约束,但是不知道传入的参数即 input 是什么,有一种简单方法是使用拒绝所有请求并将请求对象作为其拒绝消息输出的约束/模板。我们可以在创建模板时在 violation 中只保留一行 msg := sprintf("input: %v", [input]),此时创建对象时必定会失败,然后获取到输出的错误信息,里面即包含所有 input 信息,之后再通过 Rego 语法去获取需要的数据即可。

apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
  name: k8sdenyall
spec:
  crd:
    spec:
      names:
        kind: K8sDenyAll
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package k8sdenyall

        violation[{"msg": msg}] {
          msg := sprintf("input:  %v", [input])
        }
---
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sDenyAll
metadata:
  name: deny-all-namespaces
spec:
  match:
    kinds:
      - apiGroups: [""]
        kinds: ["Namespace"]

由于约束模板或者说策略库具有一定的通用性,所以 OPA Gatekeeper 社区提供了一个通用的策略库:https://github.com/open-policy-agent/gatekeeper-library,该仓库中包含了大量通用的约束模板。

a8d3ef6250bfde469c3606055f05ec10d220ca.png

每个模板库下面都包含一个 template.yaml 文件用来描述约束模板,samples 目录下面就包含具体的约束对象和示例资源清单,这些策略也是我们去学习 Rego 语言的很好的案例。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK