From ba540661923ff10036a441c5aa1697e7c6b20696 Mon Sep 17 00:00:00 2001 From: Patrick East Date: Mon, 2 Nov 2020 12:50:43 -0800 Subject: [PATCH] Move golang-opa-wasm into opa This moves the package from: github.com/open-policy-agent/golang-opa-wasm/opa to: github.com/open-policy-agent/opa/wasm/sdk/opa This avoids callers from having to do much in the way of changes besides adjusting the import path. Signed-off-by: Patrick East --- .gitignore | 1 + internal/wasm/sdk/README.md | 7 + .../wasm/sdk/examples/basic/example-1.rego | 3 + .../wasm/sdk/examples/basic/example-1.wasm | Bin 0 -> 30370 bytes .../wasm/sdk/examples/basic/example-2.rego | 3 + .../wasm/sdk/examples/basic/example-2.wasm | Bin 0 -> 30370 bytes internal/wasm/sdk/examples/basic/main.go | 97 ++++ internal/wasm/sdk/examples/loaders/bundle.tgz | Bin 0 -> 11348 bytes internal/wasm/sdk/examples/loaders/main.go | 114 +++++ internal/wasm/sdk/opa/bindings.go | 85 ++++ internal/wasm/sdk/opa/config.go | 108 +++++ internal/wasm/sdk/opa/errors.go | 26 ++ internal/wasm/sdk/opa/file/config.go | 35 ++ internal/wasm/sdk/opa/file/loader.go | 163 +++++++ internal/wasm/sdk/opa/file/loader_test.go | 128 ++++++ internal/wasm/sdk/opa/http/config.go | 66 +++ internal/wasm/sdk/opa/http/loader.go | 257 +++++++++++ internal/wasm/sdk/opa/http/loader_test.go | 125 ++++++ internal/wasm/sdk/opa/http/util.go | 42 ++ internal/wasm/sdk/opa/loader.go | 21 + internal/wasm/sdk/opa/opa.go | 203 +++++++++ internal/wasm/sdk/opa/opa_test.go | 226 ++++++++++ internal/wasm/sdk/opa/pool.go | 262 +++++++++++ internal/wasm/sdk/opa/vm.go | 416 ++++++++++++++++++ 24 files changed, 2388 insertions(+) create mode 100644 internal/wasm/sdk/README.md create mode 100644 internal/wasm/sdk/examples/basic/example-1.rego create mode 100644 internal/wasm/sdk/examples/basic/example-1.wasm create mode 100644 internal/wasm/sdk/examples/basic/example-2.rego create mode 100644 internal/wasm/sdk/examples/basic/example-2.wasm create mode 100644 internal/wasm/sdk/examples/basic/main.go create mode 100644 internal/wasm/sdk/examples/loaders/bundle.tgz create mode 100644 internal/wasm/sdk/examples/loaders/main.go create mode 100644 internal/wasm/sdk/opa/bindings.go create mode 100644 internal/wasm/sdk/opa/config.go create mode 100644 internal/wasm/sdk/opa/errors.go create mode 100644 internal/wasm/sdk/opa/file/config.go create mode 100644 internal/wasm/sdk/opa/file/loader.go create mode 100644 internal/wasm/sdk/opa/file/loader_test.go create mode 100644 internal/wasm/sdk/opa/http/config.go create mode 100644 internal/wasm/sdk/opa/http/loader.go create mode 100644 internal/wasm/sdk/opa/http/loader_test.go create mode 100644 internal/wasm/sdk/opa/http/util.go create mode 100644 internal/wasm/sdk/opa/loader.go create mode 100644 internal/wasm/sdk/opa/opa.go create mode 100644 internal/wasm/sdk/opa/opa_test.go create mode 100644 internal/wasm/sdk/opa/pool.go create mode 100644 internal/wasm/sdk/opa/vm.go diff --git a/.gitignore b/.gitignore index c09a315d05..0ec8e28e1c 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ opa_* _release wasm/_obj _test +!*_test.go site.tar.gz policy.wasm .npm diff --git a/internal/wasm/sdk/README.md b/internal/wasm/sdk/README.md new file mode 100644 index 0000000000..af407db984 --- /dev/null +++ b/internal/wasm/sdk/README.md @@ -0,0 +1,7 @@ +**Work in Progress -- Contributions welcome!** + +# Open Policy Agent WebAssemby Go SDK +This is the source for the Open Policy Agent WebAssembly Go SDK which +is a small go library for using WebAssembly (wasm) compiled [Open +Policy Agent](https://www.openpolicyagent.org/) Rego policies. + diff --git a/internal/wasm/sdk/examples/basic/example-1.rego b/internal/wasm/sdk/examples/basic/example-1.rego new file mode 100644 index 0000000000..cbc29d0560 --- /dev/null +++ b/internal/wasm/sdk/examples/basic/example-1.rego @@ -0,0 +1,3 @@ +package example + +allow = input.foo diff --git a/internal/wasm/sdk/examples/basic/example-1.wasm b/internal/wasm/sdk/examples/basic/example-1.wasm new file mode 100644 index 0000000000000000000000000000000000000000..3045bd85a644cb5b722687ba285229ea3e3d11ce GIT binary patch literal 30370 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 literal 0 HcmV?d00001 diff --git a/internal/wasm/sdk/examples/basic/example-2.rego b/internal/wasm/sdk/examples/basic/example-2.rego new file mode 100644 index 0000000000..f60b425834 --- /dev/null +++ b/internal/wasm/sdk/examples/basic/example-2.rego @@ -0,0 +1,3 @@ +package example + +allow = input.bar diff --git a/internal/wasm/sdk/examples/basic/example-2.wasm b/internal/wasm/sdk/examples/basic/example-2.wasm new file mode 100644 index 0000000000000000000000000000000000000000..665168720c02467838d10496d98b1a7e5a089288 GIT binary patch literal 30370 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 3 { + token = os.Args[2] + } + + // Setup the SDK, either with HTTP bundle loader or file bundle loader. + + if err := setup(url, token); err != nil { + fmt.Printf("error: %v\n", err) + return + } + + defer cleanup() + + // Evaluate the policy. + + var input interface{} = map[string]interface{}{ + "foo": true, + } + + ctx := context.Background() + result, err := rego.Eval(ctx, &input) + if err != nil { + fmt.Printf("error: %v\n", err) + return + } + + fmt.Printf("Policy result: %v\n", result) +} + +func setup(u string, token string) error { + r, err := opa.New().Init() + if err != nil { + return err + } + + url, err := url.Parse(u) + if err != nil { + return err + } + + var l opa.Loader + + switch url.Scheme { + case "http", "https": + l, err = http.New(r). + WithURL(url.String()). + WithPrepareRequest(func(req *gohttp.Request) error { + if token != "" { + req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token)) + } + return nil + }). + WithInterval(30*time.Second, 60*time.Second). + Init() + case "file", "": + l, err = file.New(r). + WithFile(url.String()). + WithInterval(10 * time.Second). + Init() + } + + if err != nil { + return err + } + + if err := l.Start(context.Background()); err != nil { + return err + } + + rego, loader = r, l + return nil +} + +func cleanup() { + loader.Close() + rego.Close() +} diff --git a/internal/wasm/sdk/opa/bindings.go b/internal/wasm/sdk/opa/bindings.go new file mode 100644 index 0000000000..e6df389177 --- /dev/null +++ b/internal/wasm/sdk/opa/bindings.go @@ -0,0 +1,85 @@ +// Copyright 2020 The OPA Authors. All rights reserved. +// Use of this source code is governed by an Apache2 +// license that can be found in the LICENSE file. + +package opa + +// #include +// +// extern void opa_abort(void *context, int32_t addr); +// extern int32_t opa_builtin0(void *context, int32_t builtin_id, int32_t ctx); +// extern int32_t opa_builtin1(void *context, int32_t builtin_id, int32_t ctx, int32_t arg0); +// extern int32_t opa_builtin2(void *context, int32_t builtin_id, int32_t ctx, int32_t arg0, int32_t arg1); +// extern int32_t opa_builtin3(void *context, int32_t builtin_id, int32_t ctx, int32_t arg0, int32_t arg1, int32_t arg2); +// extern int32_t opa_builtin4(void *context, int32_t builtin_id, int32_t ctx, int32_t arg0, int32_t arg1, int32_t arg2, int32_t arg3); +import "C" + +import ( + "unsafe" + + wasm "github.com/wasmerio/go-ext-wasm/wasmer" +) + +func opaFunctions(imports *wasm.Imports) (*wasm.Imports, error) { + imports, err := imports.AppendFunction("opa_abort", opa_abort, C.opa_abort) + if err != nil { + return nil, err + } + + imports, err = imports.AppendFunction("opa_builtin0", opa_builtin0, C.opa_builtin0) + if err != nil { + return nil, err + } + + imports, err = imports.AppendFunction("opa_builtin1", opa_builtin1, C.opa_builtin1) + if err != nil { + return nil, err + } + + imports, err = imports.AppendFunction("opa_builtin2", opa_builtin2, C.opa_builtin2) + if err != nil { + return nil, err + } + + imports, err = imports.AppendFunction("opa_builtin3", opa_builtin3, C.opa_builtin3) + if err != nil { + return nil, err + } + + return imports.AppendFunction("opa_builtin4", opa_builtin4, C.opa_builtin4) +} + +//export opa_abort +func opa_abort(ctx unsafe.Pointer, addr int32) { + getVM(ctx).Abort(addr) +} + +//export opa_builtin0 +func opa_builtin0(ctx unsafe.Pointer, builtinID, context int32) int32 { + return getVM(ctx).Builtin(builtinID, context) +} + +//export opa_builtin1 +func opa_builtin1(ctx unsafe.Pointer, builtinID, context, arg0 int32) int32 { + return getVM(ctx).Builtin(builtinID, context, arg0) +} + +//export opa_builtin2 +func opa_builtin2(ctx unsafe.Pointer, builtinID, context, arg0, arg1 int32) int32 { + return getVM(ctx).Builtin(builtinID, context, arg0, arg1) +} + +//export opa_builtin3 +func opa_builtin3(ctx unsafe.Pointer, builtinID, context, arg0, arg1, arg2 int32) int32 { + return getVM(ctx).Builtin(builtinID, context, arg0, arg1, arg2) +} + +//export opa_builtin4 +func opa_builtin4(ctx unsafe.Pointer, builtinID, context, arg0, arg1, arg2, arg3 int32) int32 { + return getVM(ctx).Builtin(builtinID, context, arg0, arg1, arg2, arg3) +} + +func getVM(ctx unsafe.Pointer) *vm { + ictx := wasm.IntoInstanceContext(ctx) + return ictx.Data().(*vm) +} diff --git a/internal/wasm/sdk/opa/config.go b/internal/wasm/sdk/opa/config.go new file mode 100644 index 0000000000..517749687f --- /dev/null +++ b/internal/wasm/sdk/opa/config.go @@ -0,0 +1,108 @@ +// Copyright 2020 The OPA Authors. All rights reserved. +// Use of this source code is governed by an Apache2 +// license that can be found in the LICENSE file. + +package opa + +import ( + "encoding/json" + "fmt" + "io/ioutil" +) + +const wasmPageSize = 65535 + +// WithPolicyFile configures a policy file to load. +func (o *OPA) WithPolicyFile(fileName string) *OPA { + policy, err := ioutil.ReadFile(fileName) + if err != nil { + o.configErr = fmt.Errorf("%v: %w", err.Error(), ErrInvalidConfig) + return o + } + + o.policy = policy + return o +} + +// WithPolicyBytes configures the compiled policy to load. +func (o *OPA) WithPolicyBytes(policy []byte) *OPA { + o.policy = policy + return o +} + +// WithDataFile configures the JSON data file to load. +func (o *OPA) WithDataFile(fileName string) *OPA { + data, err := ioutil.ReadFile(fileName) + if err != nil { + o.configErr = fmt.Errorf("%v: %w", err.Error(), ErrInvalidConfig) + return o + } + + o.data = data + return o +} + +// WithDataBytes configures the JSON data to load. +func (o *OPA) WithDataBytes(data []byte) *OPA { + o.data = data + return o +} + +// WithDataJSON configures the JSON data to load. +func (o *OPA) WithDataJSON(data interface{}) *OPA { + v, err := json.Marshal(data) + if err != nil { + o.configErr = fmt.Errorf("%v: %w", err.Error(), ErrInvalidConfig) + return o + } + + o.data = v + return o +} + +// WithMemoryLimits configures the memory limits (in bytes) for a single policy +// evaluation. +func (o *OPA) WithMemoryLimits(min, max uint32) *OPA { + if min < 2*65535 { + o.configErr = fmt.Errorf("too low minimum memory limit: %w", ErrInvalidConfig) + return o + } + + if max != 0 && min > max { + o.configErr = fmt.Errorf("too low maximum memory limit: %w", ErrInvalidConfig) + return o + } + + o.memoryMinPages, o.memoryMaxPages = pages(min), pages(max) + return o +} + +// WithPoolSize configures the maximum number of simultaneous policy +// evaluations, i.e., the maximum number of underlying WASM instances +// active at any time. The default is the number of logical CPUs +// usable for the process as per runtime.NumCPU(). +func (o *OPA) WithPoolSize(size uint32) *OPA { + if size == 0 { + o.configErr = fmt.Errorf("pool size: %w", ErrInvalidConfig) + return o + } + + o.poolSize = size + return o +} + +// WithErrorLogger configures an error logger invoked with all the errors. +func (o *OPA) WithErrorLogger(logger func(error)) *OPA { + o.logError = logger + return o +} + +// pages converts a byte size to pages, rounding up as necessary. +func pages(n uint32) uint32 { + pages := n / wasmPageSize + if pages*wasmPageSize == n { + return pages + } + + return pages + 1 +} diff --git a/internal/wasm/sdk/opa/errors.go b/internal/wasm/sdk/opa/errors.go new file mode 100644 index 0000000000..76630d84f5 --- /dev/null +++ b/internal/wasm/sdk/opa/errors.go @@ -0,0 +1,26 @@ +// Copyright 2020 The OPA Authors. All rights reserved. +// Use of this source code is governed by an Apache2 +// license that can be found in the LICENSE file. + +package opa + +import ( + "errors" +) + +var ( + // ErrInvalidConfig is the error returned if the OPA initialization fails due to an invalid config. + ErrInvalidConfig = errors.New("invalid config") + // ErrInvalidPolicyOrData is the error returned if either policy or data is invalid. + ErrInvalidPolicyOrData = errors.New("invalid policy or data") + // ErrInvalidBundle is the error returned if the bundle loaded is corrupted. + ErrInvalidBundle = errors.New("invalid bundle") + // ErrNotReady is the error returned if the OPA instance is not initialized. + ErrNotReady = errors.New("not ready") + // ErrUndefined is the error returned if the evaluation result is undefined. + ErrUndefined = errors.New("undefined decision") + // ErrNonBoolean is the error returned if the evaluation result is not of boolean value. + ErrNonBoolean = errors.New("non-boolean decision") + // ErrInternal is the error returned if the evaluation fails due to an internal error. + ErrInternal = errors.New("internal error") +) diff --git a/internal/wasm/sdk/opa/file/config.go b/internal/wasm/sdk/opa/file/config.go new file mode 100644 index 0000000000..43d2038836 --- /dev/null +++ b/internal/wasm/sdk/opa/file/config.go @@ -0,0 +1,35 @@ +// Copyright 2020 The OPA Authors. All rights reserved. +// Use of this source code is governed by an Apache2 +// license that can be found in the LICENSE file. + +package file + +import ( + "fmt" + "time" + + "github.com/open-policy-agent/opa/internal/wasm/sdk/opa" +) + +// WithFile configures the file to load the bundle from. +func (l *Loader) WithFile(filename string) *Loader { + l.filename = filename + return l +} + +// WithInterval configures the delay between bundle file reloading. +func (l *Loader) WithInterval(interval time.Duration) *Loader { + l.interval = interval + return l +} + +// WithErrorLogger configures an error logger invoked with all the errors. +func (l *Loader) WithErrorLogger(logger func(error)) *Loader { + if logger == nil { + l.configErr = fmt.Errorf("logger: %w", opa.ErrInvalidConfig) + return l + } + + l.logError = logger + return l +} diff --git a/internal/wasm/sdk/opa/file/loader.go b/internal/wasm/sdk/opa/file/loader.go new file mode 100644 index 0000000000..ce1a71df76 --- /dev/null +++ b/internal/wasm/sdk/opa/file/loader.go @@ -0,0 +1,163 @@ +// Copyright 2020 The OPA Authors. All rights reserved. +// Use of this source code is governed by an Apache2 +// license that can be found in the LICENSE file. + +package file + +import ( + "context" + "fmt" + "os" + "sync" + "time" + + "github.com/open-policy-agent/opa/bundle" + + "github.com/open-policy-agent/opa/internal/wasm/sdk/opa" +) + +const ( + // DefaultInterval for re-loading the bundle file. + DefaultInterval = time.Minute +) + +// Loader loads a bundle from a file. If started, it loads the bundle +// periodically until closed. +type Loader struct { + configErr error // Delayed configuration error, if any. + initialized bool + pd policyData + filename string + interval time.Duration + closing chan struct{} // Signal the request to stop the poller. + closed chan struct{} // Signals the successful stopping of the poller. + logError func(error) + mutex sync.Mutex +} + +// policyData captures the functions used in setting the policy and data. +type policyData interface { + SetPolicyData(policy []byte, data *interface{}) error +} + +// New constructs a new file loader periodically reloading the bundle +// from a file. +func New(opa *opa.OPA) *Loader { + return new(opa) +} + +// new constucts a new file loader. This is for tests. +func new(pd policyData) *Loader { + return &Loader{ + pd: pd, + interval: DefaultInterval, + logError: func(error) {}, + } +} + +// Init initializes the loader after its construction and +// configuration. If invalid config, will return ErrInvalidConfig. +func (l *Loader) Init() (*Loader, error) { + if l.configErr != nil { + return nil, l.configErr + } + + if l.filename == "" { + return nil, fmt.Errorf("filename: %w", opa.ErrInvalidConfig) + } + + l.initialized = true + return l, nil +} + +// Start starts the periodic loading byt calling Load, failing if the +// bundle loading fails. +func (l *Loader) Start(ctx context.Context) error { + if !l.initialized { + return opa.ErrNotReady + } + + if err := l.Load(ctx); err != nil { + return err + } + + l.closing = make(chan struct{}) + l.closed = make(chan struct{}) + + go l.poller() + + return nil +} + +// Close stops the loading, releasing all resources. +func (l *Loader) Close() { + if !l.initialized { + return + } + + if l.closing == nil { + return + } + + close(l.closing) + <-l.closed + + l.closing = nil + l.closed = nil +} + +// Load loads the bundle from a file and installs it. The possible +// returned errors are ErrInvalidBundle (in case of an error in +// loading or opening the bundle) and the ones SetPolicyData of OPA +// returns. +func (l *Loader) Load(ctx context.Context) error { + if !l.initialized { + return opa.ErrNotReady + } + + l.mutex.Lock() + defer l.mutex.Unlock() + + f, err := os.Open(l.filename) + if err != nil { + return fmt.Errorf("%v: %w", err, opa.ErrInvalidBundle) + } + + defer f.Close() + + // TODO: Cut the dependency to the OPA bundle package. + + bundle, err := bundle.NewReader(f).Read() + if err != nil { + return fmt.Errorf("%v: %w", err, opa.ErrInvalidBundle) + } + + if bundle.Wasm == nil { + return fmt.Errorf("missing wasm: %w", opa.ErrInvalidBundle) + } + + var data *interface{} + if bundle.Data != nil { + var v interface{} = bundle.Data + data = &v + } + + return l.pd.SetPolicyData(bundle.Wasm, data) +} + +// poller periodically downloads the bundle. +func (l *Loader) poller() { + defer close(l.closed) + + for { + if err := l.Load(context.Background()); err != nil { + l.logError(err) + } + + select { + case <-time.After(l.interval): + case <-l.closing: + return + } + } +} diff --git a/internal/wasm/sdk/opa/file/loader_test.go b/internal/wasm/sdk/opa/file/loader_test.go new file mode 100644 index 0000000000..5af655d99a --- /dev/null +++ b/internal/wasm/sdk/opa/file/loader_test.go @@ -0,0 +1,128 @@ +// Copyright 2020 The OPA Authors. All rights reserved. +// Use of this source code is governed by an Apache2 +// license that can be found in the LICENSE file. + +package file + +import ( + "bytes" + "context" + "io/ioutil" + "os" + "reflect" + "sync" + "testing" + "time" + + "github.com/open-policy-agent/opa/bundle" +) + +func TestFileLoader(t *testing.T) { + // Assign a temp file. + + f, err := ioutil.TempFile("", "test-file-loader") + if err != nil { + panic(err) + } + + defer os.Remove(f.Name()) + + // Start loader, without having a file in place. + + var pd testPolicyData + loader, err := new(&pd).WithFile(f.Name()).WithInterval(10 * time.Millisecond).Init() + if err != nil { + t.Fatal(err.Error()) + } + + ctx := context.Background() + if err := loader.Start(ctx); err == nil { + t.Fatal("missing file not resulting in an error") + } + + policy := "wasm-policy" + var data interface{} = map[string]interface{}{ + "foo": "bar", + } + + // Start loader, with the file in place. + + writeBundle(f.Name(), policy, data) + + if err := loader.Start(ctx); err != nil { + t.Fatalf("unable to start loader: %v", err) + } + + pd.CheckEqual(t, policy, &data) + + // Reload with updated contents. + + policy = "wasm-policy-modified" + data = map[string]interface{}{ + "bar": "foo", + } + + writeBundle(f.Name(), policy, data) + + pd.WaitUpdate() + pd.CheckEqual(t, policy, &data) + + loader.Close() +} + +type testPolicyData struct { + sync.Mutex + policy []byte + data *interface{} + updated chan struct{} +} + +func (pd *testPolicyData) SetPolicyData(policy []byte, data *interface{}) error { + pd.Lock() + defer pd.Unlock() + + pd.policy = policy + pd.data = data + if pd.updated != nil { + close(pd.updated) + } + + return nil +} + +func (pd *testPolicyData) CheckEqual(t *testing.T, policy string, data *interface{}) { + pd.Lock() + defer pd.Unlock() + + if !bytes.Equal([]byte(policy), pd.policy) && reflect.DeepEqual(data, pd.data) { + t.Fatal("policy/data mismatch.") + } +} + +func (pd *testPolicyData) WaitUpdate() { + pd.Lock() + pd.updated = make(chan struct{}) + pd.Unlock() + + <-pd.updated + + pd.Lock() + pd.updated = nil + pd.Unlock() +} + +func writeBundle(name string, policy string, data interface{}) { + b := bundle.Bundle{ + Data: data.(map[string]interface{}), + Wasm: []byte(policy), + } + + var buf bytes.Buffer + if err := bundle.Write(&buf, b); err != nil { + panic(err) + } + + if err := ioutil.WriteFile(name, buf.Bytes(), 0644); err != nil { + panic(err) + } +} diff --git a/internal/wasm/sdk/opa/http/config.go b/internal/wasm/sdk/opa/http/config.go new file mode 100644 index 0000000000..9cb499618e --- /dev/null +++ b/internal/wasm/sdk/opa/http/config.go @@ -0,0 +1,66 @@ +// Copyright 2020 The OPA Authors. All rights reserved. +// Use of this source code is governed by an Apache2 +// license that can be found in the LICENSE file. + +package http + +import ( + "fmt" + "net/http" + "time" + + "github.com/open-policy-agent/opa/internal/wasm/sdk/opa" +) + +// WithURL configures the URL to download the bundle from. +func (l *Loader) WithURL(url string) *Loader { + l.url = url + return l +} + +// WithClient configures the HTTP client to use. If not configured, +// http.DefaultClient is used. +func (l *Loader) WithClient(client *http.Client) *Loader { + if client == nil { + l.configErr = fmt.Errorf("client: %w", opa.ErrInvalidConfig) + return l + } + + l.client = client + return l +} + +// WithInterval configures the minimum and maximum delay between bundle downloads. +func (l *Loader) WithInterval(min, max time.Duration) *Loader { + if min > max { + l.configErr = fmt.Errorf("interval: %w", opa.ErrInvalidConfig) + return l + } + + l.minDelay = min + l.maxDelay = max + return l +} + +// WithPrepareRequest configures a handler to customize the HTTP requests before their sending. The +// HTTP request is not modified after the handle invocation. +func (l *Loader) WithPrepareRequest(prepare func(*http.Request) error) *Loader { + if prepare == nil { + l.configErr = fmt.Errorf("prepare request: %w", opa.ErrInvalidConfig) + return l + } + + l.prepareRequest = prepare + return l +} + +// WithErrorLogger configures an error logger invoked with all the errors. +func (l *Loader) WithErrorLogger(logger func(error)) *Loader { + if logger == nil { + l.configErr = fmt.Errorf("logger: %w", opa.ErrInvalidConfig) + return l + } + + l.logError = logger + return l +} diff --git a/internal/wasm/sdk/opa/http/loader.go b/internal/wasm/sdk/opa/http/loader.go new file mode 100644 index 0000000000..253d14b5c2 --- /dev/null +++ b/internal/wasm/sdk/opa/http/loader.go @@ -0,0 +1,257 @@ +// Copyright 2020 The OPA Authors. All rights reserved. +// Use of this source code is governed by an Apache2 +// license that can be found in the LICENSE file. + +package http + +import ( + "context" + "fmt" + "io" + "io/ioutil" + "math/rand" + "net/http" + "sync" + "time" + + "github.com/open-policy-agent/opa/bundle" + + "github.com/open-policy-agent/opa/internal/wasm/sdk/opa" +) + +const ( + // MinRetryDelay determines the minimum retry interval in case + // of an error. + MinRetryDelay = 100 * time.Millisecond + + // DefaultMinDelay is the default minimum re-downloading + // interval in case of a previously successful download. + DefaultMinDelay = 60 * time.Second + + // DefaultMaxDelay is the default maximum re-downloading + // interval in case of a previously successful download. + DefaultMaxDelay = 120 * time.Second +) + +// Loader downloads a bundle over HTTP. If started, it downloads the +// bundle periodically until closed. +type Loader struct { + configErr error // Delayed configuration error, if any. + initialized bool + pd policyData + client *http.Client + url string + tag string + minDelay time.Duration + maxDelay time.Duration + closing chan struct{} // Signal the request to stop the poller. + closed chan struct{} // Signals the successful stopping of the poller. + logError func(error) + prepareRequest func(*http.Request) error + mutex sync.Mutex +} + +// policyData captures the functions used in setting the policy and data. +type policyData interface { + SetPolicyData(policy []byte, data *interface{}) error +} + +// New constructs a new HTTP loader periodically downloading a bundle +// over HTTP. +func New(o *opa.OPA) *Loader { + return new(o) +} + +// new constucts a new HTTP loader. This is for tests. +func new(pd policyData) *Loader { + return &Loader{ + pd: pd, + client: http.DefaultClient, + minDelay: DefaultMinDelay, + maxDelay: DefaultMaxDelay, + logError: func(error) {}, + prepareRequest: func(*http.Request) error { return nil }, + } +} + +// Init initializes the loader after its construction and +// configuration. If invalid config, will return ErrInvalidConfig. +func (l *Loader) Init() (*Loader, error) { + if l.configErr != nil { + return nil, l.configErr + } + + if l.url == "" { + return nil, fmt.Errorf("missing url: %w", opa.ErrInvalidConfig) + } + + l.initialized = true + return l, nil +} + +// Start starts the periodic downloads, blocking until the first +// successful download. If cancelled, will return context.Cancelled. +func (l *Loader) Start(ctx context.Context) error { + if !l.initialized { + return opa.ErrNotReady + } + + if err := l.download(ctx); err != nil { + return err + } + + l.closing = make(chan struct{}) + l.closed = make(chan struct{}) + + go l.poller() + + return nil +} + +// Close stops the downloading, releasing all resources. +func (l *Loader) Close() { + if !l.initialized { + return + } + + if l.closing == nil { + return + } + + close(l.closing) + <-l.closed + + l.closing = nil + l.closed = nil +} + +// poller periodically downloads the bundle. +func (l *Loader) poller() { + defer close(l.closed) + + ctx, cancel := context.WithCancel(context.Background()) + go func() { + <-l.closing + cancel() + }() + + for { + if err := l.download(ctx); err != nil { + break + } + + select { + case <-time.After(time.Duration(float64((l.maxDelay-l.minDelay))*rand.Float64()) + l.minDelay): + case <-l.closing: + return + } + } +} + +// download blocks until a bundle has been download successfully or +// the context is cancelled. No other error besides context.Canceled +// is ever returned. +func (l *Loader) download(ctx context.Context) error { + for retry := 0; true; retry++ { + if err := l.Load(ctx); err == context.Canceled { + return err + } else if err != nil { + l.logError(err) + } else if err == nil { + break + } + + select { + case <-time.After(defaultBackoff(float64(MinRetryDelay), float64(l.maxDelay), retry)): + case <-ctx.Done(): + return context.Canceled + } + } + + return nil +} + +// Load downloads the bundle from a remote location and installs +// it. The possible returned errors are ErrInvalidBundle (in case of +// an error in downloading or opening the bundle) and the ones +// SetPolicyData of OPA returns. +func (l *Loader) Load(ctx context.Context) error { + if !l.initialized { + return opa.ErrNotReady + } + + l.mutex.Lock() + defer l.mutex.Unlock() + + bundle, err := l.get(ctx, "") + if err != nil { + return fmt.Errorf("%v: %w", err, opa.ErrInvalidBundle) + } + + if bundle.Wasm == nil { + return opa.ErrInvalidBundle + } + + var data *interface{} + if bundle.Data != nil { + var v interface{} = bundle.Data + data = &v + } + + return l.pd.SetPolicyData(bundle.Wasm, data) +} + +// get executes HTTP GET. +func (l *Loader) get(ctx context.Context, tag string) (*bundle.Bundle, error) { + req, err := http.NewRequest(http.MethodGet, l.url, nil) + if err != nil { + return nil, err + } + + if tag != "" { + req.Header.Add("If-None-Match", tag) + } + + req = req.WithContext(ctx) + if err := l.prepareRequest(req); err != nil { + return nil, err + } + + resp, err := l.client.Do(req) + if err != nil { + return nil, err + } + + defer l.close(resp) + + switch resp.StatusCode { + case http.StatusOK: + // TODO: Cut the dependency to the OPA bundle package. + + b, err := bundle.NewReader(resp.Body).Read() + if err != nil { + return nil, err + } + + l.tag = resp.Header.Get("ETag") + return &b, nil + + case http.StatusNotModified: + return nil, nil + case http.StatusUnauthorized: + return nil, fmt.Errorf("not authorized (401)") + case http.StatusForbidden: + return nil, fmt.Errorf("forbidden (403)") + case http.StatusNotFound: + return nil, fmt.Errorf("not found (404)") + default: + return nil, fmt.Errorf("unknown HTTP status %v", resp.StatusCode) + } +} + +// close closes the HTTP response gracefully, first draining it, to +// avoid resource leaks. +func (l *Loader) close(resp *http.Response) { + io.Copy(ioutil.Discard, resp.Body) // Ignore errors. + resp.Body.Close() +} diff --git a/internal/wasm/sdk/opa/http/loader_test.go b/internal/wasm/sdk/opa/http/loader_test.go new file mode 100644 index 0000000000..0c92315886 --- /dev/null +++ b/internal/wasm/sdk/opa/http/loader_test.go @@ -0,0 +1,125 @@ +// Copyright 2020 The OPA Authors. All rights reserved. +// Use of this source code is governed by an Apache2 +// license that can be found in the LICENSE file. +package http + +import ( + "bytes" + "context" + "net/http" + "net/http/httptest" + "reflect" + "sync" + "testing" + "time" + + "github.com/open-policy-agent/opa/bundle" +) + +func TestFileLoader(t *testing.T) { + // Start loader, without having the HTTP content in place. + + var pd testPolicyData + loader, err := new(&pd).WithURL("http://localhost:0").WithInterval(10*time.Millisecond, 20*time.Millisecond).Init() + if err != nil { + t.Fatal(err.Error()) + } + + ctx, cancel := context.WithCancel(context.Background()) + go func() { + time.Sleep(10 * time.Millisecond) + cancel() + }() + + if err := loader.Start(ctx); err != context.Canceled { + t.Fatalf("missing file not resulting in a correct error: %v", err) + } + + // Start again, with the HTTP content in place. + + var mutex sync.Mutex + policy := "wasm-policy" + var data interface{} = map[string]interface{}{ + "foo": "bar", + } + + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + mutex.Lock() + defer mutex.Unlock() + + if err := bundle.Write(w, bundle.Bundle{ + Data: data.(map[string]interface{}), + Wasm: []byte(policy), + }); err != nil { + panic(err) + } + })) + defer ts.Close() + + loader, err = new(&pd).WithURL(ts.URL).WithInterval(10*time.Millisecond, 20*time.Millisecond).Init() + if err != nil { + t.Fatal(err.Error()) + } + + ctx = context.Background() + if err := loader.Start(ctx); err != nil { + t.Fatalf("unable to start loader: %v", err) + } + + pd.CheckEqual(t, policy, &data) + + // Reload with updated contents. + + mutex.Lock() + policy = "wasm-policy-modified" + data = map[string]interface{}{ + "bar": "foo", + } + mutex.Unlock() + + pd.WaitUpdate() + pd.CheckEqual(t, policy, &data) + + loader.Close() +} + +type testPolicyData struct { + sync.Mutex + policy []byte + data *interface{} + updated chan struct{} +} + +func (pd *testPolicyData) SetPolicyData(policy []byte, data *interface{}) error { + pd.Lock() + defer pd.Unlock() + + pd.policy = policy + pd.data = data + if pd.updated != nil { + close(pd.updated) + } + + return nil +} + +func (pd *testPolicyData) CheckEqual(t *testing.T, policy string, data *interface{}) { + pd.Lock() + defer pd.Unlock() + + if !bytes.Equal([]byte(policy), pd.policy) && reflect.DeepEqual(data, pd.data) { + t.Fatal("policy/data mismatch.") + } +} + +func (pd *testPolicyData) WaitUpdate() { + pd.Lock() + pd.updated = make(chan struct{}) + pd.Unlock() + + <-pd.updated + + pd.Lock() + pd.updated = nil + pd.Unlock() +} diff --git a/internal/wasm/sdk/opa/http/util.go b/internal/wasm/sdk/opa/http/util.go new file mode 100644 index 0000000000..6585f5c040 --- /dev/null +++ b/internal/wasm/sdk/opa/http/util.go @@ -0,0 +1,42 @@ +// Copyright 2020 The OPA Authors. All rights reserved. +// Use of this source code is governed by an Apache2 +// license that can be found in the LICENSE file. + +package http + +import ( + "math/rand" + "time" +) + +// defaultBackoff returns a delay with an exponential backoff based on the +// number of retries. +func defaultBackoff(base, max float64, retries int) time.Duration { + return backoff(base, max, .2, 1.6, retries) +} + +// backoff returns a delay with an exponential backoff based on the number of +// retries. Same algorithm used in gRPC. +func backoff(base, max, jitter, factor float64, retries int) time.Duration { + if retries == 0 { + return 0 + } + + backoff, max := float64(base), float64(max) + for backoff < max && retries > 0 { + backoff *= factor + retries-- + } + if backoff > max { + backoff = max + } + + // Randomize backoff delays so that if a cluster of requests start at + // the same time, they won't operate in lockstep. + backoff *= 1 + jitter*(rand.Float64()*2-1) + if backoff < 0 { + return 0 + } + + return time.Duration(backoff) +} diff --git a/internal/wasm/sdk/opa/loader.go b/internal/wasm/sdk/opa/loader.go new file mode 100644 index 0000000000..8d9f5958b7 --- /dev/null +++ b/internal/wasm/sdk/opa/loader.go @@ -0,0 +1,21 @@ +// Copyright 2020 The OPA Authors. All rights reserved. +// Use of this source code is governed by an Apache2 +// license that can be found in the LICENSE file. + +package opa + +import ( + "context" +) + +// Loader is the interface all bundle loaders implement. +type Loader interface { + // Load loads a bundle. This can be invoked without starting the polling. + Load(ctx context.Context) error + + // Start starts the bundle polling. + Start(ctx context.Context) error + + // Close stops the polling. + Close() +} diff --git a/internal/wasm/sdk/opa/opa.go b/internal/wasm/sdk/opa/opa.go new file mode 100644 index 0000000000..2588ab3145 --- /dev/null +++ b/internal/wasm/sdk/opa/opa.go @@ -0,0 +1,203 @@ +// Copyright 2020 The OPA Authors. All rights reserved. +// Use of this source code is governed by an Apache2 +// license that can be found in the LICENSE file. + +package opa + +import ( + "context" + "encoding/json" + "fmt" + "runtime" + "sync" +) + +// OPA executes WebAssembly compiled Rego policies. +type OPA struct { + configErr error // Delayed configuration error, if any. + memoryMinPages uint32 + memoryMaxPages uint32 // 0 means no limit. + poolSize uint32 + pool *pool + mutex sync.Mutex // To serialize access to SetPolicy, SetData and Close. + policy []byte // Current policy. + data []byte // Current data. + logError func(error) +} + +// Result holds the evaluation result. +type Result struct { + Result interface{} +} + +// New constructs a new OPA SDK instance, ready to be configured with +// With functions. If no policy is provided as a part of +// configuration, policy (and data) needs to be set before invoking +// Eval. Once constructed and configured, the instance needs to be +// initialized before invoking the Eval. +func New() *OPA { + opa := &OPA{ + memoryMinPages: 2, + memoryMaxPages: 0, + poolSize: uint32(runtime.GOMAXPROCS(0)), + logError: func(error) {}, + } + + return opa +} + +// Init initializes the SDK instance after the construction and +// configuration. If the configuration is invalid, it returns +// ErrInvalidConfig. +func (o *OPA) Init() (*OPA, error) { + if o.configErr != nil { + return nil, o.configErr + } + + o.pool = newPool(o.poolSize, o.memoryMinPages, o.memoryMaxPages) + + if len(o.policy) != 0 { + if err := o.pool.SetPolicyData(o.policy, o.data); err != nil { + return nil, err + } + } + + return o, nil +} + +// SetData updates the data for the subsequent Eval calls. Returns +// either ErrNotReady, ErrInvalidPolicyOrData, or ErrInternal if an +// error occurs. +func (o *OPA) SetData(v interface{}) error { + if o.pool == nil { + return ErrNotReady + } + + raw, err := json.Marshal(v) + if err != nil { + return fmt.Errorf("%v: %w", err, ErrInvalidPolicyOrData) + } + + o.mutex.Lock() + defer o.mutex.Unlock() + + return o.setPolicyData(o.policy, raw) +} + +// SetPolicy updates the policy for the subsequent Eval calls. +// Returns either ErrNotReady, ErrInvalidPolicy or ErrInternal if an +// error occurs. +func (o *OPA) SetPolicy(p []byte) error { + if o.pool == nil { + return ErrNotReady + } + + o.mutex.Lock() + defer o.mutex.Unlock() + + return o.setPolicyData(p, o.data) +} + +// SetPolicyData updates both the policy and data for the subsequent +// Eval calls. Returns either ErrNotReady, ErrInvalidPolicyOrData, or +// ErrInternal if an error occurs. +func (o *OPA) SetPolicyData(policy []byte, data *interface{}) error { + if o.pool == nil { + return ErrNotReady + } + + var raw []byte + if data != nil { + var err error + raw, err = json.Marshal(*data) + if err != nil { + return fmt.Errorf("%v: %w", err, ErrInvalidPolicyOrData) + } + } + + o.mutex.Lock() + defer o.mutex.Unlock() + + return o.setPolicyData(policy, raw) +} + +func (o *OPA) setPolicyData(policy []byte, data []byte) error { + if err := o.pool.SetPolicyData(policy, data); err != nil { + return err + } + + o.policy = policy + o.data = data + return nil +} + +// Eval evaluates the policy with the given input, returning the +// evaluation results. If no policy was configured at construction +// time nor set after, the function returns ErrNotReady. It returns +// ErrInternal if any other error occurs. +func (o *OPA) Eval(ctx context.Context, input *interface{}) (*Result, error) { + if o.pool == nil { + return nil, ErrNotReady + } + + instance, err := o.pool.Acquire(ctx) + if err != nil { + return nil, err + } + + defer o.pool.Release(instance) + + result, err := instance.Eval(ctx, input) + if err != nil { + return nil, fmt.Errorf("%v: %w", err, ErrInternal) + } + + return &Result{result}, nil +} + +// Close waits until all the pending evaluations complete and then +// releases all the resources allocated. Eval will return ErrClosed +// afterwards. +func (o *OPA) Close() { + if o.pool == nil { + return + } + + o.mutex.Lock() + defer o.mutex.Unlock() + + o.pool.Close() +} + +// EvalBool evaluates the boolean policy with the given input. The +// possible error values returned are as with Eval with addition of +// ErrUndefined indicating an undefined policy decision and +// ErrNonBoolean indicating a non-boolean policy decision. +func EvalBool(ctx context.Context, o *OPA, input *interface{}) (bool, error) { + rs, err := o.Eval(ctx, input) + if err != nil { + return false, err + } + + r, ok := rs.Result.([]interface{}) + if !ok || len(r) == 0 { + return false, ErrUndefined + } + + m, ok := r[0].(map[string]interface{}) + if !ok || len(m) != 1 { + return false, ErrNonBoolean + } + + var b bool + for _, v := range m { + b, ok = v.(bool) + break + } + + if !ok { + return false, ErrNonBoolean + } + + return b, nil +} diff --git a/internal/wasm/sdk/opa/opa_test.go b/internal/wasm/sdk/opa/opa_test.go new file mode 100644 index 0000000000..0a913d8288 --- /dev/null +++ b/internal/wasm/sdk/opa/opa_test.go @@ -0,0 +1,226 @@ +// Copyright 2020 The OPA Authors. All rights reserved. +// Use of this source code is governed by an Apache2 +// license that can be found in the LICENSE file. +package opa_test + +import ( + "context" + "fmt" + "reflect" + "testing" + + "github.com/open-policy-agent/opa/internal/wasm/sdk/opa" + "github.com/open-policy-agent/opa/rego" + "github.com/open-policy-agent/opa/util" +) + +func TestOPA(t *testing.T) { + type Eval struct { + NewPolicy string + NewData string + Input string + Result string + } + + tests := []struct { + Description string + Policy string + Query string + Data string + Evals []Eval + }{ + { + Description: "No input, no data, static policy", + Policy: `a = true`, + Query: "data.p.a = x", + Evals: []Eval{ + Eval{Result: `[{"x": true}]`}, + Eval{Result: `[{"x": true}]`}, + }, + }, + { + Description: "Only input changing", + Policy: `a = input`, + Query: "data.p.a = x", + Evals: []Eval{ + Eval{Input: "false", Result: `[{"x": false}]`}, + Eval{Input: "true", Result: `[{"x": true}]`}, + }, + }, + { + Description: "Only data changing", + Policy: `a = data.q`, + Query: "data.p.a = x", + Data: `{"q": false}`, + Evals: []Eval{ + Eval{Result: `[{"x": false}]`}, + Eval{NewData: `{"q": true}`, Result: `[{"x": true}]`}, + }, + }, + { + Description: "Only policy changing", + Policy: `a = data.q`, + Query: "data.p.a = x", + Data: `{"q": false, "r": true}`, + Evals: []Eval{ + Eval{Result: `[{"x": false}]`}, + Eval{NewPolicy: `a = data.r`, Result: `[{"x": true}]`}, + }, + }, + { + Description: "Policy and data changing", + Policy: `a = data.q`, + Query: "data.p.a = x", + Data: `{"q": 0, "r": 1}`, + Evals: []Eval{ + Eval{Result: `[{"x": 0}]`}, + Eval{NewPolicy: `a = data.r`, NewData: `{"q": 2, "r": 3}`, Result: `[{"x": 3}]`}, + }, + }, + { + Description: "Builtins", + Policy: `a = count(data.q) + sum(data.q)`, + Query: "data.p.a = x", + Evals: []Eval{ + Eval{NewData: `{"q": []}`, Result: `[{"x": 0}]`}, + Eval{NewData: `{"q": [1, 2]}`, Result: `[{"x": 5}]`}, + }, + }, + { + Description: "Undefined decision", + Policy: `a = true`, + Query: "data.p.b = x", + Evals: []Eval{ + Eval{Result: `[]`}, + }, + }, + } + + for _, test := range tests { + t.Run(test.Description, func(t *testing.T) { + policy := compileRegoToWasm(test.Policy, test.Query) + data := []byte(test.Data) + if len(data) == 0 { + data = nil + } + opa, err := opa.New(). + WithPolicyBytes(policy). + WithDataBytes(data). + WithMemoryLimits(131070, 0). + WithPoolSize(1). // Minimal pool size to test pooling. + Init() + if err != nil { + t.Fatal(err) + } + + // Execute each requested policy evaluation, with their inputs and updating data if requested. + + for _, eval := range test.Evals { + switch { + case eval.NewPolicy != "" && eval.NewData != "": + policy := compileRegoToWasm(eval.NewPolicy, test.Query) + data := parseJSON(eval.NewData) + if err := opa.SetPolicyData(policy, data); err != nil { + t.Errorf(err.Error()) + } + + case eval.NewPolicy != "": + policy := compileRegoToWasm(eval.NewPolicy, test.Query) + if err := opa.SetPolicy(policy); err != nil { + t.Errorf(err.Error()) + } + + case eval.NewData != "": + data := parseJSON(eval.NewData) + if err := opa.SetData(*data); err != nil { + t.Errorf(err.Error()) + } + } + + result, err := opa.Eval(context.Background(), parseJSON(eval.Input)) + if err != nil { + t.Errorf(err.Error()) + } + + if !reflect.DeepEqual(*parseJSON(eval.Result), result.Result) { + t.Errorf("Incorrect result: %v", result.Result) + } + } + + opa.Close() + }) + } +} + +func BenchmarkWasmRego(b *testing.B) { + policy := compileRegoToWasm("a = true", "data.p.a = x") + opa, _ := opa.New(). + WithPolicyBytes(policy). + WithMemoryLimits(131070, 2*131070). // TODO: For some reason unlimited memory slows down the eval_ctx_new(). + WithPoolSize(1). + Init() + + b.ReportAllocs() + b.ResetTimer() + + ctx := context.Background() + var input interface{} = make(map[string]interface{}) + + for i := 0; i < b.N; i++ { + if _, err := opa.Eval(ctx, &input); err != nil { + panic(err) + } + } +} + +func BenchmarkGoRego(b *testing.B) { + pq := compileRego(`package p + +a = true`, "data.p.a = x") + + b.ReportAllocs() + b.ResetTimer() + + input := make(map[string]interface{}) + + for i := 0; i < b.N; i++ { + if _, err := pq.Eval(context.Background(), rego.EvalInput(input)); err != nil { + panic(err) + } + } +} + +func compileRegoToWasm(policy string, query string) []byte { + module := fmt.Sprintf("package p\n%s", policy) + cr, err := rego.New( + rego.Query(query), + rego.Module("module.rego", module), + ).Compile(context.Background(), rego.CompilePartial(false)) + if err != nil { + panic(err) + } + + return cr.Bytes +} + +func compileRego(module string, query string) rego.PreparedEvalQuery { + rego := rego.New( + rego.Query(query), + rego.Module("module.rego", module), + ) + pq, err := rego.PrepareForEval(context.Background()) + if err != nil { + panic(err) + } + + return pq +} + +func parseJSON(s string) *interface{} { + if s == "" { + return nil + } + + v := util.MustUnmarshalJSON([]byte(s)) + return &v +} diff --git a/internal/wasm/sdk/opa/pool.go b/internal/wasm/sdk/opa/pool.go new file mode 100644 index 0000000000..819a193cbd --- /dev/null +++ b/internal/wasm/sdk/opa/pool.go @@ -0,0 +1,262 @@ +// Copyright 2020 The OPA Authors. All rights reserved. +// Use of this source code is governed by an Apache2 +// license that can be found in the LICENSE file. + +package opa + +import ( + "bytes" + "context" + "fmt" + "sync" +) + +// pool maintains a pool of WebAssemly VM instances. +type pool struct { + available chan struct{} + mutex sync.Mutex + initialized bool + closed bool + policy []byte + data []byte + memoryMinPages uint32 + memoryMaxPages uint32 + vms []*vm // All current VM instances, acquired or not. + acquired []bool + pendingReinit *vm + blockedReinit chan struct{} +} + +// newPool constructs a new pool with the pool and VM configuration provided. +func newPool(poolSize, memoryMinPages, memoryMaxPages uint32) *pool { + available := make(chan struct{}, poolSize) + for i := uint32(0); i < poolSize; i++ { + available <- struct{}{} + } + + return &pool{ + memoryMinPages: memoryMinPages, + memoryMaxPages: memoryMaxPages, + available: available, + vms: make([]*vm, 0), + acquired: make([]bool, 0), + } +} + +// Acquire obtains a VM from the pool, waiting if all VMms are in use +// and building one as necessary. Returns either ErrNotReady or +// ErrInternal if an error. +func (p *pool) Acquire(ctx context.Context) (*vm, error) { + select { + case <-ctx.Done(): + return nil, ctx.Err() + case <-p.available: + } + + p.mutex.Lock() + defer p.mutex.Unlock() + + if !p.initialized || p.closed { + return nil, ErrNotReady + } + + for i, vm := range p.vms { + if !p.acquired[i] { + p.acquired[i] = true + return vm, nil + } + } + + policy, data := p.policy, p.data + + p.mutex.Unlock() + vm, err := newVM(policy, data, p.memoryMinPages, p.memoryMaxPages) + p.mutex.Lock() + + if err != nil { + p.available <- struct{}{} + return nil, fmt.Errorf("%v: %w", err, ErrInternal) + } + + p.acquired = append(p.acquired, true) + p.vms = append(p.vms, vm) + return vm, nil +} + +// Release releases the VM back to the pool. +func (p *pool) Release(vm *vm) { + p.mutex.Lock() + + // If the policy data setting is waiting for this one, don't release it back to the general consumption. + // Note the reinit is responsible for pushing to available channel once done with the VM. + if vm == p.pendingReinit { + p.mutex.Unlock() + p.blockedReinit <- struct{}{} + return + } + + for i := range p.vms { + if p.vms[i] == vm { + p.acquired[i] = false + p.mutex.Unlock() + p.available <- struct{}{} + return + } + } + + // VM instance not found anymore, hence pool reconfigured and can release the VM. + + p.mutex.Unlock() + p.available <- struct{}{} + + vm.Close() +} + +// Reset re-initializes the vms within the pool with the new policy +// and data. The re-initialization takes place atomically: all new vms +// are constructed in advance before touching the pool. Returns +// either ErrNotReady, ErrInvalidPolicy or ErrInternal if an error +// occurs. +func (p *pool) SetPolicyData(policy []byte, data []byte) error { + p.mutex.Lock() + + if !p.initialized { + vm, err := newVM(policy, data, p.memoryMinPages, p.memoryMaxPages) + if err == nil { + p.initialized = true + p.vms = append(p.vms, vm) + p.acquired = append(p.acquired, false) + p.policy, p.data = policy, data + } else { + err = fmt.Errorf("%v: %w", err, ErrInvalidPolicyOrData) + } + + p.mutex.Unlock() + return err + } + + if p.closed { + p.mutex.Unlock() + return ErrNotReady + } + + currentPolicy, currentData := p.policy, p.data + p.mutex.Unlock() + + if bytes.Equal(policy, currentPolicy) && bytes.Equal(data, currentData) { + return nil + + } + + err := p.setPolicyData(policy, data) + if err != nil { + return fmt.Errorf("%v: %w", err, ErrInternal) + } + + return nil +} + +// setPolicyData reinitializes the VMs one at a time. +func (p *pool) setPolicyData(policy []byte, data []byte) error { + for i, activations := 0, 0; true; i++ { + vm := p.wait(i) + if vm == nil { + // All have been converted. + return nil + } + + if err := vm.SetPolicyData(policy, data); err != nil { + // No guarantee about the VM state after an error; hence, remove. + p.remove(i) + p.Release(vm) + + // After the first successful activation, proceed through all the VMs, ignoring the remaining errors. + if activations == 0 { + return err + } + } else { + p.Release(vm) + } + + // Activate the policy and data, now that a single VM has been reset without errors. + + if activations == 0 { + p.activate(policy, data) + } + + activations++ + } + + return nil +} + +// Close waits for all the evaluations to finish and then releases the VMs. +func (p *pool) Close() { + for range p.vms { + <-p.available + } + + p.mutex.Lock() + defer p.mutex.Unlock() + + for _, vm := range p.vms { + if vm != nil { + vm.Close() + } + } + + p.closed = true + p.vms = nil +} + +// wait steals the i'th VM instance. The VM has to be released afterwards. +func (p *pool) wait(i int) *vm { + p.mutex.Lock() + defer p.mutex.Unlock() + + if i == len(p.vms) { + return nil + } + + vm := p.vms[i] + isActive := p.acquired[i] + p.acquired[i] = true + + if isActive { + p.blockedReinit = make(chan struct{}, 1) + p.pendingReinit = vm + } + + p.mutex.Unlock() + + if isActive { + <-p.blockedReinit + } else { + <-p.available + } + + p.mutex.Lock() + p.pendingReinit = nil + return vm +} + +// remove removes the i'th vm. +func (p *pool) remove(i int) { + p.mutex.Lock() + defer p.mutex.Unlock() + + n := len(p.vms) + if n > 1 { + p.vms[i] = p.vms[n-1] + } + + p.vms = p.vms[0 : n-1] + p.acquired = p.acquired[0 : n-1] +} + +func (p *pool) activate(policy []byte, data []byte) { + p.mutex.Lock() + defer p.mutex.Unlock() + + p.policy, p.data = policy, data +} diff --git a/internal/wasm/sdk/opa/vm.go b/internal/wasm/sdk/opa/vm.go new file mode 100644 index 0000000000..a029b0eb18 --- /dev/null +++ b/internal/wasm/sdk/opa/vm.go @@ -0,0 +1,416 @@ +// Copyright 2020 The OPA Authors. All rights reserved. +// Use of this source code is governed by an Apache2 +// license that can be found in the LICENSE file. + +package opa + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "strconv" + "time" + + "github.com/open-policy-agent/opa/ast" + "github.com/open-policy-agent/opa/metrics" + "github.com/open-policy-agent/opa/topdown" + "github.com/open-policy-agent/opa/topdown/builtins" + wasm "github.com/wasmerio/go-ext-wasm/wasmer" +) + +type vm struct { + instance *wasm.Instance // Pointer to avoid unintented destruction (triggering finalizers within). + policy []byte + data []byte + memory *wasm.Memory + memoryMin uint32 + memoryMax uint32 + bctx *topdown.BuiltinContext + builtins map[int32]topdown.BuiltinFunc + builtinResult *ast.Term + baseHeapPtr int32 + dataAddr int32 + evalHeapPtr int32 + evalHeapTop int32 + eval func(...interface{}) (wasm.Value, error) + evalCtxGetResult func(...interface{}) (wasm.Value, error) + evalCtxNew func(...interface{}) (wasm.Value, error) + evalCtxSetData func(...interface{}) (wasm.Value, error) + evalCtxSetInput func(...interface{}) (wasm.Value, error) + free func(...interface{}) (wasm.Value, error) + heapPtrGet func(...interface{}) (wasm.Value, error) + heapPtrSet func(...interface{}) (wasm.Value, error) + heapTopGet func(...interface{}) (wasm.Value, error) + heapTopSet func(...interface{}) (wasm.Value, error) + jsonDump func(...interface{}) (wasm.Value, error) + jsonParse func(...interface{}) (wasm.Value, error) + malloc func(...interface{}) (wasm.Value, error) +} + +func newVM(policy []byte, data []byte, memoryMin, memoryMax uint32) (*vm, error) { + memory, err := wasm.NewMemory(memoryMin, memoryMax) + if err != nil { + return nil, err + } + + imports, err := opaFunctions(wasm.NewImports()) + if err != nil { + return nil, err + } + + imports, err = imports.AppendMemory("memory", memory) + if err != nil { + panic(err) + } + + i, err := wasm.NewInstanceWithImports(policy, imports) + if err != nil { + return nil, err + } + + v := &vm{ + instance: &i, + policy: policy, + data: data, + memory: memory, + memoryMin: memoryMin, + memoryMax: memoryMax, + builtins: make(map[int32]topdown.BuiltinFunc), + dataAddr: 0, + eval: i.Exports["eval"], + evalCtxGetResult: i.Exports["opa_eval_ctx_get_result"], + evalCtxNew: i.Exports["opa_eval_ctx_new"], + evalCtxSetData: i.Exports["opa_eval_ctx_set_data"], + evalCtxSetInput: i.Exports["opa_eval_ctx_set_input"], + free: i.Exports["opa_free"], + heapPtrGet: i.Exports["opa_heap_ptr_get"], + heapPtrSet: i.Exports["opa_heap_ptr_set"], + heapTopGet: i.Exports["opa_heap_top_get"], + heapTopSet: i.Exports["opa_heap_top_set"], + jsonDump: i.Exports["opa_json_dump"], + jsonParse: i.Exports["opa_json_parse"], + malloc: i.Exports["opa_malloc"], + } + + // Initialize the heap. + + if _, err := v.malloc(0); err != nil { + return nil, err + } + + if v.baseHeapPtr, err = v.getHeapState(); err != nil { + return nil, err + } + + if data != nil { + if v.dataAddr, err = v.toRegoJSON(data, true); err != nil { + return nil, err + } + } + + if v.evalHeapPtr, err = v.getHeapState(); err != nil { + return nil, err + } + + // For the opa builtin functions to access the instance. + i.SetContextData(v) + + // Construct the builtin id to name mappings. + + val, err := i.Exports["builtins"]() + if err != nil { + return nil, err + } + + builtins, err := v.fromRegoJSON(val.ToI32(), true) + if err != nil { + return nil, err + } + + for name, id := range builtins.(map[string]interface{}) { + f := topdown.GetBuiltin(name) + if f == nil { + return nil, fmt.Errorf("builtin '%s' not found", name) + } + + n, err := id.(json.Number).Int64() + if err != nil { + panic(err) + } + + v.builtins[int32(n)] = f + } + + return v, nil +} + +func (i *vm) Eval(ctx context.Context, input *interface{}) (interface{}, error) { + if err := i.setHeapState(i.evalHeapPtr); err != nil { + return nil, err + } + + defer func() { + i.bctx = nil + }() + + // Parse the input JSON and activate it with the data. + + addr, err := i.evalCtxNew() + if err != nil { + return nil, err + } + + ctxAddr := addr.ToI32() + + if i.dataAddr != 0 { + if _, err := i.evalCtxSetData(ctxAddr, i.dataAddr); err != nil { + return nil, err + } + } + + if input != nil { + inputAddr, err := i.toRegoJSON(*input, false) + if err != nil { + return nil, err + } + + if _, err := i.evalCtxSetInput(ctxAddr, inputAddr); err != nil { + return nil, err + } + } + + // Evaluate the policy. + func() { + defer func() { + if e := recover(); e != nil { + switch e := e.(type) { + case abortError: + err = errors.New(e.message) + case builtinError: + err = e.err + default: + panic(e) + } + + } + }() + _, err = i.eval(ctxAddr) + }() + + if err != nil { + return nil, err + } + + resultAddr, err := i.evalCtxGetResult(ctxAddr) + if err != nil { + return nil, err + } + + result, err := i.fromRegoJSON(resultAddr.ToI32(), false) + + // Skip free'ing input and result JSON as the heap will be reset next round anyway. + + return result, err +} + +func (i *vm) SetPolicyData(policy []byte, data []byte) error { + if !bytes.Equal(policy, i.policy) { + // Swap the instance to a new one, with new policy. + + n, err := newVM(policy, data, i.memoryMin, i.memoryMax) + if err != nil { + return err + } + + i.Close() + + *i = *n + return nil + } + + i.data = data + i.dataAddr = 0 + + var err error + if err = i.setHeapState(i.baseHeapPtr); err != nil { + return err + } + + if data != nil { + if i.dataAddr, err = i.toRegoJSON(data, true); err != nil { + return err + } + } + + if i.evalHeapPtr, err = i.getHeapState(); err != nil { + return err + } + + return nil +} + +func (i *vm) Close() { + i.memory.Close() + i.instance.Close() +} + +type abortError struct { + message string +} + +// Abort is invoked by the policy if an internal error occurs during +// the policy execution. +func (i *vm) Abort(arg int32) { + data := i.memory.Data()[arg:] + n := bytes.IndexByte(data, 0) + if n == -1 { + panic("invalid abort argument") + } + + panic(abortError{message: string(data[0:n])}) +} + +type builtinError struct { + err error +} + +// Builtin executes a builtin for the policy. +func (i *vm) Builtin(builtinID, ctx int32, args ...int32) int32 { + + // TODO: Returning proper errors instead of panicing. + // TODO: To avoid growing the heap with every built-in call, recycle the JSON buffers since the free implementation is no-op. + + convertedArgs := make([]*ast.Term, len(args)) + for j, arg := range args { + x, err := i.fromRegoJSON(arg, true) + if err != nil { + panic(builtinError{err: err}) + } + + y, err := ast.InterfaceToValue(x) + if err != nil { + panic(builtinError{err: err}) + } + + convertedArgs[j] = ast.NewTerm(y) + } + + if i.bctx == nil { + i.bctx = &topdown.BuiltinContext{ + Context: context.Background(), + Cancel: nil, + Runtime: nil, + Time: ast.NumberTerm(json.Number(strconv.FormatInt(time.Now().UnixNano(), 10))), + Metrics: metrics.New(), + Cache: make(builtins.Cache), + Location: nil, + Tracers: nil, + QueryID: 0, + ParentID: 0, + } + } + + err := i.builtins[builtinID](*i.bctx, convertedArgs, i.iter) + if err != nil { + panic(builtinError{err: err}) + } + + result, err := ast.JSON(i.builtinResult.Value) + if err != nil { + panic(builtinError{err: err}) + } + + addr, err := i.toRegoJSON(result, true) + if err != nil { + panic(builtinError{err: err}) + } + + return addr +} + +func (i *vm) iter(result *ast.Term) error { + i.builtinResult = result + return nil +} + +// fromRegoJSON converts Rego JSON to go native JSON. +func (i *vm) fromRegoJSON(addr int32, free bool) (interface{}, error) { + serialized, err := i.jsonDump(addr) + if err != nil { + return nil, err + } + + data := i.memory.Data()[serialized.ToI32():] + n := bytes.IndexByte(data, 0) + if n < 0 { + n = 0 + } + + // Parse the result into go types. + + decoder := json.NewDecoder(bytes.NewReader(data[0:n])) + decoder.UseNumber() + + var result interface{} + if err := decoder.Decode(&result); err != nil { + return nil, err + } + + if free { + if _, err := i.free(serialized.ToI32()); err != nil { + return nil, err + } + } + + return result, nil +} + +// toRegoJSON converts go native JSON to Rego JSON. +func (i *vm) toRegoJSON(v interface{}, free bool) (int32, error) { + raw, ok := v.([]byte) + if !ok { + var err error + raw, err = json.Marshal(v) + if err != nil { + return 0, err + } + } + + n := int32(len(raw)) + pos, err := i.malloc(n) + if err != nil { + return 0, err + } + + p := pos.ToI32() + copy(i.memory.Data()[p:p+n], raw) + + addr, err := i.jsonParse(p, n) + if err != nil { + return 0, err + } + + if free { + if _, err := i.free(p); err != nil { + return 0, err + } + } + + return addr.ToI32(), nil +} + +func (i *vm) getHeapState() (int32, error) { + ptr, err := i.heapPtrGet() + if err != nil { + return 0, err + } + + return ptr.ToI32(), nil +} + +func (i *vm) setHeapState(ptr int32) error { + _, err := i.heapPtrSet(ptr) + return err +}