github.com/rajeev159/opa@v0.45.0/resolver/wasm/wasm.go (about) 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 wasm 6 7 import ( 8 "context" 9 "fmt" 10 "strconv" 11 12 "github.com/open-policy-agent/opa/ast" 13 "github.com/open-policy-agent/opa/internal/rego/opa" 14 "github.com/open-policy-agent/opa/resolver" 15 ) 16 17 // New creates a new Resolver instance which is using the Wasm module 18 // policy for the given entrypoint ref. 19 func New(entrypoints []ast.Ref, policy []byte, data interface{}) (*Resolver, error) { 20 e, err := opa.LookupEngine("wasm") 21 if err != nil { 22 return nil, err 23 } 24 o, err := e.New(). 25 WithPolicyBytes(policy). 26 WithDataJSON(data). 27 Init() 28 if err != nil { 29 return nil, err 30 } 31 32 // Construct a quick lookup table of ref -> entrypoint ID 33 // for handling evaluations. Only the entrypoints provided 34 // by the caller will be constructed, this may be a subset 35 // of entrypoints available in the Wasm module, however 36 // only the configured ones will be used when Eval() is 37 // called. 38 entrypointRefToID := ast.NewValueMap() 39 epIDs, err := o.Entrypoints(context.Background()) 40 if err != nil { 41 return nil, err 42 } 43 for path, id := range epIDs { 44 for _, ref := range entrypoints { 45 refPtr, err := ref.Ptr() 46 if err != nil { 47 return nil, err 48 } 49 if refPtr == path { 50 entrypointRefToID.Put(ref, ast.Number(strconv.Itoa(int(id)))) 51 } 52 } 53 } 54 55 return &Resolver{ 56 entrypoints: entrypoints, 57 entrypointIDs: entrypointRefToID, 58 o: o, 59 }, nil 60 } 61 62 // Resolver implements the resolver.Resolver interface 63 // using Wasm modules to perform an evaluation. 64 type Resolver struct { 65 entrypoints []ast.Ref 66 entrypointIDs *ast.ValueMap 67 o opa.EvalEngine 68 } 69 70 // Entrypoints returns a list of entrypoints this resolver is configured to 71 // perform evaluations on. 72 func (r *Resolver) Entrypoints() []ast.Ref { 73 return r.entrypoints 74 } 75 76 // Close shuts down the resolver. 77 func (r *Resolver) Close() { 78 r.o.Close() 79 } 80 81 // Eval performs an evaluation using the provided input and the Wasm module 82 // associated with this Resolver instance. 83 func (r *Resolver) Eval(ctx context.Context, input resolver.Input) (resolver.Result, error) { 84 v := r.entrypointIDs.Get(input.Ref) 85 if v == nil { 86 return resolver.Result{}, fmt.Errorf("unknown entrypoint %s", input.Ref) 87 } 88 89 numValue, ok := v.(ast.Number) 90 if !ok { 91 return resolver.Result{}, fmt.Errorf("internal error: invalid entrypoint id %s", numValue) 92 } 93 94 epID, ok := numValue.Int() 95 if !ok { 96 return resolver.Result{}, fmt.Errorf("internal error: invalid entrypoint id %s", numValue) 97 } 98 99 var in *interface{} 100 if input.Input != nil { 101 var str interface{} = []byte(input.Input.String()) 102 in = &str 103 } 104 105 opts := opa.EvalOpts{ 106 Input: in, 107 Entrypoint: int32(epID), 108 Metrics: input.Metrics, 109 } 110 out, err := r.o.Eval(ctx, opts) 111 if err != nil { 112 return resolver.Result{}, err 113 } 114 115 result, err := getResult(out) 116 if err != nil { 117 return resolver.Result{}, err 118 } 119 120 return resolver.Result{Value: result}, nil 121 } 122 123 // SetData will update the external data for the Wasm instance. 124 func (r *Resolver) SetData(ctx context.Context, data interface{}) error { 125 return r.o.SetData(ctx, data) 126 } 127 128 // SetDataPath will set the provided data on the wasm instance at the specified path. 129 func (r *Resolver) SetDataPath(ctx context.Context, path []string, data interface{}) error { 130 return r.o.SetDataPath(ctx, path, data) 131 } 132 133 // RemoveDataPath will remove any data at the specified path. 134 func (r *Resolver) RemoveDataPath(ctx context.Context, path []string) error { 135 return r.o.RemoveDataPath(ctx, path) 136 } 137 138 func getResult(evalResult *opa.Result) (ast.Value, error) { 139 140 parsed, err := ast.ParseTerm(string(evalResult.Result)) 141 if err != nil { 142 return nil, fmt.Errorf("failed to parse wasm result: %s", err) 143 } 144 145 resultSet, ok := parsed.Value.(ast.Set) 146 if !ok { 147 return nil, fmt.Errorf("illegal result type") 148 } 149 150 if resultSet.Len() == 0 { 151 return nil, nil 152 } 153 154 if resultSet.Len() > 1 { 155 return nil, fmt.Errorf("illegal result type") 156 } 157 158 var obj ast.Object 159 err = resultSet.Iter(func(term *ast.Term) error { 160 obj, ok = term.Value.(ast.Object) 161 if !ok || obj.Len() != 1 { 162 return fmt.Errorf("illegal result type") 163 } 164 return nil 165 }) 166 if err != nil { 167 return nil, err 168 } 169 170 result := obj.Get(ast.StringTerm("result")) 171 172 return result.Value, nil 173 }