目 录CONTENT

文章目录

k8s | OPA

如风
2023-08-14 / 0 评论 / 0 点赞 / 32 阅读 / 4,395 字

k8s | OPA

任务

任务:特权容器,系统目录,不调度到 master,不同命名空间生效—>形成 调研文档,OPA的功能和具体实现,怎么使用,规则如何写,可以运用到哪些场景,具体的测试

根据不同的命名空间(名字) 来限制挂载的 白名单。比如说,特权用户 放开所有的挂载目录,普通的Namespace 只能挂载某些目录(白名单), 而不能 挂载到一些 系统的目录(黑名单)

/sys
/proc
/data 只能读 readonly

/ tmp 可读可写

其他所有的目录都不能挂载

什么是 webhook

当请求进入Kubernetes API时,它会通过一系列步骤进行处理,然后才会执行。

  1. 请求进行身份验证和授权。

  2. 请求由一系列特殊的Kubernetes Webhook集合(称为"准入控制器")处理,这些Webhook可以对请求中的对象进行突变、修改和验证。

  3. 请求被持久化到etcd中以便执行。

Kubernetes准入控制器是集群的中间件。它们控制可以进入集群的内容。准入控制器管理请求过多资源的部署,强制执行Pod安全策略,甚至阻止部署存在漏洞的镜像。

什么是 OPA?

Open Policy Agent (OPA)是一个开源的通用策略引擎,可在整个堆栈上实现统一的、上下文感知的策略执行。

OPA使用一种叫做“策略”的东西来工作。这些策略是规则的集合,告诉Kubernetes如何处理不同情况。就像在组织中有规定和政策来指导员工行为一样,Kubernetes集群中也需要一些规则来确保运行的容器和应用程序是安全的、合规的。

OPA 解决了哪些问题

OPA通过评估查询输入以及针对策略和数据来生成策略决策

  • 哪些用户可以访问哪些资源;
  • 允许哪些子网出口流量;
  • 必须将工作负载部署到哪个群集;
  • 可以从哪些注册表二进制文件下载;
  • 容器可以执行哪些OS功能;
  • 可以在一天的哪个时间访问系统;
  • 需要策略控制用户是否可登陆服务器或者做一些操作;
  • 需要策略控制哪些项目/哪些组件可进行部署;
  • 需要策略控制如何访问数据库;
  • 需要策略控制哪些资源可部署到 Kubernetes 中;

什么是 OPA Gatekeeper

github: https://github.com/open-policy-agent/gatekeeper

官方文档https://open-policy-agent.github.io/gatekeeper/website/docs/howto

OPA Gatekeeper是Open Policy Agent的一个子项目,专门设计用于将OPA实现到Kubernetes集群中。Gatekeeper是一个验证和突变Webhook,通过由Open Policy Agent执行的基于CRD的策略强制执行。

为了确保这些应用程序在kubernetes集群中的安全性、合规性和一致性,你可能希望强制实施一些规则。这就是 OPA Gatekeeper 出现的背景。

OPA Gatekeeper 允许定义各种策略,这些策略可以规定哪些类型的资源是允许的,哪些是禁止的。这些策略可以涵盖诸如容器镜像来源、资源配额、网络策略等方面。一旦策略被定义,OPA Gatekeeper 就会在集群中监控资源的创建和更新,并根据策略进行验证。

OPA Gatekeeper 工作原理

Kubernetes 通过 准入控制 Webhook 将策略决策与 API Server 的内部工作解耦,这些 Webhook 在创建、更新或删除资源时执行。Gatekeeper 是一个验证和变异 Webhook,它执行由 Open Policy Agent 执行的基于 CRD 的策略。

Gatekeeper 充当 Kubernetes API Server 和 OPA 之间的桥梁。在实践中,这意味着 Gatekeeper 检查进入集群的每个请求,以查看它是否违反任何预定义的策略。如果是,则 apiserver 将拒绝它。

img

OPA Gatekeeper Library 库

github:https://github.com/open-policy-agent/gatekeeper-library

该库由两个主要部分组成:验证和突变。

  • 验证:网守可以根据网守验证策略(如库中定义的策略)验证集群中的资源。这些策略被定义为约束模板和约束—ConstraintTemplates

  • 变异:网守可以根据网守变异策略(如库中定义的策略)变异集群中的资源。突变策略只是示例,在应用之前,应该对它们进行定制以满足您的需求。

