searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

OPA自定义策略控制器实战

2023-06-30 04:00:27
31
0

一、创建自定义策略资源

1、创建CRD

创建CRD的第一步是通过官方文档做初步了解,地址:https://kubernetes.io/docs/tasks/access-kubernetes-api/custom-resources/custom-resource-definitions/#create-a-customresourcedefinition
登录可以执行kubectl命令的机器,创建OpaPolicy.yaml文件,内容如下:

apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  # metadata.name的内容是由"复数名.分组名"构成,如下,opapolicies是复数名,opacontroller.k8s.io是分组名
  name: opapolicies.opacontroller.k8s.io
  annotations: 
    "api-approved.kubernetes.io": "unapproved, experimental-only; please get an approval from Kubernetes API reviewers if you're trying to develop a CRD in the *.k8s.io or *.kubernetes.io groups"
spec:
  # 分组名,在REST API中也会用到的,格式是: /apis/分组名/CRD版本
  group: opacontroller.k8s.io
  # list of versions supported by this CustomResourceDefinition
  versions:
    - name: v1
      # 是否有效的开关.
      served: true
      # 只有一个版本能被标注为storage
      storage: true
      schema:
        openAPIV3Schema:
          properties:
            spec:
              x-kubernetes-preserve-unknown-fields: true
            status:
              type: object
              x-kubernetes-preserve-unknown-fields: true
          type: object
          x-kubernetes-preserve-unknown-fields: true
  # 范围是属于namespace的
  scope: Namespaced
  names:
    # 复数名
    plural: opapolicies
    # 单数名
    singular: opapolicy
    # 类型名
    kind: OpaPolicy
    # 简称,就像service的简称是svc
    shortNames:
    - op

2、创建OpaPolicy对象:

apiVersion: opacontroller.k8s.io/v1
kind: OpaPolicy
metadata:
  name: object-policy-bb
  namespace: forzz
spec:
  policy: |-
    package istio.authz
    import input.attributes.request.http as http_request
    allow {
        roles_for_user[r]
        required_roles[r]
    }
    roles_for_user[r] {
        r := user_roles[user_name][_]
    }
    required_roles[r] {
        perm := role_perms[r][_]
        perm.method = http_request.method
        perm.path = http_request.path
    }
    user_name = parsed {
        [_, encoded] := split(http_request.headers.authorization, " ")
        [parsed, _] := split(base64url.decode(encoded), ":")
    }
    user_roles = {
        "guest1": ["guest"],
        "admin1": ["admin"]
    }
    role_perms = {
        "guest": [
            {"method": "GET",  "path": "/productpage"},
        ],
        "admin": [
            {"method": "GET",  "path": "/productpage"},
            {"method": "GET",  "path": "/api/v1/products"},
        ],
    }
  workloadSelector:
    labels:
      version: v1

二、自动生成自定义策略资源Client API

接下来要做的事情就是编写API对象OpaPolicy相关的声明的定义代码,然后用代码生成工具结合这些代码,自动生成Client、Informet、WorkQueue相关的代码;

1、在$GOPATH/src/目录下创建一个文件夹opa_controller:
2、进入文件夹opa_controller,执行如下命令创建三层目录:

mkdir -p pkg/apis/opacontroller

3、在新建的opacontroller目录下创建文件register.go,内容如下:

package opacontroller

const (
        GroupName = "opacontroller.k8s.io"
        Version   = "v1"
)

4、在新建的opacontroller目录下创建名为v1的文件夹;

5、在新建的v1文件夹下创建文件doc.go,内容如下:

// +k8s:deepcopy-gen=package

// +groupName=opacontroller.k8s.io
package v1
上述代码中的两行注释,都是代码生成工具会用到的,一个是声明为整个v1包下的类型定义生成DeepCopy方法,另一个声明了这个包对应的API的组名,和CRD中的组名一致;
6、在v1文件夹下创建文件types.go,里面定义了OpaPolicy对象的具体内容:
package v1

