Embedding in Go

Use CrowdControl as a library from inside your service. Zero dependencies, pure stdlib.

Install

go get github.com/mikemackintosh/crowdcontrol

Basic usage

package main

import (
    "fmt"
    "github.com/mikemackintosh/crowdcontrol"
)

func main() {
    eng, err := crowdcontrol.New([]string{"./policies"})
    if err != nil {
        panic(err)
    }

    doc := map[string]any{
        "user":     map[string]any{"name": "alex", "role": "intern"},
        "request":  map[string]any{"action": "delete"},
        "resource": map[string]any{"environment": "production"},
    }

    results := eng.Evaluate(doc)

    for _, r := range results {
        fmt.Printf("%s [%s] passed=%v: %s\n",
            r.Rule, r.Kind, r.Passed, r.Message)
    }
}

With schema validation

import (
    "github.com/mikemackintosh/crowdcontrol"
    "github.com/mikemackintosh/crowdcontrol/evaluator"
    "github.com/mikemackintosh/crowdcontrol/types"
)

schema := &types.Schema{
    Fields: map[string]types.FieldType{
        "user.name":       types.FieldString,
        "user.role":       types.FieldString,
        "request.action":  types.FieldString,
        "resource.environment": types.FieldString,
    },
}

// validate at load time
warnings := evaluator.ValidatePolicy(policies, schema)
if len(warnings) > 0 {
    fmt.Print(evaluator.FormatWarnings(warnings))
}

With explain mode

import "github.com/mikemackintosh/crowdcontrol/evaluator"

eng := evaluator.NewFromPolicies(policies,
    evaluator.WithExplain(true),
)
results := eng.Evaluate(doc)

// each Result now has a populated Trace field
fmt.Print(evaluator.FormatExplain(results))

Default-deny mode

eng := evaluator.NewFromPolicies(policies,
    evaluator.WithDefaultEffect(types.DefaultDeny),
)

Hot-reloading policies

Policies are loaded at New(). To reload, just call New() again and swap the engine. Since the engine has no mutable state beyond compiled rules, this is safe.

var eng atomic.Pointer[crowdcontrol.Engine]

func reload() error {
    fresh, err := crowdcontrol.New([]string{"./policies"})
    if err != nil {
        return err
    }
    eng.Store(fresh)
    return nil
}

func evaluate(doc map[string]any) []types.Result {
    return eng.Load().Evaluate(doc)
}