当涉及到 Kubernetes 集群的安全性和策略时,我们希望确保部署的应用程序和资源都遵循一些预定的规则和条件,以确保系统的健康和安全。OPA(Open Policy Agent)是一个用于定义和强制执行这些规则的工具,而 OPA Gatekeeper Library 则是 OPA 的一个插件,用于在 Kubernetes 集群中实施这些规则。

可以把 OPA Gatekeeper Library 想象成一个安全守门员,它会对您的 Kubernetes 集群中的资源进行检查,确保它们符合预设的安全策略和要求。这个 "守门员" 在部署过程中会查看每个资源(如部署、服务、配置等),并根据事先设定的规则来判断是否符合规范。

这个库的工作方式类似于一个 "检查清单",您可以将您希望在集群中执行的各种规则列出来。例如,您可以定义规则,要求每个 Deployment 必须使用特定标签,或者每个 Service 必须暴露的端口必须在特定范围内。当您部署新的资源时,这个库会自动检查资源是否符合您的规则。如果资源不符合规则,部署就会被拒绝,从而帮助确保集群的安全性和一致性。

总之,OPA Gatekeeper Library 是一个帮助您管理 Kubernetes 集群中资源的工具,通过强制执行预定义的规则,确保您的集群中的资源都遵循了您的安全和策略要求,从而提高了集群的可靠性和安全性。

安装 helm

wget https://get.helm.sh/helm-v3.5.4-linux-amd64.tar.gz
tar xf helm-v3.5.4-linux-amd64.tar.gz
cp linux-amd64/helm /usr/local/bin/helm
chmod +x /usr/local/bin/helm

安装 OPA gatekeeper

helm repo add gatekeeper https://open-policy-agent.github.io/gatekeeper/charts

helm install gatekeeper/gatekeeper --name-template=gatekeeper --namespace gatekeeper-system --create-namespace

image-20230816140055043

安装之后,会自动创建一个名字为gatekeeper-system的命名空间,在此命名空间里会创建一系列的pod,确认这些pod的状态都是运行的。

kubectl get pods -n gatekeeper-system 

image-20230816140122832

gatekeeper会创建一系列的资源类型。

kubectl get crd | grep gatekeep

image-20230816140148643

Rego 语法

OPA策略以称为Rego的高级声明性语言表示

官方文档

  1. 资源基本属性:
    • input.review.object.metadata.name:获取资源的名称。
    • input.review.object.metadata.namespace:获取资源所在的命名空间。
    • input.review.object.kind:获取资源的类型(例如,Pod、Deployment等)。
  2. 容器相关属性:
    • input.review.object.spec.containers[_].name:获取容器的名称。
    • input.review.object.spec.containers[_].image:获取容器的镜像。
    • input.review.object.spec.containers[_].ports[_].containerPort:获取容器端口。
  3. 标签和注释:
    • input.review.object.metadata.labels["label_key"]:获取特定标签的值。
    • input.review.object.metadata.annotations["annotation_key"]:获取特定注释的值。
  4. 其他资源引用:
    • data.<namespace>.<resource>[<index>]:获取其他资源的属性,比如 data.k8s.pods["my-pod"].spec.containers[_].image
  5. 数组和循环:
    • count(array):计算数组中元素的数量。
    • array[_]:遍历数组中的每个元素。
    • array[index]:获取数组中特定索引的元素。
  6. 字符串操作:
    • startswith(str, prefix):检查字符串是否以指定前缀开始。
    • endswith(str, suffix):检查字符串是否以指定后缀结束。
    • contains(str, substr):检查字符串是否包含指定子串。
  7. 逻辑和条件:
    • ==, !=, <, >, <=, >=:比较运算符。
    • not, and, or:逻辑运算符。
  8. 自定义函数:
    • 使用 func 关键字可以定义自定义函数,可以在策略中调用这些函数。

实例

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])
        }

名为 violation 的规则

  • 首先,它创建了两个集合,provided 包含已有的标签,required 包含所需的标签。
  • 然后,通过计算 required 集合与 provided 集合的差集,得到了缺失的标签集合 missing
  • 接着,它检查是否存在缺失的标签,如果存在,则返回一个包含提示消息和详细信息的 JSON 对象,其中详细信息包括缺失的标签。
  • 如果没有缺失的标签,那么就没有违反规则,不会返回违反信息。

1、创建资源对象必须指定标签

创建模板

vi k8srequiredlabels-template.yaml

apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
  name: k8srequiredlabels
spec:
  crd:
    spec:
      names:
        kind: K8sRequiredLabels
      validation:
        # Schema for the `parameters` field
        openAPIV3Schema:
          type: object
          properties:
            labels:
              type: array
              items:
                type: 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])
        }
