5

通过编写自定义内置函数的方式扩展 OPA/Rego 运行时

 2 years ago
source link: https://mozillazg.com/2021/12/opa-openpolicyagent-extend-opa-via-add-new-built-in-functions-to-rego-go-runtime.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.
neoserver,ios ssh client

前言

本文记录一下如何通过编写自定义内置函数的函数扩展 OPA/Rego 运行时,使得在编写 OPA/Rego 策略语言的时候 可以直接使用自定义的这个内置函数实现更复杂的策略需求。

编写自定义内置函数

我们将要编写的一个自定义内置函数名为 auth.get_user_info ,这个函数的作用是,获取自定义 uid 的用户信息:

user := auth.get_user_info(uid)

并且将使用 github.com/open-policy-agent/[email protected] 这个 opa 运行时库来编写这个自定义函数, 我们可以使用这个库提供的 rego.RegisterBuiltin1 函数来实现我们的需求:

package main

import (
        "fmt"
        "os"

        "github.com/open-policy-agent/opa/ast"
        "github.com/open-policy-agent/opa/cmd"
        "github.com/open-policy-agent/opa/rego"
        "github.com/open-policy-agent/opa/types"
)

type User struct {
        Name string
        Age int
}

func main() {
        users := map[string]User{
                "uid-1": {
                        Name: "tom",
                        Age:  25,
                },
                "uid-2": {
                        Name: "eric",
                        Age:  30,
                },
        }
        rego.RegisterBuiltin1(
                &rego.Function{
                        Name:    "auth.get_user_info",
                        Decl:    types.NewFunction(types.Args(types.S), types.A),
                        Memoize: true,
                },
                func(bctx rego.BuiltinContext, op1 *ast.Term) (*ast.Term, error) {
                        var uid string
                        if err := ast.As(op1.Value, &uid); err != nil {
                                return nil, err
                        }
                        user, ok := users[uid]
                        if !ok {
                                return nil, fmt.Errorf("user %s is not found", uid)
                        }
                        v, err := ast.InterfaceToValue(user)
                        if err != nil {
                                return nil, err
                        }
                        return ast.NewTerm(v), nil
                },
        )

        if err := cmd.RootCommand.Execute(); err != nil {
                fmt.Println(err)
                os.Exit(1)
        }
}
$ go build -o custom-built-functions

$ ./custom-built-functions run
OPA 0.35.0 (commit , built at )

Run 'help' to see a list of commands and check for updates.

> uid := "uid-1"
Rule 'uid' defined in package repl. Type 'show' to see rules.
> uid
"uid-1"
> user := auth.get_user_info(uid)
Rule 'user' defined in package repl. Type 'show' to see rules.
> user
{
  "Age": 25,
  "Name": "tom"
}
> user.Age
25
> user.Name
"tom"
>
Do you want to exit ([y]/n)? y

通过上面的测试可以看到,我们已经实现了自定义内置函数的需求,并且测试结果也符合预期结果。

通过上面的示例代码中的 rego.RegisterBuiltin1 这个函数名称可能已经猜到了,如果要定义接受两个参数 的函数的话应该使用 rego.RegisterBuiltin2 函数,同理还有 rego.RegisterBuiltin3 和 reg.RegisterBuiltin4 函数 可供使用。以及还有一个定义不定长参数的 rego.RegisterBuiltinDyn 可以用来满足跟复杂的函数需求。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK