regobrick

package module
v0.5.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 8, 2025 License: Apache-2.0 Imports: 7 Imported by: 0

README

RegoBrick

RegoBrick provides a straightforward way to parse and transform Rego modules without modifying the OPA engine. It applies certain transformations based on special import markers (for example, import data.regobrick.default_false) and also offers convenient helpers for custom builtins and Go↔Rego value conversion.

Overview

  • Default False
    If your Rego module imports data.regobrick.default_false, RegoBrick will automatically insert a default rule that evaluates to false for any “if” or boolean rules. This helps ensure you don’t forget to explicitly set them to false when not satisfied.

  • Custom Builtins
    Easily register builtins with typed arguments and return values. RegoBrick converts Rego AST terms to Go types and back, so you can write builtins in Go with minimal boilerplate.

  • Rego ↔ Go Conversion
    The convert package allows you to map Rego types (e.g. strings, numbers, arrays, objects) to typed Go structs, decimals, time.Time, etc., and vice versa.

  • Parse & Transform
    The ParseModule function (and other internal logic) reads a Rego module, looks for any RegoBrick import markers, and applies the corresponding AST transformations. A higher-level function, regobrick.New, provides a convenient way to load multiple modules, apply transforms, and inject them into OPA.

Installation

go get github.com/sky1core/regobrick

Make sure you also have OPA in your go.mod if you plan to work with the Rego engine.

Usage

Below is an example of how to use RegoBrick with decimal input data.
We create a decimal value from a string to avoid floating-point precision issues, then pass it to RegoBrick as input.

By including import data.regobrick.default_false in your policy, RegoBrick automatically inserts a default rule (for example, default allow = false), ensuring that if the condition isn’t met, the rule defaults to false.

package main

import (
    "context"
    "fmt"

    "github.com/shopspring/decimal"
    "github.com/open-policy-agent/opa/v1/rego"
    "github.com/sky1core/regobrick"
)

func main() {
    ctx := context.Background()

    // Create a decimal value from string to avoid floating-point issues.
    amount, err := decimal.NewFromString("123.45")
    if err != nil {
        panic(err)
    }

    // Example policy for the "sub" package
    subPolicy := `
        package sub

        some_rule {
            input.amount == 123.45
        }
    `
    // Example policy for the "main" package
    mainPolicy := `
        package example

        import data.regobrick.default_false

        allow if {
            input.user == "admin"
        }
    `

    // Initialize a rego.Rego instance, passing in multiple modules and decimal input.
    r, err := regobrick.New(
        regobrick.Module("sub.rego", subPolicy, []string{"data.some.pkg"}),
        regobrick.Module("main.rego", mainPolicy, []string{"data.mycompany.util"}),
        regobrick.Input(map[string]interface{}{
            "user":   "admin",
            "amount": amount,
        }),
    ).Rego(
        rego.Query("data.example.allow"),
    )
    if err != nil {
        panic(err)
    }

    // Prepare the query for evaluation.
    query, err := r.PrepareForEval(ctx)
    if err != nil {
        panic(err)
    }

    // Evaluate the query. The input is already set via regobrick.Input().
    rs, err := query.Eval(ctx)
    if err != nil {
        panic(err)
    }

    // The result of 'data.example.allow' is in rs.
    // Because 'allow if ...' is accompanied by 'default allow = false',
    // if the condition is not met, it defaults to false.
    fmt.Println("Result:", rs)
}

Writing Custom Builtins

You can register a custom function that OPA calls within your policies. RegoBrick provides helper functions (like RegisterBuiltin1, RegisterBuiltin2, etc.) for builtins that accept typed Go arguments and return typed Go values.

package main

import (
    "context"
    "github.com/open-policy-agent/opa/v1/rego"
    "github.com/sky1core/regobrick"
)

// Example builtin that checks if a user is "admin"
func isAdmin(ctx rego.BuiltinContext, user string) (bool, error) {
    return user == "admin", nil
}

func main() {
    // RegisterBuiltin1[T1, R] has this signature:
    //   func RegisterBuiltin1[T1 any, R any](
    //       name string,
    //       fn func(rego.BuiltinContext, T1) (R, error),
    //       opts ...BuiltinRegisterOption,
    //   )
    //
    // So we pass:
    //   1) The builtin name ("is_admin")
    //   2) Our Go function (isAdmin)
    //   3) Any number of BuiltinRegisterOption values, such as categories or nondeterminism.

    regobrick.RegisterBuiltin1[string, bool](
        "is_admin",
        isAdmin,
        // We can set the categories (used in FilterCapabilities) or nondeterministic flag, etc.
        regobrick.WithCategories("my_custom_category"),
        // regobrick.WithNondeterministic() // If your builtin is nondeterministic
    )

    // Then in Rego, you can write:
    //    is_admin(input.user) => returns true if user == "admin"

    // ...
}