kubectl apply -f k8srequiredlabels-template.yaml

根据模板创建 contraints

vi k8srequiredlabels-contraints.yaml

apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredLabels
metadata:
  name: ns-must-have-gk
spec:
  match:
    kinds:
      - apiGroups: [""]
        kinds: ["Namespace"]
  parameters:
    labels: ["gatekeeper"]
kubectl apply -f k8srequiredlabels-contraints.yaml

创建 Namespace 进行测试

kubectl create ns test

出现错误

[root@hz-gd-k8s-ipstest-master-199-154-119 test]# kubectl create ns test
Error from server (Forbidden): admission webhook "validation.gatekeeper.sh" denied the request: [ns-must-have-gk] you must provide labels: {"gatekeeper"}

加上标签 gatekeeper

vi test-namespace.yaml

apiVersion: v1
kind: Namespace
metadata:
  name: test
  labels:
    gatekeeper: enabled
kubectl apply -f test-namespace.yaml

创建成功!

image-20230816153900438

2、禁止挂载到系统目录

定义策略 disallow-mounting-system.rego

白名单,限定
用于定义策略规则以确保挂载路径不包含系统敏感目录。例如,阻止挂载到 /etc/var 目录下。

package k8sdenyvolumes

violation[{"msg": msg}] {
  container := input.review.object.spec.template.spec.containers[_]
  volumeMount := container.volumeMounts[_]
  contains(volumeMount.mountPath, "/etc")  # 阻止挂载到 /etc
  msg := "Mounting to system directory /etc is disallowed"
}

violation[{"msg": msg}] {
  container := input.review.object.spec.template.spec.containers[_]
  volumeMount := container.volumeMounts[_]
  contains(volumeMount.mountPath, "/var")  # 阻止挂载到 /var
  msg := "Mounting to system directory /var is disallowed"
}

contains(volumeMount.mountPath, "/etc"):这行代码使用 contains 函数检查 volumeMountmountPath 是否包含 “/etc” 字符串,从而判断是否尝试挂载到 /etc 目录。

创建 ConstraintTemplate 创建一个 ConstraintTemplate,以在策略中引用。创建一个文件 disallow-mounting-system-template.yaml

apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
  name: k8sdenyvolumes
spec:
  crd:
    spec:
      names:
        kind: K8sDenyVolumes
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package k8sdenyvolumes

        violation[{"msg": msg}] {
          container := input.review.object.spec.template.spec.containers[_]
          volumeMount := container.volumeMounts[_]
          contains(volumeMount.mountPath, "/etc")  # 阻止挂载到 /etc
          msg := "Mounting to system directory /etc is disallowed"
        }

        violation[{"msg": msg}] {
          container := input.review.object.spec.template.spec.containers[_]
          volumeMount := container.volumeMounts[_]
          contains(volumeMount.mountPath, "/var")  # 阻止挂载到 /var
          msg := "Mounting to system directory /var is disallowed"
        }

创建 ConstraintTemplate

kubectl apply -f disallow-mounting-system-template.yaml

创建 Constraint disallow-mounting-system-constraint.yaml

apiVersion: constraints.gatekeeper.sh/v1beta1
kind: k8sdenyvolumes
metadata:
  name: disallow-mounting-system
spec:
  match:
    kinds:
      - apiGroups: ["apps"]
        kinds: ["Deployment"]
  parameters:
    allowedPrefixes:
      - "/data"  # 允许挂载到 /data 目录

创建 Constraint

kubectl apply -f disallow-mounting-system-constraint.yaml

部署 Constraint

kubectl apply -f test-deployment.yaml

创建一个 deployment 来测试,让其挂载到 /etc 目录

apiVersion: apps/v1
kind: Deployment
metadata:
  name: test-deployment
spec:
  replicas: 1
  selector:
    matchLabels:
      app: test-app
  template:
    metadata:
      labels:
        app: test-app
    spec:
      containers:
        - name: test-container
          image: nginx
          volumeMounts:
            - name: etc-volume
              mountPath: /etc
      volumes:
        - name: etc-volume
          emptyDir: {}

创建出现问题,符合预期

[root@hz-bd-k8s-release-master-safetest-199-159-12 system]# kubectl apply -f test.yaml 
Error from server (Forbidden): error when creating "test.yaml": admission webhook "validation.gatekeeper.sh" denied the request: [disallow-mounting-system] Mounting to system directory /etc is disallowed