import (
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// +genclient
// +genclient:noStatus
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

type OpaPolicy struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`
	Spec              OpaPolicySpec `json:"spec"`
}

// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type OpaPolicySpec struct {
        Policy string `json:"policy"`
        WorkloadSelector struct {
             Labels map[string]string  `json:"labels"`
        } `json:"workloadSelector"`
}

// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

// OpaPolicyList is a list of Student resources
type OpaPolicyList struct {
	metav1.TypeMeta `json:",inline"`
	metav1.ListMeta `json:"metadata"`

	Items []OpaPolicy `json:"items"`
}

从上述源码可见,OpaPolicy对象的内容已经被设定好,主要有Policy和WorkloadSelector 这两个字段,表示使用Rego语法表述的具体策略规则和设定该策略在此命名空间生效范围,因此创建OpaPolicy对象的时候内容就要和这里匹配了;

7、 在v1目录下创建register.go文件,此文件的作用是通过addKnownTypes方法使得client可以知道OpaPolicy类型的API对象:

8、至此,为自动生成代码做的准备工作已经完成了,执行以下命令,会先下载依赖包,再下载代码生成工具,再执行代码生成工作:

SCRIPT_ROOT=$(dirname "${BASH_SOURCE[0]}")/..
CODEGEN_PKG=${CODEGEN_PKG:-$(cd "${SCRIPT_ROOT}"; ls -d -1 ./vendor/k8s.io/code-generator 2>/dev/null || echo ../code-generator)}
bash "${CODEGEN_PKG}"/generate-groups.sh "deepcopy,client,lister,informer" \
opa-controller/pkg/client opa-controller/pkg/apis \
opacontroller:v1 \
--output-base "${SCRIPT_ROOT}" \
--go-header-file "${SCRIPT_ROOT}"/hack/boilerplate.go.txt \
9、如果代码写得没有问题,会看到以下输出:
[root@master opa-controller]# sh hack/update-codegen.sh
Generating deepcopy funcs
Generating clientset for opacontroller:v1 at opa-controller/pkg/client/clientset
Generating listers for opacontroller:v1 at opa-controller/pkg/client/listers
Generating informers for opacontroller:v1 at opa-controller/pkg/client/informers
 

10、此时再去$GOPATH/src/k8s_customize_controller目录下执行tree命令,可见已生成了很多内容:



三、自定义策略控制器

1、自定义策略控制器架构


2、自定义策略控制器的工作原理:

Reflector使用的是一种叫作 ListAndWatch 的方法,用于监听自定义策略资源,当策略资源发生变化时,触发相应的变更事件,如Added事件、Updated事件、Deleted事件,并将策略资源对象放到本地DeltaFIFO Queue中。

Informer 会不断地从这个 Delta FIFO Queue 里读取(Pop)增量。每拿到一个增量,Informer 就会判断这个增量里的事件类型,然后创建或者更新本地对象的缓存。这个缓存,在 Kubernetes 里一般被叫作 Store。比如,如果事件类型是 Added(添加对象),那么 Informer 就会通过一个叫作 Indexer 的库把这个增量里的 API 对象保存在本地缓存中,并为它创建索引。相反,如果增量的事件类型是 Deleted(删除对象),那么 Informer 就会从本地缓存中删除这个对象。;接着Informer 根据事件类型来触发事先注册好的 Event Handler触发回调函数,然后为 OpaPolicy注册了三个 Handler(AddFunc、UpdateFunc 和 DeleteFunc),分别对应 API 对象的“添加”“更新”和“删除”事件。而具体的处理操作,都是将该事件对应的 API 对象加入到工作队列中。需要注意的是,实际入队的并不是 API 对象本身,而是它们的 Key。而我们后面即将编写的控制循环,则会不断地从这个工作队列里拿到这些 Key,然后开始执行真正的控制逻辑。

工作线程处理业务逻辑,通过配置获取多集群信息,从而获取到注入策略引擎的POD容器,根据策略资源的命名空间及标签字段进行更细粒度控制写扩散策略到策略引擎存储。

策略通过策略管理控制台进行可视化管理,对策略进行增删改查,即对策略资源进行变更,通过自定义策略控制器,可及时下发到策略引擎。

业务进行请求时,策略引擎对策略进行加载,编译,生成策略决策树,并缓存策略决策结果,提供业务快速响应查询。

func NewController(
	kubeclientset kubernetes.Interface,
	opapolicyclientset clientset.Interface,
	opaPolicyInformer informers.OpaPolicyInformer) *Controller {

	utilruntime.Must(opascheme.AddToScheme(scheme.Scheme))
	glog.V(4).Info("Creating event broadcaster")
	eventBroadcaster := record.NewBroadcaster()
	eventBroadcaster.StartLogging(glog.Infof)
	eventBroadcaster.StartRecordingToSink(&typedcorev1.EventSinkImpl{Interface: kubeclientset.CoreV1().Events("")})
	recorder := eventBroadcaster.NewRecorder(scheme.Scheme, corev1.EventSource{Component: controllerAgentName})

	controller := &Controller{
		kubeclientset:    kubeclientset,
		opapolicyclientset: opapolicyclientset,
		opaPolicyLister:   opaPolicyInformer.Lister(),
		opaPolicySynced:   opaPolicyInformer.Informer().HasSynced,
		workqueue:        workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "OpaPolicies"),
		recorder:         recorder,
	}

	glog.Info("Setting up event handlers")
	// Set up an event handler for when Student resources change
	opaPolicyInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
		AddFunc: controller.enqueueOpaPolicy,
		UpdateFunc: func(old, new interface{}) {
			oldOpaPolicy := old.(*opacontrollerv1.OpaPolicy)
			newOpaPolicy := new.(*opacontrollerv1.OpaPolicy)
			if oldOpaPolicy.ResourceVersion == newOpaPolicy.ResourceVersion {
                //版本一致,就表示没有实际更新的操作,立即返回
				return
			}
			controller.enqueueOpaPolicyForDelete(old)
			controller.enqueueOpaPolicy(new)
		},
		DeleteFunc: controller.enqueueOpaPolicyForDelete,
	})

	return controller
}

小结
至此,controller的编码和验证就全部完成了,现在小结一下自定义controller开发的整个过程:

1、创建CRD(Custom Resource Definition),令k8s明白我们自定义的API对象;
2、编写代码,将CRD的情况写入对应的代码中,然后通过自动代码生成工具,将controller之外的informer,client等内容较为固定的代码通过工具生成;
3、编写controller,在里面判断实际情况是否达到了API对象的声明情况,如果未达到,就要进行实际业务处理,而这也是controller的通用做法;

0条评论
0 / 1000
5****m
2文章数
1粉丝数
5****m
2 文章 | 1 粉丝
5****m
2文章数
1粉丝数
5****m
2 文章 | 1 粉丝
原创

OPA自定义策略控制器实战

2023-06-30 04:00:27
31
0

一、创建自定义策略资源

1、创建CRD

创建CRD的第一步是通过官方文档做初步了解,地址:https://kubernetes.io/docs/tasks/access-kubernetes-api/custom-resources/custom-resource-definitions/#create-a-customresourcedefinition
登录可以执行kubectl命令的机器,创建OpaPolicy.yaml文件,内容如下:

apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  # metadata.name的内容是由"复数名.分组名"构成,如下,opapolicies是复数名,opacontroller.k8s.io是分组名
  name: opapolicies.opacontroller.k8s.io
  annotations: 
    "api-approved.kubernetes.io": "unapproved, experimental-only; please get an approval from Kubernetes API reviewers if you're trying to develop a CRD in the *.k8s.io or *.kubernetes.io groups"
spec:
  # 分组名,在REST API中也会用到的,格式是: /apis/分组名/CRD版本
  group: opacontroller.k8s.io
  # list of versions supported by this CustomResourceDefinition
  versions:
    - name: v1
      # 是否有效的开关.
      served: true
      # 只有一个版本能被标注为storage
      storage: true
      schema:
        openAPIV3Schema:
          properties:
            spec:
              x-kubernetes-preserve-unknown-fields: true
            status:
              type: object
              x-kubernetes-preserve-unknown-fields: true
          type: object
          x-kubernetes-preserve-unknown-fields: true
  # 范围是属于namespace的
  scope: Namespaced
  names:
    # 复数名
    plural: opapolicies
    # 单数名
    singular: opapolicy
    # 类型名
    kind: OpaPolicy
    # 简称,就像service的简称是svc
    shortNames:
    - op

2、创建OpaPolicy对象:

apiVersion: opacontroller.k8s.io/v1
kind: OpaPolicy
metadata:
  name: object-policy-bb
  namespace: forzz
spec:
  policy: |-
    package istio.authz
    import input.attributes.request.http as http_request
    allow {
        roles_for_user[r]
        required_roles[r]
    }
    roles_for_user[r] {
        r := user_roles[user_name][_]
    }
    required_roles[r] {
        perm := role_perms[r][_]
        perm.method = http_request.method
        perm.path = http_request.path
    }
    user_name = parsed {
        [_, encoded] := split(http_request.headers.authorization, " ")
        [parsed, _] := split(base64url.decode(encoded), ":")
    }
    user_roles = {
        "guest1": ["guest"],
        "admin1": ["admin"]
    }
    role_perms = {
        "guest": [
            {"method": "GET",  "path": "/productpage"},
        ],
        "admin": [
            {"method": "GET",  "path": "/productpage"},
            {"method": "GET",  "path": "/api/v1/products"},
        ],
    }
  workloadSelector:
    labels:
      version: v1

二、自动生成自定义策略资源Client API

接下来要做的事情就是编写API对象OpaPolicy相关的声明的定义代码,然后用代码生成工具结合这些代码,自动生成Client、Informet、WorkQueue相关的代码;

1、在$GOPATH/src/目录下创建一个文件夹opa_controller:
2、进入文件夹opa_controller,执行如下命令创建三层目录:

mkdir -p pkg/apis/opacontroller

3、在新建的opacontroller目录下创建文件register.go,内容如下:

package opacontroller

const (
        GroupName = "opacontroller.k8s.io"
        Version   = "v1"
)

4、在新建的opacontroller目录下创建名为v1的文件夹;

5、在新建的v1文件夹下创建文件doc.go,内容如下:

// +k8s:deepcopy-gen=package

// +groupName=opacontroller.k8s.io
package v1
上述代码中的两行注释,都是代码生成工具会用到的,一个是声明为整个v1包下的类型定义生成DeepCopy方法,另一个声明了这个包对应的API的组名,和CRD中的组名一致;
6、在v1文件夹下创建文件types.go,里面定义了OpaPolicy对象的具体内容:
package v1

import (
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// +genclient
// +genclient:noStatus
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

type OpaPolicy struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`
	Spec              OpaPolicySpec `json:"spec"`
}

// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type OpaPolicySpec struct {
        Policy string `json:"policy"`
        WorkloadSelector struct {
             Labels map[string]string  `json:"labels"`
        } `json:"workloadSelector"`
}

// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

// OpaPolicyList is a list of Student resources
type OpaPolicyList struct {
	metav1.TypeMeta `json:",inline"`
	metav1.ListMeta `json:"metadata"`

	Items []OpaPolicy `json:"items"`
}

从上述源码可见,OpaPolicy对象的内容已经被设定好,主要有Policy和WorkloadSelector 这两个字段,表示使用Rego语法表述的具体策略规则和设定该策略在此命名空间生效范围,因此创建OpaPolicy对象的时候内容就要和这里匹配了;

7、 在v1目录下创建register.go文件,此文件的作用是通过addKnownTypes方法使得client可以知道OpaPolicy类型的API对象:

8、至此,为自动生成代码做的准备工作已经完成了,执行以下命令,会先下载依赖包,再下载代码生成工具,再执行代码生成工作:

SCRIPT_ROOT=$(dirname "${BASH_SOURCE[0]}")/..
CODEGEN_PKG=${CODEGEN_PKG:-$(cd "${SCRIPT_ROOT}"; ls -d -1 ./vendor/k8s.io/code-generator 2>/dev/null || echo ../code-generator)}
bash "${CODEGEN_PKG}"/generate-groups.sh "deepcopy,client,lister,informer" \
opa-controller/pkg/client opa-controller/pkg/apis \
opacontroller:v1 \
--output-base "${SCRIPT_ROOT}" \
--go-header-file "${SCRIPT_ROOT}"/hack/boilerplate.go.txt \
9、如果代码写得没有问题,会看到以下输出:
[root@master opa-controller]# sh hack/update-codegen.sh
Generating deepcopy funcs
Generating clientset for opacontroller:v1 at opa-controller/pkg/client/clientset
Generating listers for opacontroller:v1 at opa-controller/pkg/client/listers
Generating informers for opacontroller:v1 at opa-controller/pkg/client/informers
 