RegoBrick automatically converts the Rego argument to a Go string and converts the returned bool back to a Rego boolean. For more complex use cases, you can define builtins with multiple arguments, different Go types (e.g., []string, custom structs), and so on.

Filtering Builtins with FilterCapabilities

If you want to restrict which builtins are allowed when evaluating a policy, you can use the FilterCapabilities function to include or exclude builtins by name and category. This gives you a way to lock down OPA so that only certain operations are permitted.

package main

import (
    "context"
    "fmt"

    "github.com/open-policy-agent/opa/v1/rego"
    "github.com/sky1core/regobrick"
)

func main() {
    // Suppose you want to allow only a small subset of builtins:
    // by specific names or categories.
    allowedNames := []string{"is_admin", "concat"}          // e.g., our custom builtin, plus a standard OPA builtin
    allowedCats := []string{"my_custom_category", "strings"} // categories to allow

    // FilterCapabilities returns an *ast.Capabilities object
    // that includes only the builtins matching the allowed names/infixes/categories.
    caps := regobrick.FilterCapabilities(allowedNames, allowedCats)

    // Now you can build a Rego query with these restricted capabilities:
    ctx := context.Background()
    query, err := rego.New(
        rego.Query("data.example.allow"),
        rego.Capabilities(caps),
        // Possibly other Rego options...
    ).PrepareForEval(ctx)
    if err != nil {
        panic(err)
    }

    // Evaluate the query as usual:
    rs, err := query.Eval(ctx)
    if err != nil {
        panic(err)
    }

    fmt.Println("Query result:", rs)
}

In the snippet above, builtins that do not appear in either allowedNames or allowedCats (and are not in the coreInfixes set) will be excluded from the engine’s capabilities, resulting in errors if a policy tries to use them.

Converting Rego Values ↔ Go

If you want to manually convert values, the convert package provides:

  • RegoToGoT any
    Convert an AST value to a typed Go value.
  • GoToRego(interface{})
    Convert a Go value to an AST term.

These functions support bool, string, numeric types, decimal.Decimal, time.Time, slices, maps, structs, and more.

package main

import (
    "fmt"
    "github.com/open-policy-agent/opa/v1/ast"
    "github.com/sky1core/regobrick/convert"
)

func convertExample() {
    // Suppose we have a Rego AST number
    regoNumber := ast.Number("42")
    goVal, err := convert.RegoToGo[int](regoNumber)
    if err != nil {
        panic(err)
    }
    fmt.Println("Converted to Go int:", goVal) // 42

    // Convert back to a Rego term
    term, err := convert.GoToRego(goVal)
    if err != nil {
        panic(err)
    }
    fmt.Println("Converted back to Rego term:", term)
}

With these features, you can seamlessly integrate custom transformations, builtins, capability filtering, and value conversion into your OPA-based workflows—without forking or modifying OPA’s core engine.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func FilterCapabilities added in v0.3.0

func FilterCapabilities(allowedNames []string, allowedCats []string) *ast.Capabilities

FilterCapabilities filters OPA built-in functions based on a list of allowed names and categories, along with built-ins whose infix is in coreInfixes.

  • If a built-in's infix is one of the core infixes, it is kept.
  • Otherwise, if the built-in name is in allowedNames or any of its categories are in allowedCats, it is kept.
  • If a built-in has custom category mappings, those are also checked against allowedCats.

The resulting capabilities object contains only the filtered built-ins.

func RegisterBuiltin0 added in v0.2.0

func RegisterBuiltin0[R any](name string, fn func(rego.BuiltinContext) (R, error), opts ...BuiltinRegisterOption)

RegisterBuiltin0

func RegisterBuiltin0_ added in v0.2.0

func RegisterBuiltin0_(name string, fn func(rego.BuiltinContext) error, opts ...BuiltinRegisterOption)

RegisterBuiltin0_ has no arguments, returns error => null

func RegisterBuiltin1 added in v0.2.0

func RegisterBuiltin1[T1 any, R any](name string, fn func(rego.BuiltinContext, T1) (R, error), opts ...BuiltinRegisterOption)