监控和管理:

# 检查 Constraint 的状态
kubectl get k8sdenyvolumes disallow-mounting-system -o yaml

# 检查 Deployment 的状态
kubectl get deployment test-deployment -o yaml

3、禁止使用特定的镜像

自定义一个类型为blacklistimagesCRD资源类型,用于禁止以hub.c.163.com开头的镜像。

创建Constraint Templates

vi gatekeeper-blk-type.yaml

apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
  name: blacklistimages
spec:
  crd:
    spec:
      names:
        kind: BlacklistImages
  targets:
  - rego: |
      package k8strustedimages

      images {
        image := input.review.object.spec.containers[_].image
        not startswith(image, "hub.c.163.com/")
      }

      violation[{"msg": msg}] {
        not images
        msg := "不可用的镜像!"
      }
    target: admission.k8s.gatekeeper.sh

创建

kubectl apply -f gatekeeper-blk-type.yaml 

查看资源

kubectl get blacklistimages

这里提示"No resources found",说明已经存在资源类型blacklistimages了,只是这个类型下面还没创建任何资源。
下面创建一个名字为pod-blk-imgBlacklistImages

vi gatekeeper-blacklist.yaml
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: BlacklistImages
metadata:
  generation: 1
  managedFields:
  name: pod-blk-img
  resourceVersion: "14449" 
spec:
  match:
    kinds:
    - apiGroups:
      - ""
      kinds:
      - Pod

创建

kubectl apply -f gatekeeper-blacklist.yaml 

创建一个 pod 来测试

test.yaml

apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: null
  labels:
    run: pod1
  name: pod1
spec:
  terminationGracePeriodSeconds: 0
  containers:
  - image: hub.c.163.com/library/nginx
    imagePullPolicy: IfNotPresent
    name: pod1
    resources: {}
  dnsPolicy: ClusterFirst
  restartPolicy: Always
status: {}
kubectl apply -f pod1.yaml 

提示错误

Error from server (Forbidden): error when creating "test.yaml": admission webhook "validation.gatekeeper.sh" denied the request: [pod-blk-img] 不可用的镜像!

image-20230814132659778

4、禁止创建LB类型的svc

apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
  name: lbtypesvcnotallowed
spec:
  crd:
    spec:
      names:
        kind: LBTypeSvcNotAllowed
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package kubernetes.admission
        violation[{"msg": msg}] {
                    input.review.kind.kind = "Service"
                    input.review.operation = "CREATE"
                    input.review.object.spec.type = "LoadBalancer"
                    msg := "不允许创建LB类型的service!"
        }
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: LBTypeSvcNotAllowed
metadata:
  name: deny-create-lb-type-svc
spec:
  match:
    kinds:
      - apiGroups: [""]
        kinds: ["Service"]
    namespaces:
      - "default"
apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: null
  labels:
    run: pod1
  name: pod1
spec:
  terminationGracePeriodSeconds: 0
  containers:
  - image: hub.c.163.com/library/nginx
    imagePullPolicy: IfNotPresent
    name: pod1
    resources: {}
  dnsPolicy: ClusterFirst
  restartPolicy: Always
status: {}
kubectl expose --name=svc1 pod pod1 --port=80 --type=LoadBalancer

Task

apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
  name: namespacemountpolicy
spec:
  crd:
    spec:
      names:
        kind: NamespaceMountPolicy
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package namespace_mount_policy

        violation[{"msg": msg}] {
            container := input.review.object.spec.template.spec.containers[_]
            volumeMount := container.volumeMounts[_]
            namespace := input.review.object.metadata.namespace

            # 在 kube-system 命名空间中允许挂载到 /etc
            namespace == "kube-system"
            volumeMount.mountPath == "/etc"
            msg := "Container in kube-system namespace is allowed to mount /etc"
        }

        violation[{"msg": msg}] {
            container := input.review.object.spec.template.spec.containers[_]
            volumeMount := container.volumeMounts[_]
            namespace := input.review.object.metadata.namespace

            # 在 default 命名空间中不允许挂载到 /etc
            namespace == "default"
            # volumeMount.mountPath == "/etc"
            contains(volumeMount.mountPath, "/etc")  # 阻止挂载到 /etc
            msg := "Container in default namespace cannot mount /etc"
        }

        violation[{"msg": msg}] {
            container := input.review.object.spec.template.spec.containers[_]
            volumeMount := container.volumeMounts[_]
            namespace := input.review.object.metadata.namespace

            # 在 default 命名空间中只允许挂载到 /data
            namespace == "default"
            volumeMount.mountPath != "/data"
            msg := "Container in default namespace is allowed to mount only /data"
        }


