regobrick

package module
v0.4.0 Latest Latest
Warning

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

Go to latest
Published: Mar 7, 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

  1. Transforming Modules (e.g. default_false)

Below is an example of how to use RegoBrick’s transformations (such as default_false). You create a BrickConfig instance by adding modules through method chaining and then calling .Build() to generate an option suitable for OPA’s rego.New():

import (
    "context"
    "log/slog"

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

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

    subPolicy := `
        package sub

        some_rule {
            input.value == 123
        }
    `

    mainPolicy := `
        package example

        import data.regobrick.default_false

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

    // Create a BrickConfig and add modules using method chaining.
    brick := regobrick.New().
        Module("sub.rego", subPolicy, []string{"data.some.pkg"}).
        Module("main.rego", mainPolicy, []string{"data.mycompany.util"})

    // Call Build() to prepare the modules for rego.New().
    brickOption, err := brick.Build()
    if err != nil {
        panic(err)
        return
    }

    // Pass the brickOption to rego.New().
    query, err := rego.New(
        brickOption,
        rego.Query("data.example.allow"),
    ).PrepareForEval(ctx)
    if err != nil {
        panic(err)
    }

    rs, err := query.Eval(ctx, rego.EvalInput(map[string]interface{}{
        "user":  "admin",
        "value": 123,
    }))
    if err != nil {
        panic(err)
    }

    // The result contains the evaluated value of 'data.example.allow'.
    // Due to 'default_false', if 'allow' conditions aren't met,
    // it automatically defaults to false.
}

This ensures your modules and transformations are correctly applied and prepared for evaluation.


2. 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.

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() {
    // Register the builtin with 1 string argument, returning bool.
    // The second argument is the categories for this builtin (can be used in FilterCapabilities).
    // The third argument is whether it's nondeterministic (false here).
    regobrick.RegisterBuiltin1[string, bool]("is_admin", []string{"my_custom_category"}, false, isAdmin)

    // 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.


3. 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.

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.


4. 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.

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 BrickConfig added in v0.4.0

type BrickConfig struct {
	Modules []RegoModule
	// contains filtered or unexported fields
}

BrickConfig holds configuration data gathered by method chaining.

func New added in v0.3.0

func New() *BrickConfig

New creates a new BrickConfig for method chaining.

func (*BrickConfig) Build added in v0.4.0

func (cfg *BrickConfig) Build() (func(*rego.Rego), error)

Build parses Rego modules, converts input to decimal-friendly data if requested, and returns a function that can be passed to rego.New(...) for evaluation.

func (*BrickConfig) InputWithDecimalNumber added in v0.4.0

func (cfg *BrickConfig) InputWithDecimalNumber(value interface{}) *BrickConfig

InputWithDecimalNumber specifies that the given value should be used as the Rego input (replacing rego.Input()). Any decimal-type fields (e.g. decimal.Decimal) in Go are passed to Rego as JSON numbers (not strings), preserving precision.

func (*BrickConfig) Module added in v0.4.0

func (cfg *BrickConfig) Module(filename, source string, imports []string) *BrickConfig

Module adds a Rego module definition (filename, source, imports) to BrickConfig.

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 RegoModule added in v0.3.0

type RegoModule struct {
	Filename string
	Source   string
	Imports  []string
	AST      *ast.Module
}

RegoModule represents a single Rego module (filename, source, imports, and AST).

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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