10、此时再去$GOPATH/src/k8s_customize_controller目录下执行tree命令,可见已生成了很多内容:



三、自定义策略控制器

1、自定义策略控制器架构


2、自定义策略控制器的工作原理:

Reflector使用的是一种叫作 ListAndWatch 的方法,用于监听自定义策略资源,当策略资源发生变化时,触发相应的变更事件,如Added事件、Updated事件、Deleted事件,并将策略资源对象放到本地DeltaFIFO Queue中。

Informer 会不断地从这个 Delta FIFO Queue 里读取(Pop)增量。每拿到一个增量,Informer 就会判断这个增量里的事件类型,然后创建或者更新本地对象的缓存。这个缓存,在 Kubernetes 里一般被叫作 Store。比如,如果事件类型是 Added(添加对象),那么 Informer 就会通过一个叫作 Indexer 的库把这个增量里的 API 对象保存在本地缓存中,并为它创建索引。相反,如果增量的事件类型是 Deleted(删除对象),那么 Informer 就会从本地缓存中删除这个对象。;接着Informer 根据事件类型来触发事先注册好的 Event Handler触发回调函数,然后为 OpaPolicy注册了三个 Handler(AddFunc、UpdateFunc 和 DeleteFunc),分别对应 API 对象的“添加”“更新”和“删除”事件。而具体的处理操作,都是将该事件对应的 API 对象加入到工作队列中。需要注意的是,实际入队的并不是 API 对象本身,而是它们的 Key。而我们后面即将编写的控制循环,则会不断地从这个工作队列里拿到这些 Key,然后开始执行真正的控制逻辑。

