美文网首页Kyverno
Kyverno外部数据源

Kyverno外部数据源

作者: 王勇1024 | 来源:发表于2023-01-08 09:42 被阅读0次

    在 Kyverno 策略中使用来自 ConfigMap、Kubernetes API Server和 image registry 的数据。

    变量部分讨论了变量如何帮助创建更智能和可重用的策略定义,并介绍了存储所有变量的规则 context 的概念。

    本节提供关于在策略中使用来自 ConfigMap、Kubernetes API Server和 image registry 的数据的详细信息。

    注意

    为了提高安全性和性能,Kyverno 被设计为不允许连接到集群 Kubernetes API Server和 image registry以外的系统。使用单独的控制器从任何来源获取数据并将其存储在可在策略中有效使用的 ConfigMap 中。这种设计可以实现关注点分离和安全边界的实施。

    来自 ConfigMap 的变量

    Kubernetes 中的 ConfigMap 资源通常用作应用程序可以使用的配置详细信息的来源。这些数据可以以多种格式写入,存储在命名空间中,并且可以轻松访问。 Kyverno 支持使用 ConfigMap 作为变量的数据源。评估引用 ConfigMap 资源的策略时,会检查 ConfigMap 数据,以确保对 ConfigMap 的引用始终是动态的。如果 ConfigMap 更新,后续策略查找将在该时间点获取最新数据。

    为了在 rule 中使用来自 ConfigMap 的数据,需要一个context。对于您希望使用 ConfigMap 中的数据的每个 rule,您必须定义一个 context。然后可以使用 JMESPath 表示法在策略 rule 中引用上下文数据。

    查找 ConfigMap 值

    在规则的 context 中定义的 ConfigMap 可以使用其在上下文中的唯一名称来引用。可以使用 JMESPath 样式表达式引用 ConfigMap 值。

    {{ <context-name>.data.<key-name> }}
    

    考虑这样一个简单的 ConfigMap 定义。

    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: some-config-map
      namespace: some-namespace
    data:
      env: production
    

    要在 rule 内引用来自 ConfigMap 的值,请在 rule 内定义一个使用了一个或多个 ConfigMap 声明的 context。使用上面引用的示例 ConfigMap 片段,以下 rule 定义了一个按名称引用此特定 ConfigMap 的 context。

    rules:
      - name: example-lookup
        # Define a context for the rule
        context:
        # A unique name for the ConfigMap
        - name: dictionary
          configMap:
            # Name of the ConfigMap which will be looked up
            name: some-config-map
            # Namespace in which this ConfigMap is stored
            namespace: some-namespace 
    

    基于上面的示例,我们现在可以使用 {{dictionary.data.env}} 引用 ConfigMap 值。在策略执行期间,该变量将替换为 production。

    放入完整 ClusterPolicy 的上下文中,将 ConfigMap 作为变量引用如下所示。

    apiVersion: kyverno.io/v1
    kind: ClusterPolicy
    metadata:
      name: cm-variable-example
      annotations:
        pod-policies.kyverno.io/autogen-controllers: DaemonSet,Deployment,StatefulSet
    spec:
        rules:
        - name: example-configmap-lookup
          context:
          - name: dictionary
            configMap:
              name: some-config-map
              namespace: some-namespace
          match:
            any:
            - resources:
                kinds:
                - Pod
          mutate:
            patchStrategicMerge:
              metadata:
                labels:
                  my-environment-name: "{{dictionary.data.env}}"
    

    在上面的 ClusterPolicy 中,一个 mutate 规则匹配所有传入的 Pod 资源,并为其添加一个名为 my-environment-name 的 label。因为我们已经定义了一个 context,它指向我们之前名为 some-config-map 的 ConfigMap,所以我们可以使用表达式 {{dictionary.data.env}} 来引用该值。一个新创建的 Pod 将会接收到 label my-environment-name=production。

    注意:ConfigMap 名称和键可以包含 JMESPath 不支持的字符,例如“-”(减号或破折号)或“/”(斜杠)。要将这些字符计算为文字,请在 JMESPath 表达式的该部分添加双引号,如下所示:

    {{ "<name>".data."<key>" }}
    

    有关格式化问题的更多信息,请参阅 JMESPath 页面

    处理 ConfigMap 数组值

    除了简单的字符串值之外,Kyverno 还能够使用 ConfigMap 中的数组值。

    注意:自 Kyverno 1.7.0 起,将数组值存储在 YAML 块标量中已被删除。请改用 JSON 编码的字符串数组。

    例如,假设您想在 ConfigMap 中定义允许的角色列表。Kyverno 策略可以引用此列表来拒绝注释中定义的角色的请求。

    假设一个 ConfigMap,其内容为 YAML 多行值。

    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: roles-dictionary
      namespace: default
    data:
      allowed-roles: "[\"cluster-admin\", \"cluster-operator\", \"tenant-admin\"]"
    

    注意:如前所述,某些字符必须转义以进行 JMESPath 处理。在这种情况下,反斜杠 ("") 字符用于转义双引号,这允许将 ConfigMap 数据存储为 JSON 数组。

    现在数组数据保存在 allowed-roles 键中,下面是一个示例 ClusterPolicy,其中包含一个规则,如果名为 role 的注解的值不在允许列表中,则该规则会阻止部署:

    apiVersion: kyverno.io/v1
    kind: ClusterPolicy
    metadata:
      name: cm-array-example
    spec:
      validationFailureAction: enforce
      background: false
      rules:
      - name: validate-role-annotation
        context:
          - name: roles-dictionary
            configMap:
              name: roles-dictionary
              namespace: default
        match:
          any:
          - resources:
              kinds:
              - Deployment
        validate:
          message: "The role {{ request.object.metadata.annotations.role }} is not in the allowed list of roles: {{ \"roles-dictionary\".data.\"allowed-roles\" }}."
          deny:
            conditions:
              any:
              - key: "{{ request.object.metadata.annotations.role }}"
                operator: NotIn
                value:  "{{ \"roles-dictionary\".data.\"allowed-roles\" }}"
    

    如果在我们之前定义的名为 roles-dictionary 的 ConfigMap 的数组中找不到注解 role,则此规则拒绝新建 Deployment 的请求。

    注意:您可能还会注意到,此示例在单个规则中使用了来自 AdmissionReview 和 ConfigMap 源的变量。这种组合可以证明在制定有用的策略方面非常强大和灵活。

    创建此示例 ClusterPolicy 后,尝试创建注解 role=super-user 的新 Deployment 并测试结果。

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: busybox
      annotations:
        role: super-user
      labels:
        app: busybox
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: busybox
      template:
        metadata:
          labels:
            app: busybox
        spec:
          containers:
          - image: busybox:1.28
            name: busybox
            command: ["sleep", "9999"]
    

    提交清单,看看 Kyverno 的反应。

    $ kubectl create -f deploy.yaml
    Error from server: error when creating "deploy.yaml": admission webhook "validate.kyverno.svc" denied the request:
    
    resource Deployment/default/busybox was blocked due to the following policies
    
    cm-array-example:
      validate-role-annotation: 'The role super-user is not in the allowed list of roles: ["cluster-admin", "cluster-operator", "tenant-admin"].'
    

    将注解 role 更改为 ConfigMap 中存在的值之一,例如 tenant-admin,允许创建 Deployment 资源。

    来自 Kubernetes API Server调用的变量

    Kubernetes 由允许查询和操作资源的声明性 API 提供支持。Kyverno 策略可以使用 Kubernetes API 来获取资源,甚至是资源类型的集合,以在策略中使用。此外,Kyverno 允许将 JMESPath(JSON 匹配表达式)应用于资源数据,以提取值并将其转换为易于在策略中使用的格式。

    Kyverno Kubernetes API 调用与 kubectl 和其他 API 客户端一样工作,并且可以使用现有工具进行测试。

    例如,下面是一个命令行,它使用 kubectl 获取命名空间中的 Pod 列表,然后将输出通过管道传输到 kyverno jp,以计算 Pod 的数量:

    kubectl get --raw /api/v1/namespaces/kyverno/pods | kyverno jp "items | length(@)"
    

    使用 kubectl get --raw 和 kyverno jp 命令来测试 API 调用。

    Kyverno 中相应的 API 调用定义如下。使用一个变量 {{request.namespace}} 来使用被操作对象的 Namespace,然后同样用 JMESPath 获取 Namespace 中 Pod 的数量,并以变量 podCount 的形式存储到 context 中。

    rules:
    - name: example-api-call
      context:
      - name: podCount
        apiCall:
          urlPath: "/api/v1/namespaces/{{request.namespace}}/pods"
          jmesPath: "items | length(@)"   
    

    URL路径

    Kubernetes API 使用 group 和 version来组织资源。例如,资源类型 Deployment 的 API Group 是 apps, version 是 v1。

    API 调用的 HTTP URL 路径基于group、version和资源类型,如下所示:

    • /apis/{GROUP}/{VERSION}/{RESOURCETYPE}:获取一个资源集合

    • /apis/{GROUP}/{VERSION}/{RESOURCETYPE}/{NAME}:获取一个资源

    对于命名空间级别的资源,要通过名称获取特定资源或获取命名空间中的所有资源,还必须提供命名空间名称,如下所示:

    • /apis/{GROUP}/{VERSION}/namespaces/{NAMESPACE}/{RESOURCETYPE}:获取命名空间中的一组资源集合

    • /apis/{GROUP}/{VERSION}/namespaces/{NAMESPACE}/{RESOURCETYPE}/{NAME}:获取命名空间中的一个资源

    对于历史资源,Kubernetes 核心 API 都在 /api/v1 下。例如,要查询所有命名空间资源,使用路径 /api/v1/namespaces。

    v1.22 的 API 参考文档中定义了 Kubernetes API 组,也可以通过如下所示的 kubectl api-resources 命令检索:

    $ kubectl api-resources
    NAME                              SHORTNAMES   APIGROUP                       NAMESPACED   KIND
    bindings                                                                      true         Binding
    componentstatuses                 cs                                          false        ComponentStatus
    configmaps                        cm                                          true         ConfigMap
    endpoints                         ep                                          true         Endpoints
    events                            ev                                          true         Event
    limitranges                       limits                                      true         LimitRange
    namespaces                        ns                                          false        Namespace
    nodes                             no                                          false        Node
    persistentvolumeclaims            pvc                                         true         PersistentVolumeClaim
    
    ...
    

    kubectl api-versions 命令打印出每个 API 组的可用版本。这是一个示例:

    $ kubectl api-versions
    admissionregistration.k8s.io/v1
    admissionregistration.k8s.io/v1beta1
    apiextensions.k8s.io/v1
    apiextensions.k8s.io/v1beta1
    apiregistration.k8s.io/v1
    apiregistration.k8s.io/v1beta1
    apps/v1
    authentication.k8s.io/v1
    authentication.k8s.io/v1beta1
    authorization.k8s.io/v1
    authorization.k8s.io/v1beta1
    autoscaling/v1
    autoscaling/v2beta1
    autoscaling/v2beta2
    batch/v1
    ...
    

    您可以结合使用这些命令来查找资源的 URL 路径,如下所示:

    要查找资源的 API 组和版本,请使用 kubectl api-resources 查找组,然后使用 kubectl api-versions 查找可用版本。

    本示例查找 Deployment 资源组,然后查询版本:

    kubectl api-resources | grep deploy
    

    API 组显示在输出的第三列中。然后,您可以使用组名来查找版本:

    kubectl api-versions | grep apps
    

    其输出将是 apps/v1。旧版本的 Kubernetes(1.18 之前)将显示 apps/v1beta2。

    处理集合

    请求资源集合的 URL 路径上的 HTTP GET 的 API 服务器响应将是具有项目(资源)列表的对象。

    这是一个获取所有命名空间资源的示例:

    kubectl get --raw /api/v1/namespaces | jq
    

    使用 jq 格式化输出以提高可读性。

    这将返回一个 NamespaceList 对象,其属性 items 包含命名空间列表:

    {
        "kind": "NamespaceList",
        "apiVersion": "v1",
        "metadata": {
          "selfLink": "/api/v1/namespaces",
          "resourceVersion": "2009258"
        },
        "items": [
          {
            "metadata": {
              "name": "default",
              "selfLink": "/api/v1/namespaces/default",
              "uid": "5011b5d5-abb7-4fef-93f9-8b5fa4b2eba9",
              "resourceVersion": "155",
              "creationTimestamp": "2021-01-19T20:20:37Z",
              "managedFields": [
                {
                  "manager": "kube-apiserver",
                  "operation": "Update",
                  "apiVersion": "v1",
                  "time": "2021-01-19T20:20:37Z",
                  "fieldsType": "FieldsV1",
                  "fieldsV1": {
                    "f:status": {
                      "f:phase": {}
                    }
                  }
                }
              ]
            },
            "spec": {
              "finalizers": [
                "kubernetes"
              ]
            },
            "status": {
              "phase": "Active"
            }
          },
          ...
    

    要在 JMESPath 中处理此数据,请引用items。这是一个示例,它在所有命名空间资源中提取了一些元数据字段:

    kubectl get --raw /api/v1/namespaces | kyverno jp "items[*].{name: metadata.name, creationTime: metadata.creationTimestamp}"
    

    这将生成一个新的 JSON 对象列表,其中包含属性名称和创建时间。

    [
      {
        "creationTimestamp": "2021-01-19T20:20:37Z",
        "name": "default"
      },
      {
        "creationTimestamp": "2021-01-19T20:20:36Z",
        "name": "kube-node-lease"
      },
      ...
    

    要在列表中查找项目,您可以使用 JMESPath 过滤器。例如,此命令将按名称匹配命名空间:

    kubectl get --raw /api/v1/namespaces | kyverno jp "items[?metadata.name == 'default'].{uid: metadata.uid, creationTimestamp: metadata.creationTimestamp}"
    

    除了通配符和过滤器之外,JMESPath 还有许多其他强大的、有用的功能。请务必阅读 JMESPath 教程并尝试此处的 Kyverno JMESPath 页面之外的交互式示例。

    示例策略:在命名空间中限制 LoadBalancer 类型的服务

    这是一个完整的示例策略,它将限制每个命名空间只能有一个 LoadBalancer 类型的服务。

    apiVersion: kyverno.io/v1
    kind: ClusterPolicy
    metadata:
      name: limits
    spec:
      validationFailureAction: enforce
      rules:
      - name: limit-lb-svc
        match:
          any:
          - resources:
              kinds:
              - Service
        context:
        - name: serviceCount
          apiCall:
            urlPath: "/api/v1/namespaces/{{ request.namespace }}/services"
            jmesPath: "items[?spec.type == 'LoadBalancer'] | length(@)"    
        preconditions:
          any:
          - key: "{{ request.operation }}"
            operator: Equals
            value: CREATE
        validate:
          message: "Only one LoadBalancer service is allowed per namespace"
          deny:
            conditions:
              any:
              - key: "{{ serviceCount }}"
                operator: GreaterThan
                value: 1
    

    此示例策略检索命名空间中的 Service列表,并将 LoadBalancer 类型的 Service 计数,并存储在名为 serviceCount 的变量中。deny 规则用于确保计数不能超过 1。

    来自 Image Registry 的变量

    通过使用 imageRegistry context 类型,context也可以使用 OCI 镜像上的元数据。通过使用此外部数据源,Kyverno 策略可以根据作为传入资源的一部分出现的容器镜像的详细信息做出决策。

    例如,如果您使用如下所示的 imageRegistry:

    context: 
    - name: imageData
      imageRegistry: 
        reference: "ghcr.io/kyverno/kyverno"
    

    输出 imageData 变量将具有如下结构:

    {
        "image":         "ghcr.io/kyverno/kyverno",
        "resolvedImage": "ghcr.io/kyverno/kyverno@sha256:17bfcdf276ce2cec0236e069f0ad6b3536c653c73dbeba59405334c0d3b51ecb",
        "registry":      "ghcr.io",
        "repository":    "kyverno/kyverno",
        "identifier":    "latest",
        "manifest":      manifest,
        "configData":    config,
    }
    

    注意

    imageData 代表了一个镜像在 registry 执行任何重定向并由 Kyverno 进行内部修改之后的“归一化”的视图(Kyverno 默认将一个空注册表设置为 docker.io 并将一个空标签设置为 latest)。最值得注意的是,这会影响托管在 Docker Hub 上的官方镜像。Docker Hub 上的官方镜像与其他镜像的区别在于它们的存储库以 library/ 为前缀,即使被拉取的镜像不包含它。例如,使用 python:slim 拉取 python 官方图像会导致设置 imageData 的以下字段:

    {
        "image":         "docker.io/python:slim",
        "resolvedImage": "index.docker.io/library/python@sha256:43705a7d3a22c5b954ed4bd8db073698522128cf2aaec07690a34aab59c65066",
        "registry":      "index.docker.io",
        "repository":    "library/python",
        "identifier":    "slim"
    }
    

    manifest 和 config 分别包含来自 crane manifest <image> 和 crane config <image> 的输出。

    例如,可以检查给定图像的labels、entrypoint、volumes、history、layers、etc 等。使用工具 crane,显示镜像 ghcr.io/kyverno/kyverno:latest 的配置信息:

    $ crane config ghcr.io/kyverno/kyverno:latest | jq
    {
      "architecture": "amd64",
      "config": {
        "User": "10001",
        "Env": [
          "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
        ],
        "Entrypoint": [
          "./kyverno"
        ],
        "WorkingDir": "/",
        "Labels": {
          "maintainer": "Kyverno"
        },
        "OnBuild": null
      },
      "created": "2022-02-04T08:57:38.818583756Z",
      "history": [
        {
          "created": "2022-02-04T08:57:38.454742161Z",
          "created_by": "LABEL maintainer=Kyverno",
          "comment": "buildkit.dockerfile.v0",
          "empty_layer": true
        },
        {
          "created": "2022-02-04T08:57:38.454742161Z",
          "created_by": "COPY /output/kyverno / # buildkit",
          "comment": "buildkit.dockerfile.v0"
        },
        {
          "created": "2022-02-04T08:57:38.802069102Z",
          "created_by": "COPY /etc/passwd /etc/passwd # buildkit",
          "comment": "buildkit.dockerfile.v0"
        },
        {
          "created": "2022-02-04T08:57:38.818583756Z",
          "created_by": "COPY /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ # buildkit",
          "comment": "buildkit.dockerfile.v0"
        },
        {
          "created": "2022-02-04T08:57:38.818583756Z",
          "created_by": "USER 10001",
          "comment": "buildkit.dockerfile.v0",
          "empty_layer": true
        },
        {
          "created": "2022-02-04T08:57:38.818583756Z",
          "created_by": "ENTRYPOINT [\"./kyverno\"]",
          "comment": "buildkit.dockerfile.v0",
          "empty_layer": true
        }
      ],
      "os": "linux",
      "rootfs": {
        "type": "layers",
        "diff_ids": [
          "sha256:180b308b8730567d2d06a342148e1e9d274c8db84113077cfd0104a7e68db646",
          "sha256:99187eab8264c714d0c260ae8b727c4d2bda3a9962635aaea67d04d0f8b0f466",
          "sha256:26d825f3d198779c4990007ae907ba21e7c7b6213a7eb78d908122e435ec9958"
        ]
      }
    }
    

    在上面的输出中,我们可以在 config.User 下看到运行这个容器的 Dockerfile 的 USER 声明是 10001。可以编写 Kyverno 策略来利用此信息并执行,例如,验证镜像的 USER 是非 root。

    apiVersion: kyverno.io/v1
    kind: ClusterPolicy
    metadata:
      name: imageref-demo
    spec:
      validationFailureAction: enforce
      rules:
      - name: no-root-images
        match:
          any:
          - resources:
              kinds:
              - Pod
        preconditions:
          all:
          - key: "{{request.operation}}"
            operator: NotEquals
            value: DELETE
        validate:
          message: "Images run as root are not allowed."  
          foreach:
          - list: "request.object.spec.containers"
            context: 
            - name: imageData
              imageRegistry: 
                reference: "{{ element.image }}"
            deny:
              conditions:
                any:
                  - key: "{{ imageData.configData.config.User || ''}}"
                    operator: Equals
                    value: ""
    

    在上面的示例策略中,已经编写了一个名为 imageData ,类型为 imageRegistry 的新 context。reference 键用于指示 Kyverno 存储镜像元数据的位置。其中 element 是 Pod 内的每个容器,因此 element.image 的容器镜像。然后可以在表达式中引用该值,例如在 deny.conditions 中通过键 {{ imageData.configData.config.User || ''}}。

    使用示例“bad” Pod 来测试违反此政策的情况,如下所示,Pod 被阻止。

    apiVersion: v1
    kind: Pod
    metadata:
      name: badpod
    spec:
      containers:
      - name: ubuntu
        image: ubuntu:latest
    
    $ kubectl apply -f bad.yaml 
    Error from server: error when creating "bad.yaml": admission webhook "validate.kyverno.svc-fail" denied the request: 
    
    resource Pod/default/badpod was blocked due to the following policies
    
    imageref-demo:
      no-root-images: 'validation failure: Images run as root are not allowed.'
    

    相比之下,当使用“good”的 Pod 时,例如上面提到的 Kyverno 容器镜像,该资源是允许的。

    apiVersion: v1
    kind: Pod
    metadata:
      name: goodpod
    spec:
      containers:
      - name: kyverno
        image: ghcr.io/kyverno/kyverno:latest
    
    $ kubectl apply -f good.yaml 
    pod/goodpod created
    

    imageRegistry 上下文类型还有一个名为 jmesPath 的可选属性,能够应用一个 JMESPath 表达式,事先存储为 context 的值,并写入 imageRegistry 的返回的内容中。例如,下面的代码片段通过叠加其清单报告的镜像的所有组成层,将镜像的总大小存储在名为 imageSize 的 context 中(通过 crane manifest 可以得到各层的信息)。然后可以在稍后的表达式中评估 context 变量的值。

    context: 
      - name: imageSize
        imageRegistry: 
          reference: "{{ element.image }}"
          # Note that we need to use `to_string` here to allow kyverno to treat it like a resource quantity of type memory
          # the total size of an image as calculated by docker is the total sum of its layer sizes
          jmesPath: "to_string(sum(manifest.layers[*].size))"
    

    要访问存储在私有 registry 中的图像,请参阅使用私有 registry

    有关使用 imageRegistry con text的更多示例,请参阅示例页面

    相关文章

      网友评论

        本文标题:Kyverno外部数据源

        本文链接:https://www.haomeiwen.com/subject/rzpscdtx.html