apiVersion: constraints.gatekeeper.sh/v1beta1
kind: NamespaceMountPolicy
metadata:
  name: disallow-master-mount
spec:
  match:
    kinds:
      - apiGroups: ["apps"]
        kinds: ["Deployment"]
  parameters:
    allowedPrefixes:
      - "/data"  # 允许挂载到 /data 目录
    # 这里可以添加你的参数,如果有的话
    # 例如 labels: ["gatekeeper"]
    # 例如 allowedPaths: ["/data"]

不调度到 master 节点

创建 constraint template

vi disallow-master-scheduling-template.yaml

apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
  name: disallowmasterscheduling
spec:
  crd:
    spec:
      names:
        kind: DisallowMasterScheduling
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package k8sdenydeployment

        violation[{"msg": msg}] {
          node := input.review.object.spec.template.spec.nodeSelector
          contains(disallowed_labels, node)
          err := "master erro"
          msg := sprintf("Deployment scheduling on master nodes is disallowed,%v", err)
        }

kubectl apply -f disallow-master-scheduling-template.yaml

创建模板的时候报错,应该是语法问题,但是没有找到具体的解决方法

创建一个具体的策略实例

vi disallow-master-scheduling-constraint.yaml

apiVersion: constraints.gatekeeper.sh/v1beta1
kind: DisallowMasterScheduling
metadata:
  name: disallow-master-scheduling-constraint
spec:
  match:
    kinds:
      - apiGroups: ["apps"]
        kinds: ["Deployment"]
  parameters:
    enforcementAction: deny

kubectl apply -f disallow-master-scheduling-constraint.yaml

创建一个 deployment 来测试

vi test-deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: test-deployment
spec:
  replicas: 2
  selector:
    matchLabels:
      app: test
  template:
    metadata:
      labels:
        app: test
      annotations:
        prometheus.io/scrape: "true"  # An example annotation
    spec:
      containers:
        - name: nginx
          image: nginx:latest
          ports:
            - containerPort: 80

kubectl apply -f test-deployment.yaml
package deployment-mount-policy

import data.k8s
import data.mount_policy

violation[msg] {
    container := input.review.object.spec.template.spec.containers[_]
    volumeMount := container.volumeMounts[_]
    
    not allowed_mounts
    allowed_mounts[_].mountPath == volumeMount.mountPath
    not allowed_mounts[_].readonly
    allowed_mounts[_].mountPath == "/tmp"
    not volumeMount.readOnly
    
    msg := sprintf("Mounting at path %v with disallowed options", [volumeMount.mountPath])
}

allowed_mounts[mount] {
    namespace := input.review.object.metadata.namespace
    mount := mount_policy.allowed_mounts[namespace][_]
}

not allowed_mounts {
    namespace := input.review.object.metadata.namespace
    mount := mount_policy.disallowed_mounts[namespace][_]
    mount_path := mount.mountPath
    not volumeMount.mountPath == mount_path
}

常用指令

查看所有的 constrainttemplate

kubectl get constrainttemplate
apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
  name: namespacemountpolicy
spec:
  crd:
    spec:
      names:
        kind: NamespaceMountPolicy
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package namespace_mount_policy

        special_namespaces = {
            "kube-system",
        }

        allowed_mounts = {
            "data",
        }

        blocked_mounts = {
            "etc",
            "var",
        }

        violation[{"msg": msg}] {
          input.review.object.kind == "Pod"
          container := input.review.object.spec.containers[_]
          volume := container.volumeMounts[_]
          namespace := input.review.object.metadata.namespace
          
          not allow_mount[namespace]
          allow_mount[namespace]
          volume.mountPath == allowed_mount
          not volume.mountPath == blocked_mounts
          msg := sprintf("Namespace %v has an invalid mount policy", [namespace])
        }

        allow_mount[namespace] {
          namespace == special_namespaces[_]
        }

        allow_mount[namespace] {
          namespace != special_namespaces[_]
          volume.mountPath == allowed_mounts[_]
          not volume.mountPath == blocked_mounts[_]
        }


apiVersion: constraints.gatekeeper.sh/v1beta1
kind: NamespaceMountPolicy
metadata:
  name: enforce-namespace-mount-policy
spec:
  match:
    kinds:
      - apiGroups: [""]
        kinds: ["Pod"]
  parameters:
    enforcementAction: deny


0

评论区