工作线程处理业务逻辑,通过配置获取多集群信息,从而获取到注入策略引擎的POD容器,根据策略资源的命名空间及标签字段进行更细粒度控制写扩散策略到策略引擎存储。

策略通过策略管理控制台进行可视化管理,对策略进行增删改查,即对策略资源进行变更,通过自定义策略控制器,可及时下发到策略引擎。

业务进行请求时,策略引擎对策略进行加载,编译,生成策略决策树,并缓存策略决策结果,提供业务快速响应查询。

func NewController(
	kubeclientset kubernetes.Interface,
	opapolicyclientset clientset.Interface,
	opaPolicyInformer informers.OpaPolicyInformer) *Controller {

	utilruntime.Must(opascheme.AddToScheme(scheme.Scheme))
	glog.V(4).Info("Creating event broadcaster")
	eventBroadcaster := record.NewBroadcaster()
	eventBroadcaster.StartLogging(glog.Infof)
	eventBroadcaster.StartRecordingToSink(&typedcorev1.EventSinkImpl{Interface: kubeclientset.CoreV1().Events("")})
	recorder := eventBroadcaster.NewRecorder(scheme.Scheme, corev1.EventSource{Component: controllerAgentName})

	controller := &Controller{
		kubeclientset:    kubeclientset,
		opapolicyclientset: opapolicyclientset,
		opaPolicyLister:   opaPolicyInformer.Lister(),
		opaPolicySynced:   opaPolicyInformer.Informer().HasSynced,
		workqueue:        workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "OpaPolicies"),
		recorder:         recorder,
	}

	glog.Info("Setting up event handlers")
	// Set up an event handler for when Student resources change
	opaPolicyInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
		AddFunc: controller.enqueueOpaPolicy,
		UpdateFunc: func(old, new interface{}) {
			oldOpaPolicy := old.(*opacontrollerv1.OpaPolicy)
			newOpaPolicy := new.(*opacontrollerv1.OpaPolicy)
			if oldOpaPolicy.ResourceVersion == newOpaPolicy.ResourceVersion {
                //版本一致,就表示没有实际更新的操作,立即返回
				return
			}
			controller.enqueueOpaPolicyForDelete(old)
			controller.enqueueOpaPolicy(new)
		},
		DeleteFunc: controller.enqueueOpaPolicyForDelete,
	})

	return controller
}

小结
至此,controller的编码和验证就全部完成了,现在小结一下自定义controller开发的整个过程:

1、创建CRD(Custom Resource Definition),令k8s明白我们自定义的API对象;
2、编写代码,将CRD的情况写入对应的代码中,然后通过自动代码生成工具,将controller之外的informer,client等内容较为固定的代码通过工具生成;
3、编写controller,在里面判断实际情况是否达到了API对象的声明情况,如果未达到,就要进行实际业务处理,而这也是controller的通用做法;

文章来自个人专栏
OPA
2 文章 | 1 订阅
0条评论
0 / 1000
请输入你的评论
2
2