RegisterBuiltin1

func RegisterBuiltin1_ added in v0.2.0

func RegisterBuiltin1_[T1 any](name string, fn func(rego.BuiltinContext, T1) error, opts ...BuiltinRegisterOption)

RegisterBuiltin1_ has 1 argument, returns error => null

func RegisterBuiltin2 added in v0.2.0

func RegisterBuiltin2[T1 any, T2 any, R any](name string, fn func(rego.BuiltinContext, T1, T2) (R, error), opts ...BuiltinRegisterOption)

RegisterBuiltin2

func RegisterBuiltin2_ added in v0.2.0

func RegisterBuiltin2_[T1 any, T2 any](name string, fn func(rego.BuiltinContext, T1, T2) error, opts ...BuiltinRegisterOption)

RegisterBuiltin2_

func RegisterBuiltin3 added in v0.2.0

func RegisterBuiltin3[T1 any, T2 any, T3 any, R any](name string, fn func(rego.BuiltinContext, T1, T2, T3) (R, error), opts ...BuiltinRegisterOption)

RegisterBuiltin3

func RegisterBuiltin3_ added in v0.2.0

func RegisterBuiltin3_[T1 any, T2 any, T3 any](name string, fn func(rego.BuiltinContext, T1, T2, T3) error, opts ...BuiltinRegisterOption)

RegisterBuiltin3_

func RegisterBuiltin4 added in v0.2.0

func RegisterBuiltin4[T1 any, T2 any, T3 any, T4 any, R any](name string, fn func(rego.BuiltinContext, T1, T2, T3, T4) (R, error), opts ...BuiltinRegisterOption)

RegisterBuiltin4

func RegisterBuiltin4_ added in v0.2.0

func RegisterBuiltin4_[T1 any, T2 any, T3 any, T4 any](name string, fn func(rego.BuiltinContext, T1, T2, T3, T4) error, opts ...BuiltinRegisterOption)

RegisterBuiltin4_

func RegisterBuiltin5 added in v0.2.0

func RegisterBuiltin5[T1 any, T2 any, T3 any, T4 any, T5 any, R any](name string, fn func(rego.BuiltinContext, T1, T2, T3, T4, T5) (R, error), opts ...BuiltinRegisterOption)

RegisterBuiltin5

func RegisterBuiltin5_ added in v0.2.0

func RegisterBuiltin5_[T1 any, T2 any, T3 any, T4 any, T5 any](name string, fn func(rego.BuiltinContext, T1, T2, T3, T4, T5) error, opts ...BuiltinRegisterOption)

RegisterBuiltin5_

Types

type Brick added in v0.5.0

type Brick struct {
	// contains filtered or unexported fields
}

Brick holds configuration for RegoBrick.

func New added in v0.3.0

func New(options ...FnBrickOption) *Brick

New returns a new Brick.

func (*Brick) Rego added in v0.5.0

func (b *Brick) Rego(regoOpts ...FnRegoOption) (*rego.Rego, error)

Rego builds a rego.Rego instance using the configured modules and input.

type BuiltinRegisterOption added in v0.4.0

type BuiltinRegisterOption func(*builtinRegisterConfig)

func WithCategories added in v0.4.0

func WithCategories(cats ...string) BuiltinRegisterOption

func WithDefaultDecimal added in v0.4.0

func WithDefaultDecimal() BuiltinRegisterOption

When set, RegoToGo will convert ast.Number to decimal.Decimal by default.

func WithNondeterministic added in v0.4.0

func WithNondeterministic() BuiltinRegisterOption

type FnBrickOption added in v0.5.0

type FnBrickOption = func(*Brick)

func Input added in v0.5.0

func Input(input interface{}) FnBrickOption

Input sets the input data for evaluation. If the value is a decimal.Decimal, it is converted into a JSON number rather than a string, preserving numeric precision.

func Module

func Module(filename, source string, imports []string) FnBrickOption

Module adds a single Rego module to the Brick.

func Modules added in v0.5.0

func Modules(moduleOpts ...ModuleOption) FnBrickOption

Modules adds multiple Rego modules to the Brick.

type FnRegoOption added in v0.5.0

type FnRegoOption = func(*rego.Rego)

type ModuleOption added in v0.5.0

type ModuleOption struct {
	Filename string
	Source   string
	Imports  []string
}

ModuleOption holds information about a Rego module.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL