├── .github └── workflows │ └── pull_requests.yaml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── Makefile ├── README.md ├── build ├── check-fmt.sh ├── check-lint.sh ├── check-vet.sh ├── run-goimports.sh └── utils.sh ├── examples ├── basic │ ├── example-1.rego │ ├── example-1.wasm │ ├── example-2.rego │ ├── example-2.wasm │ └── main.go └── loaders │ ├── bundle.tgz │ └── main.go ├── go.mod ├── go.sum └── opa ├── bindings.go ├── config.go ├── errors.go ├── file ├── config.go ├── loader.go └── loader_test.go ├── http ├── config.go ├── loader.go ├── loader_test.go └── util.go ├── loader.go ├── opa.go ├── opa_test.go ├── pool.go ├── tools.go └── vm.go /.github/workflows/pull_requests.yaml: -------------------------------------------------------------------------------- 1 | name: Check PR 2 | 3 | on: 4 | pull_request: 5 | 6 | jobs: 7 | tests: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | - name: Execute tests 12 | run: make all 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # development environment 2 | .DS_Store 3 | .vscode 4 | .idea 5 | *~ 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. This 4 | project adheres to [Semantic Versioning](http://semver.org/). 5 | 6 | ## Unreleased 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | GO := go 2 | 3 | ###################################################### 4 | # 5 | # Development targets 6 | # 7 | ###################################################### 8 | 9 | .PHONY: all 10 | all: test check 11 | 12 | .PHONY: test 13 | test: 14 | $(GO) test ./... 15 | 16 | .PHONY: check 17 | check: check-fmt check-vet check-lint 18 | 19 | .PHONY: check-fmt 20 | check-fmt: 21 | ./build/check-fmt.sh 22 | 23 | .PHONY: check-vet 24 | check-vet: 25 | ./build/check-vet.sh 26 | 27 | .PHONY: check-lint 28 | check-lint: 29 | ./build/check-lint.sh 30 | 31 | .PHONY: fmt 32 | fmt: 33 | ./build/run-fmt.sh 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **Work in Progress -- Contributions welcome!** 2 | 3 | # Open Policy Agent WebAssemby Go SDK 4 | This is the source for the Open Policy Agent WebAssembly Go SDK which 5 | is a small go library for using WebAssembly (wasm) compiled [Open 6 | Policy Agent](https://www.openpolicyagent.org/) Rego policies. 7 | 8 | -------------------------------------------------------------------------------- /build/check-fmt.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | OPA_DIR=$(dirname "${BASH_SOURCE}")/.. 4 | source ${OPA_DIR}/build/utils.sh 5 | 6 | function opa::check_fmt() { 7 | exec 5>&1 8 | exit_code=0 9 | out=$(${OPA_DIR}/build/run-goimports.sh -format-only -d $(opa::all_go_files) | tee >(cat - >&5)) 10 | if [ ! -z "$out" ]; then 11 | exit_code=1 12 | fi 13 | exit $exit_code 14 | } 15 | 16 | opa::check_fmt 17 | -------------------------------------------------------------------------------- /build/check-lint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | OPA_DIR=$( 4 | dir=$(dirname "${BASH_SOURCE}")/.. 5 | cd "$dir" 6 | pwd 7 | ) 8 | source $OPA_DIR/build/utils.sh 9 | 10 | 11 | function opa::check_lint() { 12 | exec 5>&1 13 | exit_code=0 14 | for pkg in $(opa::go_packages); do 15 | __output=$(go run golang.org/x/lint/golint $pkg | tee >(cat - >&5)) 16 | if [ ! -z "$__output" ]; then 17 | exit_code=1 18 | fi 19 | done 20 | exit $exit_code 21 | } 22 | 23 | opa::check_lint 24 | -------------------------------------------------------------------------------- /build/check-vet.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | OPA_DIR=$( 4 | dir=$(dirname "${BASH_SOURCE}")/.. 5 | cd "$dir" 6 | pwd 7 | ) 8 | source $OPA_DIR/build/utils.sh 9 | 10 | function opa::check_vet() { 11 | exec 5>&1 12 | rc=0 13 | exit_code=0 14 | for pkg in $(opa::go_packages); do 15 | go vet $pkg || rc=$? 16 | if [[ $rc != 0 ]]; then 17 | exit_code=1 18 | fi 19 | done 20 | exit $exit_code 21 | } 22 | 23 | opa::check_vet 24 | -------------------------------------------------------------------------------- /build/run-goimports.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | GO111MODULE=on GOOS="" GOARCH="" go run golang.org/x/tools/cmd/goimports -local github.com/open-policy-agent/golang-opa-wasm $@ 4 | -------------------------------------------------------------------------------- /build/utils.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -o errexit 4 | set -o pipefail 5 | set -o nounset 6 | 7 | export GO111MODULE=on 8 | 9 | function opa::go_packages() { 10 | for pkg in $(go list ./.../ 2>/dev/null | grep -v vendor); do 11 | echo $pkg 12 | done 13 | } 14 | 15 | function opa::go_files_in_package() { 16 | dir=$(go list -f '{{ .Dir }}' $1) 17 | for file in $(go list -f '{{ join .GoFiles "\n" }}' $1); do 18 | echo $dir/$file 19 | done 20 | for file in $(go list -f '{{ join .TestGoFiles "\n" }}' $1); do 21 | echo $dir/$file 22 | done 23 | } 24 | 25 | function opa::all_go_files() { 26 | FILES="" 27 | for pkg in $(opa::go_packages); do 28 | for file in $(opa::go_files_in_package $pkg); do 29 | FILES+=" ${file}" 30 | done 31 | done 32 | echo "${FILES}" 33 | } 34 | -------------------------------------------------------------------------------- /examples/basic/example-1.rego: -------------------------------------------------------------------------------- 1 | package example 2 | 3 | allow = input.foo 4 | -------------------------------------------------------------------------------- /examples/basic/example-1.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-policy-agent/golang-opa-wasm/8041bec28e319aad31fd4632bae46e47727f3ddc/examples/basic/example-1.wasm -------------------------------------------------------------------------------- /examples/basic/example-2.rego: -------------------------------------------------------------------------------- 1 | package example 2 | 3 | allow = input.bar 4 | -------------------------------------------------------------------------------- /examples/basic/example-2.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-policy-agent/golang-opa-wasm/8041bec28e319aad31fd4632bae46e47727f3ddc/examples/basic/example-2.wasm -------------------------------------------------------------------------------- /examples/basic/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The OPA Authors. All rights reserved. 2 | // Use of this source code is governed by an Apache2 3 | // license that can be found in the LICENSE file. 4 | package main 5 | 6 | import ( 7 | "context" 8 | "fmt" 9 | "io/ioutil" 10 | "os" 11 | "path" 12 | 13 | "github.com/open-policy-agent/golang-opa-wasm/opa" 14 | ) 15 | 16 | // main demonstrates the loading and executnig of OPA produced wasm 17 | // policy binary. To execute run 'go run main.go .' in the directory 18 | // of the main.go. 19 | func main() { 20 | if len(os.Args) < 2 { 21 | fmt.Printf("%s: first argument must a path to a directory with example-1.wasm and example-2.wasm.\n", os.Args[0]) 22 | return 23 | } 24 | 25 | directory := os.Args[1] 26 | 27 | // Setup the SDK 28 | 29 | policy, err := ioutil.ReadFile(path.Join(directory, "example-1.wasm")) 30 | if err != nil { 31 | fmt.Printf("error: %v\n", err) 32 | return 33 | } 34 | 35 | rego, err := opa.New().WithPolicyBytes(policy).Init() 36 | if err != nil { 37 | fmt.Printf("error: %v\n", err) 38 | return 39 | } 40 | 41 | defer rego.Close() 42 | 43 | // Evaluate the policy once. 44 | 45 | var input interface{} = map[string]interface{}{ 46 | "foo": true, 47 | "bar": false, 48 | } 49 | 50 | ctx := context.Background() 51 | result, err := rego.Eval(ctx, &input) 52 | if err != nil { 53 | fmt.Printf("error: %v\n", err) 54 | return 55 | } 56 | 57 | fmt.Printf("Policy 1 result: %v\n", result) 58 | 59 | resultBool, err := opa.EvalBool(ctx, rego, &input) 60 | if err != nil { 61 | fmt.Printf("error: %v\n", err) 62 | return 63 | } 64 | 65 | fmt.Printf("Policy 1 boolean result: %v\n", resultBool) 66 | 67 | // Update the policy on the fly. 68 | 69 | policy, err = ioutil.ReadFile(path.Join(directory, "example-2.wasm")) 70 | if err != nil { 71 | fmt.Printf("error: %v\n", err) 72 | return 73 | } 74 | 75 | // Evaluate the new policy. 76 | 77 | if err := rego.SetPolicy(policy); err != nil { 78 | fmt.Printf("error: %v\n", err) 79 | return 80 | } 81 | 82 | result, err = rego.Eval(ctx, &input) 83 | if err != nil { 84 | fmt.Printf("error: %v\n", err) 85 | return 86 | } 87 | 88 | fmt.Printf("Policy 2 result: %v\n", result) 89 | 90 | resultBool, err = opa.EvalBool(ctx, rego, &input) 91 | if err != nil { 92 | fmt.Printf("error: %v\n", err) 93 | return 94 | } 95 | 96 | fmt.Printf("Policy 2 boolean result: %v\n", resultBool) 97 | } 98 | -------------------------------------------------------------------------------- /examples/loaders/bundle.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-policy-agent/golang-opa-wasm/8041bec28e319aad31fd4632bae46e47727f3ddc/examples/loaders/bundle.tgz -------------------------------------------------------------------------------- /examples/loaders/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The OPA Authors. All rights reserved. 2 | // Use of this source code is governed by an Apache2 3 | // license that can be found in the LICENSE file. 4 | package main 5 | 6 | import ( 7 | "context" 8 | "fmt" 9 | gohttp "net/http" 10 | "net/url" 11 | "os" 12 | "time" 13 | 14 | "github.com/open-policy-agent/golang-opa-wasm/opa" 15 | "github.com/open-policy-agent/golang-opa-wasm/opa/file" 16 | "github.com/open-policy-agent/golang-opa-wasm/opa/http" 17 | ) 18 | 19 | var ( 20 | loader opa.Loader 21 | rego *opa.OPA 22 | ) 23 | 24 | // main loads a bundle either from a file or HTTP server. 25 | // 26 | // In the directory of the main.go, execute 'go run main.go 27 | // bundle.tgz' to load the accompanied bundle file. Similarly, execute 28 | // 'go run main.go http://url/to/bundle.tgz' to test the HTTP 29 | // downloading from a HTTP server. 30 | func main() { 31 | if len(os.Args) < 2 { 32 | fmt.Printf("provide URL or file\n") 33 | return 34 | } 35 | 36 | url := os.Args[1] 37 | token := "" 38 | if len(os.Args) >= 3 { 39 | token = os.Args[2] 40 | } 41 | 42 | // Setup the SDK, either with HTTP bundle loader or file bundle loader. 43 | 44 | if err := setup(url, token); err != nil { 45 | fmt.Printf("error: %v\n", err) 46 | return 47 | } 48 | 49 | defer cleanup() 50 | 51 | // Evaluate the policy. 52 | 53 | var input interface{} = map[string]interface{}{ 54 | "foo": true, 55 | } 56 | 57 | ctx := context.Background() 58 | result, err := rego.Eval(ctx, &input) 59 | if err != nil { 60 | fmt.Printf("error: %v\n", err) 61 | return 62 | } 63 | 64 | fmt.Printf("Policy result: %v\n", result) 65 | } 66 | 67 | func setup(u string, token string) error { 68 | r, err := opa.New().Init() 69 | if err != nil { 70 | return err 71 | } 72 | 73 | url, err := url.Parse(u) 74 | if err != nil { 75 | return err 76 | } 77 | 78 | var l opa.Loader 79 | 80 | switch url.Scheme { 81 | case "http", "https": 82 | l, err = http.New(r). 83 | WithURL(url.String()). 84 | WithPrepareRequest(func(req *gohttp.Request) error { 85 | if token != "" { 86 | req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token)) 87 | } 88 | return nil 89 | }). 90 | WithInterval(30*time.Second, 60*time.Second). 91 | Init() 92 | case "file", "": 93 | l, err = file.New(r). 94 | WithFile(url.String()). 95 | WithInterval(10 * time.Second). 96 | Init() 97 | } 98 | 99 | if err != nil { 100 | return err 101 | } 102 | 103 | if err := l.Start(context.Background()); err != nil { 104 | return err 105 | } 106 | 107 | rego, loader = r, l 108 | return nil 109 | } 110 | 111 | func cleanup() { 112 | loader.Close() 113 | rego.Close() 114 | } 115 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/open-policy-agent/golang-opa-wasm 2 | 3 | go 1.12 4 | 5 | require ( 6 | github.com/OneOfOne/xxhash v1.2.8 // indirect 7 | github.com/ghodss/yaml v1.0.0 // indirect 8 | github.com/kr/pretty v0.1.0 // indirect 9 | github.com/open-policy-agent/opa v0.23.1-0.20201009130859-4851c4b0e8ad 10 | github.com/pkg/errors v0.9.1 // indirect 11 | github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 // indirect 12 | github.com/wasmerio/go-ext-wasm v0.3.1 13 | golang.org/x/lint v0.0.0-20200302205851-738671d3881b 14 | golang.org/x/tools v0.0.0-20201009032223-96877f285f7e 15 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect 16 | gopkg.in/yaml.v2 v2.3.0 // indirect 17 | ) 18 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 2 | github.com/OneOfOne/xxhash v1.2.7 h1:fzrmmkskv067ZQbd9wERNGuxckWw67dyzoMG62p7LMo= 3 | github.com/OneOfOne/xxhash v1.2.7/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= 4 | github.com/OneOfOne/xxhash v1.2.8 h1:31czK/TI9sNkxIKfaUfGlU47BAxQ0ztGgd9vPyqimf8= 5 | github.com/OneOfOne/xxhash v1.2.8/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= 6 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0= 7 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 8 | github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= 9 | github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= 10 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 11 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 12 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 13 | github.com/ghodss/yaml v0.0.0-20180820084758-c7ce16629ff4 h1:bRzFpEzvausOAt4va+I/22BZ1vXDtERngp0BNYDKej0= 14 | github.com/ghodss/yaml v0.0.0-20180820084758-c7ce16629ff4/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 15 | github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= 16 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 17 | github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= 18 | github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= 19 | github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= 20 | github.com/golang/protobuf v0.0.0-20181025225059-d3de96c4c28e h1:+RasoGgq0ljH8THLqlTAOxyz87eBeA6cx7sD0KnGQGQ= 21 | github.com/golang/protobuf v0.0.0-20181025225059-d3de96c4c28e/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0= 22 | github.com/gorilla/mux v0.0.0-20181024020800-521ea7b17d02 h1:hsoQua/9DqRrTqNB9E0hbJLp1DctU92ZmRo3cF6reyE= 23 | github.com/gorilla/mux v0.0.0-20181024020800-521ea7b17d02/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= 24 | github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= 25 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 26 | github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= 27 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 28 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 29 | github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= 30 | github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 31 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 32 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 33 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 34 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 35 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 36 | github.com/mattn/go-runewidth v0.0.0-20181025052659-b20a3daf6a39 h1:0E3wlIAcvD6zt/8UJgTd4JMT6UQhsnYyjCIqllyVLbs= 37 | github.com/mattn/go-runewidth v0.0.0-20181025052659-b20a3daf6a39/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= 38 | github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= 39 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 40 | github.com/olekukonko/tablewriter v0.0.1 h1:b3iUnf1v+ppJiOfNX4yxxqfWKMQPZR5yoh8urCTFX88= 41 | github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= 42 | github.com/open-policy-agent/opa v0.23.1-0.20201009130859-4851c4b0e8ad h1:wb85BZaIyTnE5BtMTYoNzoqlv+Gd1COzltXWFzdVA10= 43 | github.com/open-policy-agent/opa v0.23.1-0.20201009130859-4851c4b0e8ad/go.mod h1:qEyD/i8j+RQettHGp4f86yjrjvv+ZYia+JHCMv2G7wA= 44 | github.com/peterh/liner v0.0.0-20170211195444-bf27d3ba8e1d h1:zapSxdmZYY6vJWXFKLQ+MkI+agc+HQyfrCGowDSHiKs= 45 | github.com/peterh/liner v0.0.0-20170211195444-bf27d3ba8e1d/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc= 46 | github.com/pkg/errors v0.0.0-20181023235946-059132a15dd0 h1:R+lX9nKwNd1n7UE5SQAyoorREvRn3aLF6ZndXBoIWqY= 47 | github.com/pkg/errors v0.0.0-20181023235946-059132a15dd0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 48 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 49 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 50 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 51 | github.com/prometheus/client_golang v0.0.0-20181025174421-f30f42803563 h1:dBs8k8qNuGuW/owkqQ33ppcjCORmu5LhKPPfavb80EE= 52 | github.com/prometheus/client_golang v0.0.0-20181025174421-f30f42803563/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 53 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8= 54 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 55 | github.com/prometheus/common v0.0.0-20181020173914-7e9e6cabbd39 h1:Cto4X6SVMWRPBkJ/3YHn1iDGDGc/Z+sW+AEMKHMVvN4= 56 | github.com/prometheus/common v0.0.0-20181020173914-7e9e6cabbd39/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= 57 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d h1:GoAlyOgbOEIFdaDqxJVlbOQ1DtGmZWs/Qau0hIlk+WQ= 58 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 59 | github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a h1:9ZKAASQSHhDYGoxY8uLVpewe1GDZ2vu2Tr/vTdVAkFQ= 60 | github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= 61 | github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 h1:MkV+77GLUNo5oJ0jf870itWm3D0Sjh7+Za9gazKc5LQ= 62 | github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= 63 | github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= 64 | github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 65 | github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= 66 | github.com/sirupsen/logrus v1.4.1 h1:GL2rEmy6nsikmW0r8opw9JIRScdMF5hA8cOYLH7In1k= 67 | github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= 68 | github.com/spf13/cobra v0.0.0-20181021141114-fe5e611709b0 h1:BgSbPgT2Zu8hDen1jJDGLWO8voaSRVrwsk18Q/uSh5M= 69 | github.com/spf13/cobra v0.0.0-20181021141114-fe5e611709b0/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= 70 | github.com/spf13/pflag v0.0.0-20181024212040-082b515c9490 h1:EmIGPbInxgMLEZd2f2MZwv0lCYiAv93kztj4caWSUZA= 71 | github.com/spf13/pflag v0.0.0-20181024212040-082b515c9490/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 72 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 73 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 74 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 75 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 76 | github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= 77 | github.com/wasmerio/go-ext-wasm v0.3.1 h1:G95XP3fE2FszQSwIU+fHPBYzD0Csmd2ef33snQXNA5Q= 78 | github.com/wasmerio/go-ext-wasm v0.3.1/go.mod h1:VGyarTzasuS7k5KhSIGpM3tciSZlkP31Mp9VJTHMMeI= 79 | github.com/yashtewari/glob-intersection v0.0.0-20180916065949-5c77d914dd0b h1:vVRagRXf67ESqAb72hG2C/ZwI8NtJF2u2V76EsuOHGY= 80 | github.com/yashtewari/glob-intersection v0.0.0-20180916065949-5c77d914dd0b/go.mod h1:HptNXiXVDcJjXe9SqMd0v2FsL9f8dz4GnXgltU6q/co= 81 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 82 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 83 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 84 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 85 | golang.org/x/lint v0.0.0-20181023182221-1baf3a9d7d67 h1:I+72n01vnlWdNIgza2K2Ykpj383VsU74Zg9q6Xmnl9Q= 86 | golang.org/x/lint v0.0.0-20181023182221-1baf3a9d7d67/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 87 | golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k= 88 | golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 89 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 90 | golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= 91 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 92 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 93 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 94 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 95 | golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 96 | golang.org/x/net v0.0.0-20200927032502-5d4f70055728 h1:5wtQIAulKU5AbLQOkjxl32UufnIOqgBX72pS0AV14H0= 97 | golang.org/x/net v0.0.0-20200927032502-5d4f70055728/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 98 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 99 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 100 | golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 101 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 102 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 103 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 104 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= 105 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 106 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 107 | golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= 108 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 109 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 110 | golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 111 | golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72 h1:bw9doJza/SFBEweII/rHQh338oozWyiFsBRHtrflcws= 112 | golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 113 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 114 | golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 115 | golang.org/x/tools v0.0.0-20201009032223-96877f285f7e h1:G1acLyqfyttmexrW7XPhzsaS8m6s+P9XsW9djwh10s4= 116 | golang.org/x/tools v0.0.0-20201009032223-96877f285f7e/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= 117 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 118 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 119 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= 120 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 121 | google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 122 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 123 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 124 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= 125 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 126 | gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= 127 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 128 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 129 | gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= 130 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 131 | gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= 132 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 133 | -------------------------------------------------------------------------------- /opa/bindings.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The OPA Authors. All rights reserved. 2 | // Use of this source code is governed by an Apache2 3 | // license that can be found in the LICENSE file. 4 | 5 | package opa 6 | 7 | // #include 8 | // 9 | // extern void opa_abort(void *context, int32_t addr); 10 | // extern int32_t opa_builtin0(void *context, int32_t builtin_id, int32_t ctx); 11 | // extern int32_t opa_builtin1(void *context, int32_t builtin_id, int32_t ctx, int32_t arg0); 12 | // extern int32_t opa_builtin2(void *context, int32_t builtin_id, int32_t ctx, int32_t arg0, int32_t arg1); 13 | // extern int32_t opa_builtin3(void *context, int32_t builtin_id, int32_t ctx, int32_t arg0, int32_t arg1, int32_t arg2); 14 | // 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); 15 | import "C" 16 | 17 | import ( 18 | "unsafe" 19 | 20 | wasm "github.com/wasmerio/go-ext-wasm/wasmer" 21 | ) 22 | 23 | func opaFunctions(imports *wasm.Imports) (*wasm.Imports, error) { 24 | imports, err := imports.AppendFunction("opa_abort", opa_abort, C.opa_abort) 25 | if err != nil { 26 | return nil, err 27 | } 28 | 29 | imports, err = imports.AppendFunction("opa_builtin0", opa_builtin0, C.opa_builtin0) 30 | if err != nil { 31 | return nil, err 32 | } 33 | 34 | imports, err = imports.AppendFunction("opa_builtin1", opa_builtin1, C.opa_builtin1) 35 | if err != nil { 36 | return nil, err 37 | } 38 | 39 | imports, err = imports.AppendFunction("opa_builtin2", opa_builtin2, C.opa_builtin2) 40 | if err != nil { 41 | return nil, err 42 | } 43 | 44 | imports, err = imports.AppendFunction("opa_builtin3", opa_builtin3, C.opa_builtin3) 45 | if err != nil { 46 | return nil, err 47 | } 48 | 49 | return imports.AppendFunction("opa_builtin4", opa_builtin4, C.opa_builtin4) 50 | } 51 | 52 | //export opa_abort 53 | func opa_abort(ctx unsafe.Pointer, addr int32) { 54 | getVM(ctx).Abort(addr) 55 | } 56 | 57 | //export opa_builtin0 58 | func opa_builtin0(ctx unsafe.Pointer, builtinID, context int32) int32 { 59 | return getVM(ctx).Builtin(builtinID, context) 60 | } 61 | 62 | //export opa_builtin1 63 | func opa_builtin1(ctx unsafe.Pointer, builtinID, context, arg0 int32) int32 { 64 | return getVM(ctx).Builtin(builtinID, context, arg0) 65 | } 66 | 67 | //export opa_builtin2 68 | func opa_builtin2(ctx unsafe.Pointer, builtinID, context, arg0, arg1 int32) int32 { 69 | return getVM(ctx).Builtin(builtinID, context, arg0, arg1) 70 | } 71 | 72 | //export opa_builtin3 73 | func opa_builtin3(ctx unsafe.Pointer, builtinID, context, arg0, arg1, arg2 int32) int32 { 74 | return getVM(ctx).Builtin(builtinID, context, arg0, arg1, arg2) 75 | } 76 | 77 | //export opa_builtin4 78 | func opa_builtin4(ctx unsafe.Pointer, builtinID, context, arg0, arg1, arg2, arg3 int32) int32 { 79 | return getVM(ctx).Builtin(builtinID, context, arg0, arg1, arg2, arg3) 80 | } 81 | 82 | func getVM(ctx unsafe.Pointer) *vm { 83 | ictx := wasm.IntoInstanceContext(ctx) 84 | return ictx.Data().(*vm) 85 | } 86 | -------------------------------------------------------------------------------- /opa/config.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The OPA Authors. All rights reserved. 2 | // Use of this source code is governed by an Apache2 3 | // license that can be found in the LICENSE file. 4 | 5 | package opa 6 | 7 | import ( 8 | "encoding/json" 9 | "fmt" 10 | "io/ioutil" 11 | ) 12 | 13 | const wasmPageSize = 65535 14 | 15 | // WithPolicyFile configures a policy file to load. 16 | func (o *OPA) WithPolicyFile(fileName string) *OPA { 17 | policy, err := ioutil.ReadFile(fileName) 18 | if err != nil { 19 | o.configErr = fmt.Errorf("%v: %w", err.Error(), ErrInvalidConfig) 20 | return o 21 | } 22 | 23 | o.policy = policy 24 | return o 25 | } 26 | 27 | // WithPolicyBytes configures the compiled policy to load. 28 | func (o *OPA) WithPolicyBytes(policy []byte) *OPA { 29 | o.policy = policy 30 | return o 31 | } 32 | 33 | // WithDataFile configures the JSON data file to load. 34 | func (o *OPA) WithDataFile(fileName string) *OPA { 35 | data, err := ioutil.ReadFile(fileName) 36 | if err != nil { 37 | o.configErr = fmt.Errorf("%v: %w", err.Error(), ErrInvalidConfig) 38 | return o 39 | } 40 | 41 | o.data = data 42 | return o 43 | } 44 | 45 | // WithDataBytes configures the JSON data to load. 46 | func (o *OPA) WithDataBytes(data []byte) *OPA { 47 | o.data = data 48 | return o 49 | } 50 | 51 | // WithDataJSON configures the JSON data to load. 52 | func (o *OPA) WithDataJSON(data interface{}) *OPA { 53 | v, err := json.Marshal(data) 54 | if err != nil { 55 | o.configErr = fmt.Errorf("%v: %w", err.Error(), ErrInvalidConfig) 56 | return o 57 | } 58 | 59 | o.data = v 60 | return o 61 | } 62 | 63 | // WithMemoryLimits configures the memory limits (in bytes) for a single policy 64 | // evaluation. 65 | func (o *OPA) WithMemoryLimits(min, max uint32) *OPA { 66 | if min < 2*65535 { 67 | o.configErr = fmt.Errorf("too low minimum memory limit: %w", ErrInvalidConfig) 68 | return o 69 | } 70 | 71 | if max != 0 && min > max { 72 | o.configErr = fmt.Errorf("too low maximum memory limit: %w", ErrInvalidConfig) 73 | return o 74 | } 75 | 76 | o.memoryMinPages, o.memoryMaxPages = pages(min), pages(max) 77 | return o 78 | } 79 | 80 | // WithPoolSize configures the maximum number of simultaneous policy 81 | // evaluations, i.e., the maximum number of underlying WASM instances 82 | // active at any time. The default is the number of logical CPUs 83 | // usable for the process as per runtime.NumCPU(). 84 | func (o *OPA) WithPoolSize(size uint32) *OPA { 85 | if size == 0 { 86 | o.configErr = fmt.Errorf("pool size: %w", ErrInvalidConfig) 87 | return o 88 | } 89 | 90 | o.poolSize = size 91 | return o 92 | } 93 | 94 | // WithErrorLogger configures an error logger invoked with all the errors. 95 | func (o *OPA) WithErrorLogger(logger func(error)) *OPA { 96 | o.logError = logger 97 | return o 98 | } 99 | 100 | // pages converts a byte size to pages, rounding up as necessary. 101 | func pages(n uint32) uint32 { 102 | pages := n / wasmPageSize 103 | if pages*wasmPageSize == n { 104 | return pages 105 | } 106 | 107 | return pages + 1 108 | } 109 | -------------------------------------------------------------------------------- /opa/errors.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The OPA Authors. All rights reserved. 2 | // Use of this source code is governed by an Apache2 3 | // license that can be found in the LICENSE file. 4 | 5 | package opa 6 | 7 | import ( 8 | "errors" 9 | ) 10 | 11 | var ( 12 | // ErrInvalidConfig is the error returned if the OPA initialization fails due to an invalid config. 13 | ErrInvalidConfig = errors.New("invalid config") 14 | // ErrInvalidPolicyOrData is the error returned if either policy or data is invalid. 15 | ErrInvalidPolicyOrData = errors.New("invalid policy or data") 16 | // ErrInvalidBundle is the error returned if the bundle loaded is corrupted. 17 | ErrInvalidBundle = errors.New("invalid bundle") 18 | // ErrNotReady is the error returned if the OPA instance is not initialized. 19 | ErrNotReady = errors.New("not ready") 20 | // ErrUndefined is the error returned if the evaluation result is undefined. 21 | ErrUndefined = errors.New("undefined decision") 22 | // ErrNonBoolean is the error returned if the evaluation result is not of boolean value. 23 | ErrNonBoolean = errors.New("non-boolean decision") 24 | // ErrInternal is the error returned if the evaluation fails due to an internal error. 25 | ErrInternal = errors.New("internal error") 26 | ) 27 | -------------------------------------------------------------------------------- /opa/file/config.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The OPA Authors. All rights reserved. 2 | // Use of this source code is governed by an Apache2 3 | // license that can be found in the LICENSE file. 4 | 5 | package file 6 | 7 | import ( 8 | "fmt" 9 | "time" 10 | 11 | "github.com/open-policy-agent/golang-opa-wasm/opa" 12 | ) 13 | 14 | // WithFile configures the file to load the bundle from. 15 | func (l *Loader) WithFile(filename string) *Loader { 16 | l.filename = filename 17 | return l 18 | } 19 | 20 | // WithInterval configures the delay between bundle file reloading. 21 | func (l *Loader) WithInterval(interval time.Duration) *Loader { 22 | l.interval = interval 23 | return l 24 | } 25 | 26 | // WithErrorLogger configures an error logger invoked with all the errors. 27 | func (l *Loader) WithErrorLogger(logger func(error)) *Loader { 28 | if logger == nil { 29 | l.configErr = fmt.Errorf("logger: %w", opa.ErrInvalidConfig) 30 | return l 31 | } 32 | 33 | l.logError = logger 34 | return l 35 | } 36 | -------------------------------------------------------------------------------- /opa/file/loader.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The OPA Authors. All rights reserved. 2 | // Use of this source code is governed by an Apache2 3 | // license that can be found in the LICENSE file. 4 | 5 | package file 6 | 7 | import ( 8 | "context" 9 | "fmt" 10 | "os" 11 | "sync" 12 | "time" 13 | 14 | "github.com/open-policy-agent/opa/bundle" 15 | 16 | "github.com/open-policy-agent/golang-opa-wasm/opa" 17 | ) 18 | 19 | const ( 20 | // DefaultInterval for re-loading the bundle file. 21 | DefaultInterval = time.Minute 22 | ) 23 | 24 | // Loader loads a bundle from a file. If started, it loads the bundle 25 | // periodically until closed. 26 | type Loader struct { 27 | configErr error // Delayed configuration error, if any. 28 | initialized bool 29 | pd policyData 30 | filename string 31 | interval time.Duration 32 | closing chan struct{} // Signal the request to stop the poller. 33 | closed chan struct{} // Signals the successful stopping of the poller. 34 | logError func(error) 35 | mutex sync.Mutex 36 | } 37 | 38 | // policyData captures the functions used in setting the policy and data. 39 | type policyData interface { 40 | SetPolicyData(policy []byte, data *interface{}) error 41 | } 42 | 43 | // New constructs a new file loader periodically reloading the bundle 44 | // from a file. 45 | func New(opa *opa.OPA) *Loader { 46 | return new(opa) 47 | } 48 | 49 | // new constucts a new file loader. This is for tests. 50 | func new(pd policyData) *Loader { 51 | return &Loader{ 52 | pd: pd, 53 | interval: DefaultInterval, 54 | logError: func(error) {}, 55 | } 56 | } 57 | 58 | // Init initializes the loader after its construction and 59 | // configuration. If invalid config, will return ErrInvalidConfig. 60 | func (l *Loader) Init() (*Loader, error) { 61 | if l.configErr != nil { 62 | return nil, l.configErr 63 | } 64 | 65 | if l.filename == "" { 66 | return nil, fmt.Errorf("filename: %w", opa.ErrInvalidConfig) 67 | } 68 | 69 | l.initialized = true 70 | return l, nil 71 | } 72 | 73 | // Start starts the periodic loading byt calling Load, failing if the 74 | // bundle loading fails. 75 | func (l *Loader) Start(ctx context.Context) error { 76 | if !l.initialized { 77 | return opa.ErrNotReady 78 | } 79 | 80 | if err := l.Load(ctx); err != nil { 81 | return err 82 | } 83 | 84 | l.closing = make(chan struct{}) 85 | l.closed = make(chan struct{}) 86 | 87 | go l.poller() 88 | 89 | return nil 90 | } 91 | 92 | // Close stops the loading, releasing all resources. 93 | func (l *Loader) Close() { 94 | if !l.initialized { 95 | return 96 | } 97 | 98 | if l.closing == nil { 99 | return 100 | } 101 | 102 | close(l.closing) 103 | <-l.closed 104 | 105 | l.closing = nil 106 | l.closed = nil 107 | } 108 | 109 | // Load loads the bundle from a file and installs it. The possible 110 | // returned errors are ErrInvalidBundle (in case of an error in 111 | // loading or opening the bundle) and the ones SetPolicyData of OPA 112 | // returns. 113 | func (l *Loader) Load(ctx context.Context) error { 114 | if !l.initialized { 115 | return opa.ErrNotReady 116 | } 117 | 118 | l.mutex.Lock() 119 | defer l.mutex.Unlock() 120 | 121 | f, err := os.Open(l.filename) 122 | if err != nil { 123 | return fmt.Errorf("%v: %w", err, opa.ErrInvalidBundle) 124 | } 125 | 126 | defer f.Close() 127 | 128 | // TODO: Cut the dependency to the OPA bundle package. 129 | 130 | bundle, err := bundle.NewReader(f).Read() 131 | if err != nil { 132 | return fmt.Errorf("%v: %w", err, opa.ErrInvalidBundle) 133 | } 134 | 135 | if bundle.Wasm == nil { 136 | return fmt.Errorf("missing wasm: %w", opa.ErrInvalidBundle) 137 | } 138 | 139 | var data *interface{} 140 | if bundle.Data != nil { 141 | var v interface{} = bundle.Data 142 | data = &v 143 | } 144 | 145 | return l.pd.SetPolicyData(bundle.Wasm, data) 146 | } 147 | 148 | // poller periodically downloads the bundle. 149 | func (l *Loader) poller() { 150 | defer close(l.closed) 151 | 152 | for { 153 | if err := l.Load(context.Background()); err != nil { 154 | l.logError(err) 155 | } 156 | 157 | select { 158 | case <-time.After(l.interval): 159 | case <-l.closing: 160 | return 161 | } 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /opa/file/loader_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The OPA Authors. All rights reserved. 2 | // Use of this source code is governed by an Apache2 3 | // license that can be found in the LICENSE file. 4 | 5 | package file 6 | 7 | import ( 8 | "bytes" 9 | "context" 10 | "io/ioutil" 11 | "os" 12 | "reflect" 13 | "sync" 14 | "testing" 15 | "time" 16 | 17 | "github.com/open-policy-agent/opa/bundle" 18 | ) 19 | 20 | func TestFileLoader(t *testing.T) { 21 | // Assign a temp file. 22 | 23 | f, err := ioutil.TempFile("", "test-file-loader") 24 | if err != nil { 25 | panic(err) 26 | } 27 | 28 | defer os.Remove(f.Name()) 29 | 30 | // Start loader, without having a file in place. 31 | 32 | var pd testPolicyData 33 | loader, err := new(&pd).WithFile(f.Name()).WithInterval(10 * time.Millisecond).Init() 34 | if err != nil { 35 | t.Fatal(err.Error()) 36 | } 37 | 38 | ctx := context.Background() 39 | if err := loader.Start(ctx); err == nil { 40 | t.Fatal("missing file not resulting in an error") 41 | } 42 | 43 | policy := "wasm-policy" 44 | var data interface{} = map[string]interface{}{ 45 | "foo": "bar", 46 | } 47 | 48 | // Start loader, with the file in place. 49 | 50 | writeBundle(f.Name(), policy, data) 51 | 52 | if err := loader.Start(ctx); err != nil { 53 | t.Fatalf("unable to start loader: %v", err) 54 | } 55 | 56 | pd.CheckEqual(t, policy, &data) 57 | 58 | // Reload with updated contents. 59 | 60 | policy = "wasm-policy-modified" 61 | data = map[string]interface{}{ 62 | "bar": "foo", 63 | } 64 | 65 | writeBundle(f.Name(), policy, data) 66 | 67 | pd.WaitUpdate() 68 | pd.CheckEqual(t, policy, &data) 69 | 70 | loader.Close() 71 | } 72 | 73 | type testPolicyData struct { 74 | sync.Mutex 75 | policy []byte 76 | data *interface{} 77 | updated chan struct{} 78 | } 79 | 80 | func (pd *testPolicyData) SetPolicyData(policy []byte, data *interface{}) error { 81 | pd.Lock() 82 | defer pd.Unlock() 83 | 84 | pd.policy = policy 85 | pd.data = data 86 | if pd.updated != nil { 87 | close(pd.updated) 88 | } 89 | 90 | return nil 91 | } 92 | 93 | func (pd *testPolicyData) CheckEqual(t *testing.T, policy string, data *interface{}) { 94 | pd.Lock() 95 | defer pd.Unlock() 96 | 97 | if !bytes.Equal([]byte(policy), pd.policy) && reflect.DeepEqual(data, pd.data) { 98 | t.Fatal("policy/data mismatch.") 99 | } 100 | } 101 | 102 | func (pd *testPolicyData) WaitUpdate() { 103 | pd.Lock() 104 | pd.updated = make(chan struct{}) 105 | pd.Unlock() 106 | 107 | <-pd.updated 108 | 109 | pd.Lock() 110 | pd.updated = nil 111 | pd.Unlock() 112 | } 113 | 114 | func writeBundle(name string, policy string, data interface{}) { 115 | b := bundle.Bundle{ 116 | Data: data.(map[string]interface{}), 117 | Wasm: []byte(policy), 118 | } 119 | 120 | var buf bytes.Buffer 121 | if err := bundle.Write(&buf, b); err != nil { 122 | panic(err) 123 | } 124 | 125 | if err := ioutil.WriteFile(name, buf.Bytes(), 0644); err != nil { 126 | panic(err) 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /opa/http/config.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The OPA Authors. All rights reserved. 2 | // Use of this source code is governed by an Apache2 3 | // license that can be found in the LICENSE file. 4 | 5 | package http 6 | 7 | import ( 8 | "fmt" 9 | "net/http" 10 | "time" 11 | 12 | "github.com/open-policy-agent/golang-opa-wasm/opa" 13 | ) 14 | 15 | // WithURL configures the URL to download the bundle from. 16 | func (l *Loader) WithURL(url string) *Loader { 17 | l.url = url 18 | return l 19 | } 20 | 21 | // WithClient configures the HTTP client to use. If not configured, 22 | // http.DefaultClient is used. 23 | func (l *Loader) WithClient(client *http.Client) *Loader { 24 | if client == nil { 25 | l.configErr = fmt.Errorf("client: %w", opa.ErrInvalidConfig) 26 | return l 27 | } 28 | 29 | l.client = client 30 | return l 31 | } 32 | 33 | // WithInterval configures the minimum and maximum delay between bundle downloads. 34 | func (l *Loader) WithInterval(min, max time.Duration) *Loader { 35 | if min > max { 36 | l.configErr = fmt.Errorf("interval: %w", opa.ErrInvalidConfig) 37 | return l 38 | } 39 | 40 | l.minDelay = min 41 | l.maxDelay = max 42 | return l 43 | } 44 | 45 | // WithPrepareRequest configures a handler to customize the HTTP requests before their sending. The 46 | // HTTP request is not modified after the handle invocation. 47 | func (l *Loader) WithPrepareRequest(prepare func(*http.Request) error) *Loader { 48 | if prepare == nil { 49 | l.configErr = fmt.Errorf("prepare request: %w", opa.ErrInvalidConfig) 50 | return l 51 | } 52 | 53 | l.prepareRequest = prepare 54 | return l 55 | } 56 | 57 | // WithErrorLogger configures an error logger invoked with all the errors. 58 | func (l *Loader) WithErrorLogger(logger func(error)) *Loader { 59 | if logger == nil { 60 | l.configErr = fmt.Errorf("logger: %w", opa.ErrInvalidConfig) 61 | return l 62 | } 63 | 64 | l.logError = logger 65 | return l 66 | } 67 | -------------------------------------------------------------------------------- /opa/http/loader.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The OPA Authors. All rights reserved. 2 | // Use of this source code is governed by an Apache2 3 | // license that can be found in the LICENSE file. 4 | 5 | package http 6 | 7 | import ( 8 | "context" 9 | "fmt" 10 | "io" 11 | "io/ioutil" 12 | "math/rand" 13 | "net/http" 14 | "sync" 15 | "time" 16 | 17 | "github.com/open-policy-agent/opa/bundle" 18 | 19 | "github.com/open-policy-agent/golang-opa-wasm/opa" 20 | ) 21 | 22 | const ( 23 | // MinRetryDelay determines the minimum retry interval in case 24 | // of an error. 25 | MinRetryDelay = 100 * time.Millisecond 26 | 27 | // DefaultMinDelay is the default minimum re-downloading 28 | // interval in case of a previously successful download. 29 | DefaultMinDelay = 60 * time.Second 30 | 31 | // DefaultMaxDelay is the default maximum re-downloading 32 | // interval in case of a previously successful download. 33 | DefaultMaxDelay = 120 * time.Second 34 | ) 35 | 36 | // Loader downloads a bundle over HTTP. If started, it downloads the 37 | // bundle periodically until closed. 38 | type Loader struct { 39 | configErr error // Delayed configuration error, if any. 40 | initialized bool 41 | pd policyData 42 | client *http.Client 43 | url string 44 | tag string 45 | minDelay time.Duration 46 | maxDelay time.Duration 47 | closing chan struct{} // Signal the request to stop the poller. 48 | closed chan struct{} // Signals the successful stopping of the poller. 49 | logError func(error) 50 | prepareRequest func(*http.Request) error 51 | mutex sync.Mutex 52 | } 53 | 54 | // policyData captures the functions used in setting the policy and data. 55 | type policyData interface { 56 | SetPolicyData(policy []byte, data *interface{}) error 57 | } 58 | 59 | // New constructs a new HTTP loader periodically downloading a bundle 60 | // over HTTP. 61 | func New(o *opa.OPA) *Loader { 62 | return new(o) 63 | } 64 | 65 | // new constucts a new HTTP loader. This is for tests. 66 | func new(pd policyData) *Loader { 67 | return &Loader{ 68 | pd: pd, 69 | client: http.DefaultClient, 70 | minDelay: DefaultMinDelay, 71 | maxDelay: DefaultMaxDelay, 72 | logError: func(error) {}, 73 | prepareRequest: func(*http.Request) error { return nil }, 74 | } 75 | } 76 | 77 | // Init initializes the loader after its construction and 78 | // configuration. If invalid config, will return ErrInvalidConfig. 79 | func (l *Loader) Init() (*Loader, error) { 80 | if l.configErr != nil { 81 | return nil, l.configErr 82 | } 83 | 84 | if l.url == "" { 85 | return nil, fmt.Errorf("missing url: %w", opa.ErrInvalidConfig) 86 | } 87 | 88 | l.initialized = true 89 | return l, nil 90 | } 91 | 92 | // Start starts the periodic downloads, blocking until the first 93 | // successful download. If cancelled, will return context.Cancelled. 94 | func (l *Loader) Start(ctx context.Context) error { 95 | if !l.initialized { 96 | return opa.ErrNotReady 97 | } 98 | 99 | if err := l.download(ctx); err != nil { 100 | return err 101 | } 102 | 103 | l.closing = make(chan struct{}) 104 | l.closed = make(chan struct{}) 105 | 106 | go l.poller() 107 | 108 | return nil 109 | } 110 | 111 | // Close stops the downloading, releasing all resources. 112 | func (l *Loader) Close() { 113 | if !l.initialized { 114 | return 115 | } 116 | 117 | if l.closing == nil { 118 | return 119 | } 120 | 121 | close(l.closing) 122 | <-l.closed 123 | 124 | l.closing = nil 125 | l.closed = nil 126 | } 127 | 128 | // poller periodically downloads the bundle. 129 | func (l *Loader) poller() { 130 | defer close(l.closed) 131 | 132 | ctx, cancel := context.WithCancel(context.Background()) 133 | go func() { 134 | <-l.closing 135 | cancel() 136 | }() 137 | 138 | for { 139 | if err := l.download(ctx); err != nil { 140 | break 141 | } 142 | 143 | select { 144 | case <-time.After(time.Duration(float64((l.maxDelay-l.minDelay))*rand.Float64()) + l.minDelay): 145 | case <-l.closing: 146 | return 147 | } 148 | } 149 | } 150 | 151 | // download blocks until a bundle has been download successfully or 152 | // the context is cancelled. No other error besides context.Canceled 153 | // is ever returned. 154 | func (l *Loader) download(ctx context.Context) error { 155 | for retry := 0; true; retry++ { 156 | if err := l.Load(ctx); err == context.Canceled { 157 | return err 158 | } else if err != nil { 159 | l.logError(err) 160 | } else if err == nil { 161 | break 162 | } 163 | 164 | select { 165 | case <-time.After(defaultBackoff(float64(MinRetryDelay), float64(l.maxDelay), retry)): 166 | case <-ctx.Done(): 167 | return context.Canceled 168 | } 169 | } 170 | 171 | return nil 172 | } 173 | 174 | // Load downloads the bundle from a remote location and installs 175 | // it. The possible returned errors are ErrInvalidBundle (in case of 176 | // an error in downloading or opening the bundle) and the ones 177 | // SetPolicyData of OPA returns. 178 | func (l *Loader) Load(ctx context.Context) error { 179 | if !l.initialized { 180 | return opa.ErrNotReady 181 | } 182 | 183 | l.mutex.Lock() 184 | defer l.mutex.Unlock() 185 | 186 | bundle, err := l.get(ctx, "") 187 | if err != nil { 188 | return fmt.Errorf("%v: %w", err, opa.ErrInvalidBundle) 189 | } 190 | 191 | if bundle.Wasm == nil { 192 | return opa.ErrInvalidBundle 193 | } 194 | 195 | var data *interface{} 196 | if bundle.Data != nil { 197 | var v interface{} = bundle.Data 198 | data = &v 199 | } 200 | 201 | return l.pd.SetPolicyData(bundle.Wasm, data) 202 | } 203 | 204 | // get executes HTTP GET. 205 | func (l *Loader) get(ctx context.Context, tag string) (*bundle.Bundle, error) { 206 | req, err := http.NewRequest(http.MethodGet, l.url, nil) 207 | if err != nil { 208 | return nil, err 209 | } 210 | 211 | if tag != "" { 212 | req.Header.Add("If-None-Match", tag) 213 | } 214 | 215 | req = req.WithContext(ctx) 216 | if err := l.prepareRequest(req); err != nil { 217 | return nil, err 218 | } 219 | 220 | resp, err := l.client.Do(req) 221 | if err != nil { 222 | return nil, err 223 | } 224 | 225 | defer l.close(resp) 226 | 227 | switch resp.StatusCode { 228 | case http.StatusOK: 229 | // TODO: Cut the dependency to the OPA bundle package. 230 | 231 | b, err := bundle.NewReader(resp.Body).Read() 232 | if err != nil { 233 | return nil, err 234 | } 235 | 236 | l.tag = resp.Header.Get("ETag") 237 | return &b, nil 238 | 239 | case http.StatusNotModified: 240 | return nil, nil 241 | case http.StatusUnauthorized: 242 | return nil, fmt.Errorf("not authorized (401)") 243 | case http.StatusForbidden: 244 | return nil, fmt.Errorf("forbidden (403)") 245 | case http.StatusNotFound: 246 | return nil, fmt.Errorf("not found (404)") 247 | default: 248 | return nil, fmt.Errorf("unknown HTTP status %v", resp.StatusCode) 249 | } 250 | } 251 | 252 | // close closes the HTTP response gracefully, first draining it, to 253 | // avoid resource leaks. 254 | func (l *Loader) close(resp *http.Response) { 255 | io.Copy(ioutil.Discard, resp.Body) // Ignore errors. 256 | resp.Body.Close() 257 | } 258 | -------------------------------------------------------------------------------- /opa/http/loader_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The OPA Authors. All rights reserved. 2 | // Use of this source code is governed by an Apache2 3 | // license that can be found in the LICENSE file. 4 | package http 5 | 6 | import ( 7 | "bytes" 8 | "context" 9 | "net/http" 10 | "net/http/httptest" 11 | "reflect" 12 | "sync" 13 | "testing" 14 | "time" 15 | 16 | "github.com/open-policy-agent/opa/bundle" 17 | ) 18 | 19 | func TestFileLoader(t *testing.T) { 20 | // Start loader, without having the HTTP content in place. 21 | 22 | var pd testPolicyData 23 | loader, err := new(&pd).WithURL("http://localhost:0").WithInterval(10*time.Millisecond, 20*time.Millisecond).Init() 24 | if err != nil { 25 | t.Fatal(err.Error()) 26 | } 27 | 28 | ctx, cancel := context.WithCancel(context.Background()) 29 | go func() { 30 | time.Sleep(10 * time.Millisecond) 31 | cancel() 32 | }() 33 | 34 | if err := loader.Start(ctx); err != context.Canceled { 35 | t.Fatalf("missing file not resulting in a correct error: %v", err) 36 | } 37 | 38 | // Start again, with the HTTP content in place. 39 | 40 | var mutex sync.Mutex 41 | policy := "wasm-policy" 42 | var data interface{} = map[string]interface{}{ 43 | "foo": "bar", 44 | } 45 | 46 | ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 47 | mutex.Lock() 48 | defer mutex.Unlock() 49 | 50 | if err := bundle.Write(w, bundle.Bundle{ 51 | Data: data.(map[string]interface{}), 52 | Wasm: []byte(policy), 53 | }); err != nil { 54 | panic(err) 55 | } 56 | })) 57 | defer ts.Close() 58 | 59 | loader, err = new(&pd).WithURL(ts.URL).WithInterval(10*time.Millisecond, 20*time.Millisecond).Init() 60 | if err != nil { 61 | t.Fatal(err.Error()) 62 | } 63 | 64 | ctx = context.Background() 65 | if err := loader.Start(ctx); err != nil { 66 | t.Fatalf("unable to start loader: %v", err) 67 | } 68 | 69 | pd.CheckEqual(t, policy, &data) 70 | 71 | // Reload with updated contents. 72 | 73 | mutex.Lock() 74 | policy = "wasm-policy-modified" 75 | data = map[string]interface{}{ 76 | "bar": "foo", 77 | } 78 | mutex.Unlock() 79 | 80 | pd.WaitUpdate() 81 | pd.CheckEqual(t, policy, &data) 82 | 83 | loader.Close() 84 | } 85 | 86 | type testPolicyData struct { 87 | sync.Mutex 88 | policy []byte 89 | data *interface{} 90 | updated chan struct{} 91 | } 92 | 93 | func (pd *testPolicyData) SetPolicyData(policy []byte, data *interface{}) error { 94 | pd.Lock() 95 | defer pd.Unlock() 96 | 97 | pd.policy = policy 98 | pd.data = data 99 | if pd.updated != nil { 100 | close(pd.updated) 101 | } 102 | 103 | return nil 104 | } 105 | 106 | func (pd *testPolicyData) CheckEqual(t *testing.T, policy string, data *interface{}) { 107 | pd.Lock() 108 | defer pd.Unlock() 109 | 110 | if !bytes.Equal([]byte(policy), pd.policy) && reflect.DeepEqual(data, pd.data) { 111 | t.Fatal("policy/data mismatch.") 112 | } 113 | } 114 | 115 | func (pd *testPolicyData) WaitUpdate() { 116 | pd.Lock() 117 | pd.updated = make(chan struct{}) 118 | pd.Unlock() 119 | 120 | <-pd.updated 121 | 122 | pd.Lock() 123 | pd.updated = nil 124 | pd.Unlock() 125 | } 126 | -------------------------------------------------------------------------------- /opa/http/util.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The OPA Authors. All rights reserved. 2 | // Use of this source code is governed by an Apache2 3 | // license that can be found in the LICENSE file. 4 | 5 | package http 6 | 7 | import ( 8 | "math/rand" 9 | "time" 10 | ) 11 | 12 | // defaultBackoff returns a delay with an exponential backoff based on the 13 | // number of retries. 14 | func defaultBackoff(base, max float64, retries int) time.Duration { 15 | return backoff(base, max, .2, 1.6, retries) 16 | } 17 | 18 | // backoff returns a delay with an exponential backoff based on the number of 19 | // retries. Same algorithm used in gRPC. 20 | func backoff(base, max, jitter, factor float64, retries int) time.Duration { 21 | if retries == 0 { 22 | return 0 23 | } 24 | 25 | backoff, max := float64(base), float64(max) 26 | for backoff < max && retries > 0 { 27 | backoff *= factor 28 | retries-- 29 | } 30 | if backoff > max { 31 | backoff = max 32 | } 33 | 34 | // Randomize backoff delays so that if a cluster of requests start at 35 | // the same time, they won't operate in lockstep. 36 | backoff *= 1 + jitter*(rand.Float64()*2-1) 37 | if backoff < 0 { 38 | return 0 39 | } 40 | 41 | return time.Duration(backoff) 42 | } 43 | -------------------------------------------------------------------------------- /opa/loader.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The OPA Authors. All rights reserved. 2 | // Use of this source code is governed by an Apache2 3 | // license that can be found in the LICENSE file. 4 | 5 | package opa 6 | 7 | import ( 8 | "context" 9 | ) 10 | 11 | // Loader is the interface all bundle loaders implement. 12 | type Loader interface { 13 | // Load loads a bundle. This can be invoked without starting the polling. 14 | Load(ctx context.Context) error 15 | 16 | // Start starts the bundle polling. 17 | Start(ctx context.Context) error 18 | 19 | // Close stops the polling. 20 | Close() 21 | } 22 | -------------------------------------------------------------------------------- /opa/opa.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The OPA Authors. All rights reserved. 2 | // Use of this source code is governed by an Apache2 3 | // license that can be found in the LICENSE file. 4 | 5 | package opa 6 | 7 | import ( 8 | "context" 9 | "encoding/json" 10 | "fmt" 11 | "runtime" 12 | "sync" 13 | ) 14 | 15 | // OPA executes WebAssembly compiled Rego policies. 16 | type OPA struct { 17 | configErr error // Delayed configuration error, if any. 18 | memoryMinPages uint32 19 | memoryMaxPages uint32 // 0 means no limit. 20 | poolSize uint32 21 | pool *pool 22 | mutex sync.Mutex // To serialize access to SetPolicy, SetData and Close. 23 | policy []byte // Current policy. 24 | data []byte // Current data. 25 | logError func(error) 26 | } 27 | 28 | // Result holds the evaluation result. 29 | type Result struct { 30 | Result interface{} 31 | } 32 | 33 | // New constructs a new OPA SDK instance, ready to be configured with 34 | // With functions. If no policy is provided as a part of 35 | // configuration, policy (and data) needs to be set before invoking 36 | // Eval. Once constructed and configured, the instance needs to be 37 | // initialized before invoking the Eval. 38 | func New() *OPA { 39 | opa := &OPA{ 40 | memoryMinPages: 2, 41 | memoryMaxPages: 0, 42 | poolSize: uint32(runtime.GOMAXPROCS(0)), 43 | logError: func(error) {}, 44 | } 45 | 46 | return opa 47 | } 48 | 49 | // Init initializes the SDK instance after the construction and 50 | // configuration. If the configuration is invalid, it returns 51 | // ErrInvalidConfig. 52 | func (o *OPA) Init() (*OPA, error) { 53 | if o.configErr != nil { 54 | return nil, o.configErr 55 | } 56 | 57 | o.pool = newPool(o.poolSize, o.memoryMinPages, o.memoryMaxPages) 58 | 59 | if len(o.policy) != 0 { 60 | if err := o.pool.SetPolicyData(o.policy, o.data); err != nil { 61 | return nil, err 62 | } 63 | } 64 | 65 | return o, nil 66 | } 67 | 68 | // SetData updates the data for the subsequent Eval calls. Returns 69 | // either ErrNotReady, ErrInvalidPolicyOrData, or ErrInternal if an 70 | // error occurs. 71 | func (o *OPA) SetData(v interface{}) error { 72 | if o.pool == nil { 73 | return ErrNotReady 74 | } 75 | 76 | raw, err := json.Marshal(v) 77 | if err != nil { 78 | return fmt.Errorf("%v: %w", err, ErrInvalidPolicyOrData) 79 | } 80 | 81 | o.mutex.Lock() 82 | defer o.mutex.Unlock() 83 | 84 | return o.setPolicyData(o.policy, raw) 85 | } 86 | 87 | // SetPolicy updates the policy for the subsequent Eval calls. 88 | // Returns either ErrNotReady, ErrInvalidPolicy or ErrInternal if an 89 | // error occurs. 90 | func (o *OPA) SetPolicy(p []byte) error { 91 | if o.pool == nil { 92 | return ErrNotReady 93 | } 94 | 95 | o.mutex.Lock() 96 | defer o.mutex.Unlock() 97 | 98 | return o.setPolicyData(p, o.data) 99 | } 100 | 101 | // SetPolicyData updates both the policy and data for the subsequent 102 | // Eval calls. Returns either ErrNotReady, ErrInvalidPolicyOrData, or 103 | // ErrInternal if an error occurs. 104 | func (o *OPA) SetPolicyData(policy []byte, data *interface{}) error { 105 | if o.pool == nil { 106 | return ErrNotReady 107 | } 108 | 109 | var raw []byte 110 | if data != nil { 111 | var err error 112 | raw, err = json.Marshal(*data) 113 | if err != nil { 114 | return fmt.Errorf("%v: %w", err, ErrInvalidPolicyOrData) 115 | } 116 | } 117 | 118 | o.mutex.Lock() 119 | defer o.mutex.Unlock() 120 | 121 | return o.setPolicyData(policy, raw) 122 | } 123 | 124 | func (o *OPA) setPolicyData(policy []byte, data []byte) error { 125 | if err := o.pool.SetPolicyData(policy, data); err != nil { 126 | return err 127 | } 128 | 129 | o.policy = policy 130 | o.data = data 131 | return nil 132 | } 133 | 134 | // Eval evaluates the policy with the given input, returning the 135 | // evaluation results. If no policy was configured at construction 136 | // time nor set after, the function returns ErrNotReady. It returns 137 | // ErrInternal if any other error occurs. 138 | func (o *OPA) Eval(ctx context.Context, input *interface{}) (*Result, error) { 139 | if o.pool == nil { 140 | return nil, ErrNotReady 141 | } 142 | 143 | instance, err := o.pool.Acquire(ctx) 144 | if err != nil { 145 | return nil, err 146 | } 147 | 148 | defer o.pool.Release(instance) 149 | 150 | result, err := instance.Eval(ctx, input) 151 | if err != nil { 152 | return nil, fmt.Errorf("%v: %w", err, ErrInternal) 153 | } 154 | 155 | return &Result{result}, nil 156 | } 157 | 158 | // Close waits until all the pending evaluations complete and then 159 | // releases all the resources allocated. Eval will return ErrClosed 160 | // afterwards. 161 | func (o *OPA) Close() { 162 | if o.pool == nil { 163 | return 164 | } 165 | 166 | o.mutex.Lock() 167 | defer o.mutex.Unlock() 168 | 169 | o.pool.Close() 170 | } 171 | 172 | // EvalBool evaluates the boolean policy with the given input. The 173 | // possible error values returned are as with Eval with addition of 174 | // ErrUndefined indicating an undefined policy decision and 175 | // ErrNonBoolean indicating a non-boolean policy decision. 176 | func EvalBool(ctx context.Context, o *OPA, input *interface{}) (bool, error) { 177 | rs, err := o.Eval(ctx, input) 178 | if err != nil { 179 | return false, err 180 | } 181 | 182 | r, ok := rs.Result.([]interface{}) 183 | if !ok || len(r) == 0 { 184 | return false, ErrUndefined 185 | } 186 | 187 | m, ok := r[0].(map[string]interface{}) 188 | if !ok || len(m) != 1 { 189 | return false, ErrNonBoolean 190 | } 191 | 192 | var b bool 193 | for _, v := range m { 194 | b, ok = v.(bool) 195 | break 196 | } 197 | 198 | if !ok { 199 | return false, ErrNonBoolean 200 | } 201 | 202 | return b, nil 203 | } 204 | -------------------------------------------------------------------------------- /opa/opa_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The OPA Authors. All rights reserved. 2 | // Use of this source code is governed by an Apache2 3 | // license that can be found in the LICENSE file. 4 | package opa 5 | 6 | import ( 7 | "context" 8 | "fmt" 9 | "reflect" 10 | "testing" 11 | 12 | "github.com/open-policy-agent/opa/rego" 13 | "github.com/open-policy-agent/opa/util" 14 | ) 15 | 16 | func TestOPA(t *testing.T) { 17 | type Eval struct { 18 | NewPolicy string 19 | NewData string 20 | Input string 21 | Result string 22 | } 23 | 24 | tests := []struct { 25 | Description string 26 | Policy string 27 | Query string 28 | Data string 29 | Evals []Eval 30 | }{ 31 | { 32 | Description: "No input, no data, static policy", 33 | Policy: `a = true`, 34 | Query: "data.p.a = x", 35 | Evals: []Eval{ 36 | Eval{Result: `[{"x": true}]`}, 37 | Eval{Result: `[{"x": true}]`}, 38 | }, 39 | }, 40 | { 41 | Description: "Only input changing", 42 | Policy: `a = input`, 43 | Query: "data.p.a = x", 44 | Evals: []Eval{ 45 | Eval{Input: "false", Result: `[{"x": false}]`}, 46 | Eval{Input: "true", Result: `[{"x": true}]`}, 47 | }, 48 | }, 49 | { 50 | Description: "Only data changing", 51 | Policy: `a = data.q`, 52 | Query: "data.p.a = x", 53 | Data: `{"q": false}`, 54 | Evals: []Eval{ 55 | Eval{Result: `[{"x": false}]`}, 56 | Eval{NewData: `{"q": true}`, Result: `[{"x": true}]`}, 57 | }, 58 | }, 59 | { 60 | Description: "Only policy changing", 61 | Policy: `a = data.q`, 62 | Query: "data.p.a = x", 63 | Data: `{"q": false, "r": true}`, 64 | Evals: []Eval{ 65 | Eval{Result: `[{"x": false}]`}, 66 | Eval{NewPolicy: `a = data.r`, Result: `[{"x": true}]`}, 67 | }, 68 | }, 69 | { 70 | Description: "Policy and data changing", 71 | Policy: `a = data.q`, 72 | Query: "data.p.a = x", 73 | Data: `{"q": 0, "r": 1}`, 74 | Evals: []Eval{ 75 | Eval{Result: `[{"x": 0}]`}, 76 | Eval{NewPolicy: `a = data.r`, NewData: `{"q": 2, "r": 3}`, Result: `[{"x": 3}]`}, 77 | }, 78 | }, 79 | { 80 | Description: "Builtins", 81 | Policy: `a = count(data.q) + sum(data.q)`, 82 | Query: "data.p.a = x", 83 | Evals: []Eval{ 84 | Eval{NewData: `{"q": []}`, Result: `[{"x": 0}]`}, 85 | Eval{NewData: `{"q": [1, 2]}`, Result: `[{"x": 5}]`}, 86 | }, 87 | }, 88 | { 89 | Description: "Undefined decision", 90 | Policy: `a = true`, 91 | Query: "data.p.b = x", 92 | Evals: []Eval{ 93 | Eval{Result: `[]`}, 94 | }, 95 | }, 96 | } 97 | 98 | for _, test := range tests { 99 | t.Run(test.Description, func(t *testing.T) { 100 | policy := compileRegoToWasm(test.Policy, test.Query) 101 | data := []byte(test.Data) 102 | if len(data) == 0 { 103 | data = nil 104 | } 105 | opa, err := New(). 106 | WithPolicyBytes(policy). 107 | WithDataBytes(data). 108 | WithMemoryLimits(131070, 0). 109 | WithPoolSize(1). // Minimal pool size to test pooling. 110 | Init() 111 | if err != nil { 112 | t.Fatal(err) 113 | } 114 | 115 | // Execute each requested policy evaluation, with their inputs and updating data if requested. 116 | 117 | for _, eval := range test.Evals { 118 | switch { 119 | case eval.NewPolicy != "" && eval.NewData != "": 120 | policy := compileRegoToWasm(eval.NewPolicy, test.Query) 121 | data := parseJSON(eval.NewData) 122 | if err := opa.SetPolicyData(policy, data); err != nil { 123 | t.Errorf(err.Error()) 124 | } 125 | 126 | case eval.NewPolicy != "": 127 | policy := compileRegoToWasm(eval.NewPolicy, test.Query) 128 | if err := opa.SetPolicy(policy); err != nil { 129 | t.Errorf(err.Error()) 130 | } 131 | 132 | case eval.NewData != "": 133 | data := parseJSON(eval.NewData) 134 | if err := opa.SetData(*data); err != nil { 135 | t.Errorf(err.Error()) 136 | } 137 | } 138 | 139 | result, err := opa.Eval(context.Background(), parseJSON(eval.Input)) 140 | if err != nil { 141 | t.Errorf(err.Error()) 142 | } 143 | 144 | if !reflect.DeepEqual(*parseJSON(eval.Result), result.Result) { 145 | t.Errorf("Incorrect result: %v", result.Result) 146 | } 147 | } 148 | 149 | opa.Close() 150 | }) 151 | } 152 | } 153 | 154 | func BenchmarkWasmRego(b *testing.B) { 155 | policy := compileRegoToWasm("a = true", "data.p.a = x") 156 | opa, _ := New(). 157 | WithPolicyBytes(policy). 158 | WithMemoryLimits(131070, 2*131070). // TODO: For some reason unlimited memory slows down the eval_ctx_new(). 159 | WithPoolSize(1). 160 | Init() 161 | 162 | b.ReportAllocs() 163 | b.ResetTimer() 164 | 165 | ctx := context.Background() 166 | var input interface{} = make(map[string]interface{}) 167 | 168 | for i := 0; i < b.N; i++ { 169 | if _, err := opa.Eval(ctx, &input); err != nil { 170 | panic(err) 171 | } 172 | } 173 | } 174 | 175 | func BenchmarkGoRego(b *testing.B) { 176 | pq := compileRego(`package p 177 | 178 | a = true`, "data.p.a = x") 179 | 180 | b.ReportAllocs() 181 | b.ResetTimer() 182 | 183 | input := make(map[string]interface{}) 184 | 185 | for i := 0; i < b.N; i++ { 186 | if _, err := pq.Eval(context.Background(), rego.EvalInput(input)); err != nil { 187 | panic(err) 188 | } 189 | } 190 | } 191 | 192 | func compileRegoToWasm(policy string, query string) []byte { 193 | module := fmt.Sprintf("package p\n%s", policy) 194 | cr, err := rego.New( 195 | rego.Query(query), 196 | rego.Module("module.rego", module), 197 | ).Compile(context.Background(), rego.CompilePartial(false)) 198 | if err != nil { 199 | panic(err) 200 | } 201 | 202 | return cr.Bytes 203 | } 204 | 205 | func compileRego(module string, query string) rego.PreparedEvalQuery { 206 | rego := rego.New( 207 | rego.Query(query), 208 | rego.Module("module.rego", module), 209 | ) 210 | pq, err := rego.PrepareForEval(context.Background()) 211 | if err != nil { 212 | panic(err) 213 | } 214 | 215 | return pq 216 | } 217 | 218 | func parseJSON(s string) *interface{} { 219 | if s == "" { 220 | return nil 221 | } 222 | 223 | v := util.MustUnmarshalJSON([]byte(s)) 224 | return &v 225 | } 226 | -------------------------------------------------------------------------------- /opa/pool.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The OPA Authors. All rights reserved. 2 | // Use of this source code is governed by an Apache2 3 | // license that can be found in the LICENSE file. 4 | 5 | package opa 6 | 7 | import ( 8 | "bytes" 9 | "context" 10 | "fmt" 11 | "sync" 12 | ) 13 | 14 | // pool maintains a pool of WebAssemly VM instances. 15 | type pool struct { 16 | available chan struct{} 17 | mutex sync.Mutex 18 | initialized bool 19 | closed bool 20 | policy []byte 21 | data []byte 22 | memoryMinPages uint32 23 | memoryMaxPages uint32 24 | vms []*vm // All current VM instances, acquired or not. 25 | acquired []bool 26 | pendingReinit *vm 27 | blockedReinit chan struct{} 28 | } 29 | 30 | // newPool constructs a new pool with the pool and VM configuration provided. 31 | func newPool(poolSize, memoryMinPages, memoryMaxPages uint32) *pool { 32 | available := make(chan struct{}, poolSize) 33 | for i := uint32(0); i < poolSize; i++ { 34 | available <- struct{}{} 35 | } 36 | 37 | return &pool{ 38 | memoryMinPages: memoryMinPages, 39 | memoryMaxPages: memoryMaxPages, 40 | available: available, 41 | vms: make([]*vm, 0), 42 | acquired: make([]bool, 0), 43 | } 44 | } 45 | 46 | // Acquire obtains a VM from the pool, waiting if all VMms are in use 47 | // and building one as necessary. Returns either ErrNotReady or 48 | // ErrInternal if an error. 49 | func (p *pool) Acquire(ctx context.Context) (*vm, error) { 50 | select { 51 | case <-ctx.Done(): 52 | return nil, ctx.Err() 53 | case <-p.available: 54 | } 55 | 56 | p.mutex.Lock() 57 | defer p.mutex.Unlock() 58 | 59 | if !p.initialized || p.closed { 60 | return nil, ErrNotReady 61 | } 62 | 63 | for i, vm := range p.vms { 64 | if !p.acquired[i] { 65 | p.acquired[i] = true 66 | return vm, nil 67 | } 68 | } 69 | 70 | policy, data := p.policy, p.data 71 | 72 | p.mutex.Unlock() 73 | vm, err := newVM(policy, data, p.memoryMinPages, p.memoryMaxPages) 74 | p.mutex.Lock() 75 | 76 | if err != nil { 77 | p.available <- struct{}{} 78 | return nil, fmt.Errorf("%v: %w", err, ErrInternal) 79 | } 80 | 81 | p.acquired = append(p.acquired, true) 82 | p.vms = append(p.vms, vm) 83 | return vm, nil 84 | } 85 | 86 | // Release releases the VM back to the pool. 87 | func (p *pool) Release(vm *vm) { 88 | p.mutex.Lock() 89 | 90 | // If the policy data setting is waiting for this one, don't release it back to the general consumption. 91 | // Note the reinit is responsible for pushing to available channel once done with the VM. 92 | if vm == p.pendingReinit { 93 | p.mutex.Unlock() 94 | p.blockedReinit <- struct{}{} 95 | return 96 | } 97 | 98 | for i := range p.vms { 99 | if p.vms[i] == vm { 100 | p.acquired[i] = false 101 | p.mutex.Unlock() 102 | p.available <- struct{}{} 103 | return 104 | } 105 | } 106 | 107 | // VM instance not found anymore, hence pool reconfigured and can release the VM. 108 | 109 | p.mutex.Unlock() 110 | p.available <- struct{}{} 111 | 112 | vm.Close() 113 | } 114 | 115 | // Reset re-initializes the vms within the pool with the new policy 116 | // and data. The re-initialization takes place atomically: all new vms 117 | // are constructed in advance before touching the pool. Returns 118 | // either ErrNotReady, ErrInvalidPolicy or ErrInternal if an error 119 | // occurs. 120 | func (p *pool) SetPolicyData(policy []byte, data []byte) error { 121 | p.mutex.Lock() 122 | 123 | if !p.initialized { 124 | vm, err := newVM(policy, data, p.memoryMinPages, p.memoryMaxPages) 125 | if err == nil { 126 | p.initialized = true 127 | p.vms = append(p.vms, vm) 128 | p.acquired = append(p.acquired, false) 129 | p.policy, p.data = policy, data 130 | } else { 131 | err = fmt.Errorf("%v: %w", err, ErrInvalidPolicyOrData) 132 | } 133 | 134 | p.mutex.Unlock() 135 | return err 136 | } 137 | 138 | if p.closed { 139 | p.mutex.Unlock() 140 | return ErrNotReady 141 | } 142 | 143 | currentPolicy, currentData := p.policy, p.data 144 | p.mutex.Unlock() 145 | 146 | if bytes.Equal(policy, currentPolicy) && bytes.Equal(data, currentData) { 147 | return nil 148 | 149 | } 150 | 151 | err := p.setPolicyData(policy, data) 152 | if err != nil { 153 | return fmt.Errorf("%v: %w", err, ErrInternal) 154 | } 155 | 156 | return nil 157 | } 158 | 159 | // setPolicyData reinitializes the VMs one at a time. 160 | func (p *pool) setPolicyData(policy []byte, data []byte) error { 161 | for i, activations := 0, 0; true; i++ { 162 | vm := p.wait(i) 163 | if vm == nil { 164 | // All have been converted. 165 | return nil 166 | } 167 | 168 | if err := vm.SetPolicyData(policy, data); err != nil { 169 | // No guarantee about the VM state after an error; hence, remove. 170 | p.remove(i) 171 | p.Release(vm) 172 | 173 | // After the first successful activation, proceed through all the VMs, ignoring the remaining errors. 174 | if activations == 0 { 175 | return err 176 | } 177 | } else { 178 | p.Release(vm) 179 | } 180 | 181 | // Activate the policy and data, now that a single VM has been reset without errors. 182 | 183 | if activations == 0 { 184 | p.activate(policy, data) 185 | } 186 | 187 | activations++ 188 | } 189 | 190 | return nil 191 | } 192 | 193 | // Close waits for all the evaluations to finish and then releases the VMs. 194 | func (p *pool) Close() { 195 | for range p.vms { 196 | <-p.available 197 | } 198 | 199 | p.mutex.Lock() 200 | defer p.mutex.Unlock() 201 | 202 | for _, vm := range p.vms { 203 | if vm != nil { 204 | vm.Close() 205 | } 206 | } 207 | 208 | p.closed = true 209 | p.vms = nil 210 | } 211 | 212 | // wait steals the i'th VM instance. The VM has to be released afterwards. 213 | func (p *pool) wait(i int) *vm { 214 | p.mutex.Lock() 215 | defer p.mutex.Unlock() 216 | 217 | if i == len(p.vms) { 218 | return nil 219 | } 220 | 221 | vm := p.vms[i] 222 | isActive := p.acquired[i] 223 | p.acquired[i] = true 224 | 225 | if isActive { 226 | p.blockedReinit = make(chan struct{}, 1) 227 | p.pendingReinit = vm 228 | } 229 | 230 | p.mutex.Unlock() 231 | 232 | if isActive { 233 | <-p.blockedReinit 234 | } else { 235 | <-p.available 236 | } 237 | 238 | p.mutex.Lock() 239 | p.pendingReinit = nil 240 | return vm 241 | } 242 | 243 | // remove removes the i'th vm. 244 | func (p *pool) remove(i int) { 245 | p.mutex.Lock() 246 | defer p.mutex.Unlock() 247 | 248 | n := len(p.vms) 249 | if n > 1 { 250 | p.vms[i] = p.vms[n-1] 251 | } 252 | 253 | p.vms = p.vms[0 : n-1] 254 | p.acquired = p.acquired[0 : n-1] 255 | } 256 | 257 | func (p *pool) activate(policy []byte, data []byte) { 258 | p.mutex.Lock() 259 | defer p.mutex.Unlock() 260 | 261 | p.policy, p.data = policy, data 262 | } 263 | -------------------------------------------------------------------------------- /opa/tools.go: -------------------------------------------------------------------------------- 1 | // +build tools 2 | 3 | // Copyright 2020 The OPA Authors. All rights reserved. 4 | // Use of this source code is governed by an Apache2 5 | // license that can be found in the LICENSE file. 6 | 7 | // package tools imports require tooling so that 8 | // the dependencies are tracked with the OPA module 9 | // and are archived in the vendor directory. 10 | package tools 11 | 12 | import ( 13 | _ "golang.org/x/lint/golint" 14 | _ "golang.org/x/tools/cmd/goimports" 15 | ) 16 | -------------------------------------------------------------------------------- /opa/vm.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The OPA Authors. All rights reserved. 2 | // Use of this source code is governed by an Apache2 3 | // license that can be found in the LICENSE file. 4 | 5 | package opa 6 | 7 | import ( 8 | "bytes" 9 | "context" 10 | "encoding/json" 11 | "errors" 12 | "fmt" 13 | "strconv" 14 | "time" 15 | 16 | "github.com/open-policy-agent/opa/ast" 17 | "github.com/open-policy-agent/opa/metrics" 18 | "github.com/open-policy-agent/opa/topdown" 19 | "github.com/open-policy-agent/opa/topdown/builtins" 20 | wasm "github.com/wasmerio/go-ext-wasm/wasmer" 21 | ) 22 | 23 | type vm struct { 24 | instance *wasm.Instance // Pointer to avoid unintented destruction (triggering finalizers within). 25 | policy []byte 26 | data []byte 27 | memory *wasm.Memory 28 | memoryMin uint32 29 | memoryMax uint32 30 | bctx *topdown.BuiltinContext 31 | builtins map[int32]topdown.BuiltinFunc 32 | builtinResult *ast.Term 33 | baseHeapPtr int32 34 | dataAddr int32 35 | evalHeapPtr int32 36 | evalHeapTop int32 37 | eval func(...interface{}) (wasm.Value, error) 38 | evalCtxGetResult func(...interface{}) (wasm.Value, error) 39 | evalCtxNew func(...interface{}) (wasm.Value, error) 40 | evalCtxSetData func(...interface{}) (wasm.Value, error) 41 | evalCtxSetInput func(...interface{}) (wasm.Value, error) 42 | free func(...interface{}) (wasm.Value, error) 43 | heapPtrGet func(...interface{}) (wasm.Value, error) 44 | heapPtrSet func(...interface{}) (wasm.Value, error) 45 | heapTopGet func(...interface{}) (wasm.Value, error) 46 | heapTopSet func(...interface{}) (wasm.Value, error) 47 | jsonDump func(...interface{}) (wasm.Value, error) 48 | jsonParse func(...interface{}) (wasm.Value, error) 49 | malloc func(...interface{}) (wasm.Value, error) 50 | } 51 | 52 | func newVM(policy []byte, data []byte, memoryMin, memoryMax uint32) (*vm, error) { 53 | memory, err := wasm.NewMemory(memoryMin, memoryMax) 54 | if err != nil { 55 | return nil, err 56 | } 57 | 58 | imports, err := opaFunctions(wasm.NewImports()) 59 | if err != nil { 60 | return nil, err 61 | } 62 | 63 | imports, err = imports.AppendMemory("memory", memory) 64 | if err != nil { 65 | panic(err) 66 | } 67 | 68 | i, err := wasm.NewInstanceWithImports(policy, imports) 69 | if err != nil { 70 | return nil, err 71 | } 72 | 73 | v := &vm{ 74 | instance: &i, 75 | policy: policy, 76 | data: data, 77 | memory: memory, 78 | memoryMin: memoryMin, 79 | memoryMax: memoryMax, 80 | builtins: make(map[int32]topdown.BuiltinFunc), 81 | dataAddr: 0, 82 | eval: i.Exports["eval"], 83 | evalCtxGetResult: i.Exports["opa_eval_ctx_get_result"], 84 | evalCtxNew: i.Exports["opa_eval_ctx_new"], 85 | evalCtxSetData: i.Exports["opa_eval_ctx_set_data"], 86 | evalCtxSetInput: i.Exports["opa_eval_ctx_set_input"], 87 | free: i.Exports["opa_free"], 88 | heapPtrGet: i.Exports["opa_heap_ptr_get"], 89 | heapPtrSet: i.Exports["opa_heap_ptr_set"], 90 | heapTopGet: i.Exports["opa_heap_top_get"], 91 | heapTopSet: i.Exports["opa_heap_top_set"], 92 | jsonDump: i.Exports["opa_json_dump"], 93 | jsonParse: i.Exports["opa_json_parse"], 94 | malloc: i.Exports["opa_malloc"], 95 | } 96 | 97 | // Initialize the heap. 98 | 99 | if _, err := v.malloc(0); err != nil { 100 | return nil, err 101 | } 102 | 103 | if v.baseHeapPtr, err = v.getHeapState(); err != nil { 104 | return nil, err 105 | } 106 | 107 | if data != nil { 108 | if v.dataAddr, err = v.toRegoJSON(data, true); err != nil { 109 | return nil, err 110 | } 111 | } 112 | 113 | if v.evalHeapPtr, err = v.getHeapState(); err != nil { 114 | return nil, err 115 | } 116 | 117 | // For the opa builtin functions to access the instance. 118 | i.SetContextData(v) 119 | 120 | // Construct the builtin id to name mappings. 121 | 122 | val, err := i.Exports["builtins"]() 123 | if err != nil { 124 | return nil, err 125 | } 126 | 127 | builtins, err := v.fromRegoJSON(val.ToI32(), true) 128 | if err != nil { 129 | return nil, err 130 | } 131 | 132 | for name, id := range builtins.(map[string]interface{}) { 133 | f := topdown.GetBuiltin(name) 134 | if f == nil { 135 | return nil, fmt.Errorf("builtin '%s' not found", name) 136 | } 137 | 138 | n, err := id.(json.Number).Int64() 139 | if err != nil { 140 | panic(err) 141 | } 142 | 143 | v.builtins[int32(n)] = f 144 | } 145 | 146 | return v, nil 147 | } 148 | 149 | func (i *vm) Eval(ctx context.Context, input *interface{}) (interface{}, error) { 150 | if err := i.setHeapState(i.evalHeapPtr); err != nil { 151 | return nil, err 152 | } 153 | 154 | defer func() { 155 | i.bctx = nil 156 | }() 157 | 158 | // Parse the input JSON and activate it with the data. 159 | 160 | addr, err := i.evalCtxNew() 161 | if err != nil { 162 | return nil, err 163 | } 164 | 165 | ctxAddr := addr.ToI32() 166 | 167 | if i.dataAddr != 0 { 168 | if _, err := i.evalCtxSetData(ctxAddr, i.dataAddr); err != nil { 169 | return nil, err 170 | } 171 | } 172 | 173 | if input != nil { 174 | inputAddr, err := i.toRegoJSON(*input, false) 175 | if err != nil { 176 | return nil, err 177 | } 178 | 179 | if _, err := i.evalCtxSetInput(ctxAddr, inputAddr); err != nil { 180 | return nil, err 181 | } 182 | } 183 | 184 | // Evaluate the policy. 185 | func() { 186 | defer func() { 187 | if e := recover(); e != nil { 188 | switch e := e.(type) { 189 | case abortError: 190 | err = errors.New(e.message) 191 | case builtinError: 192 | err = e.err 193 | default: 194 | panic(e) 195 | } 196 | 197 | } 198 | }() 199 | _, err = i.eval(ctxAddr) 200 | }() 201 | 202 | if err != nil { 203 | return nil, err 204 | } 205 | 206 | resultAddr, err := i.evalCtxGetResult(ctxAddr) 207 | if err != nil { 208 | return nil, err 209 | } 210 | 211 | result, err := i.fromRegoJSON(resultAddr.ToI32(), false) 212 | 213 | // Skip free'ing input and result JSON as the heap will be reset next round anyway. 214 | 215 | return result, err 216 | } 217 | 218 | func (i *vm) SetPolicyData(policy []byte, data []byte) error { 219 | if !bytes.Equal(policy, i.policy) { 220 | // Swap the instance to a new one, with new policy. 221 | 222 | n, err := newVM(policy, data, i.memoryMin, i.memoryMax) 223 | if err != nil { 224 | return err 225 | } 226 | 227 | i.Close() 228 | 229 | *i = *n 230 | return nil 231 | } 232 | 233 | i.data = data 234 | i.dataAddr = 0 235 | 236 | var err error 237 | if err = i.setHeapState(i.baseHeapPtr); err != nil { 238 | return err 239 | } 240 | 241 | if data != nil { 242 | if i.dataAddr, err = i.toRegoJSON(data, true); err != nil { 243 | return err 244 | } 245 | } 246 | 247 | if i.evalHeapPtr, err = i.getHeapState(); err != nil { 248 | return err 249 | } 250 | 251 | return nil 252 | } 253 | 254 | func (i *vm) Close() { 255 | i.memory.Close() 256 | i.instance.Close() 257 | } 258 | 259 | type abortError struct { 260 | message string 261 | } 262 | 263 | // Abort is invoked by the policy if an internal error occurs during 264 | // the policy execution. 265 | func (i *vm) Abort(arg int32) { 266 | data := i.memory.Data()[arg:] 267 | n := bytes.IndexByte(data, 0) 268 | if n == -1 { 269 | panic("invalid abort argument") 270 | } 271 | 272 | panic(abortError{message: string(data[0:n])}) 273 | } 274 | 275 | type builtinError struct { 276 | err error 277 | } 278 | 279 | // Builtin executes a builtin for the policy. 280 | func (i *vm) Builtin(builtinID, ctx int32, args ...int32) int32 { 281 | 282 | // TODO: Returning proper errors instead of panicing. 283 | // TODO: To avoid growing the heap with every built-in call, recycle the JSON buffers since the free implementation is no-op. 284 | 285 | convertedArgs := make([]*ast.Term, len(args)) 286 | for j, arg := range args { 287 | x, err := i.fromRegoJSON(arg, true) 288 | if err != nil { 289 | panic(builtinError{err: err}) 290 | } 291 | 292 | y, err := ast.InterfaceToValue(x) 293 | if err != nil { 294 | panic(builtinError{err: err}) 295 | } 296 | 297 | convertedArgs[j] = ast.NewTerm(y) 298 | } 299 | 300 | if i.bctx == nil { 301 | i.bctx = &topdown.BuiltinContext{ 302 | Context: context.Background(), 303 | Cancel: nil, 304 | Runtime: nil, 305 | Time: ast.NumberTerm(json.Number(strconv.FormatInt(time.Now().UnixNano(), 10))), 306 | Metrics: metrics.New(), 307 | Cache: make(builtins.Cache), 308 | Location: nil, 309 | Tracers: nil, 310 | QueryID: 0, 311 | ParentID: 0, 312 | } 313 | } 314 | 315 | err := i.builtins[builtinID](*i.bctx, convertedArgs, i.iter) 316 | if err != nil { 317 | panic(builtinError{err: err}) 318 | } 319 | 320 | result, err := ast.JSON(i.builtinResult.Value) 321 | if err != nil { 322 | panic(builtinError{err: err}) 323 | } 324 | 325 | addr, err := i.toRegoJSON(result, true) 326 | if err != nil { 327 | panic(builtinError{err: err}) 328 | } 329 | 330 | return addr 331 | } 332 | 333 | func (i *vm) iter(result *ast.Term) error { 334 | i.builtinResult = result 335 | return nil 336 | } 337 | 338 | // fromRegoJSON converts Rego JSON to go native JSON. 339 | func (i *vm) fromRegoJSON(addr int32, free bool) (interface{}, error) { 340 | serialized, err := i.jsonDump(addr) 341 | if err != nil { 342 | return nil, err 343 | } 344 | 345 | data := i.memory.Data()[serialized.ToI32():] 346 | n := bytes.IndexByte(data, 0) 347 | if n < 0 { 348 | n = 0 349 | } 350 | 351 | // Parse the result into go types. 352 | 353 | decoder := json.NewDecoder(bytes.NewReader(data[0:n])) 354 | decoder.UseNumber() 355 | 356 | var result interface{} 357 | if err := decoder.Decode(&result); err != nil { 358 | return nil, err 359 | } 360 | 361 | if free { 362 | if _, err := i.free(serialized.ToI32()); err != nil { 363 | return nil, err 364 | } 365 | } 366 | 367 | return result, nil 368 | } 369 | 370 | // toRegoJSON converts go native JSON to Rego JSON. 371 | func (i *vm) toRegoJSON(v interface{}, free bool) (int32, error) { 372 | raw, ok := v.([]byte) 373 | if !ok { 374 | var err error 375 | raw, err = json.Marshal(v) 376 | if err != nil { 377 | return 0, err 378 | } 379 | } 380 | 381 | n := int32(len(raw)) 382 | pos, err := i.malloc(n) 383 | if err != nil { 384 | return 0, err 385 | } 386 | 387 | p := pos.ToI32() 388 | copy(i.memory.Data()[p:p+n], raw) 389 | 390 | addr, err := i.jsonParse(p, n) 391 | if err != nil { 392 | return 0, err 393 | } 394 | 395 | if free { 396 | if _, err := i.free(p); err != nil { 397 | return 0, err 398 | } 399 | } 400 | 401 | return addr.ToI32(), nil 402 | } 403 | 404 | func (i *vm) getHeapState() (int32, error) { 405 | ptr, err := i.heapPtrGet() 406 | if err != nil { 407 | return 0, err 408 | } 409 | 410 | return ptr.ToI32(), nil 411 | } 412 | 413 | func (i *vm) setHeapState(ptr int32) error { 414 | _, err := i.heapPtrSet(ptr) 415 | return err 416 | } 417 | --------------------------------------------------------------------------------