From 7e44d75f68ac43b62bfb250be1ac76e27436d5ab Mon Sep 17 00:00:00 2001 From: Christine Dodrill Date: Sat, 14 Dec 2019 21:04:28 +0000 Subject: [PATCH] support policies, closes #12 --- cmd/internal/policy.go | 18 ++++++++++++++++ cmd/internal/topic.go | 3 ++- cmd/internal/types.go | 5 +++-- cmd/wasmcloud-executor/main.go | 10 +++++++++ cmd/wasmcloud/handler_create.go | 23 ++++++++++++++++++++- cmd/wasmcloud/handler_update.go | 23 ++++++++++++++++++++- cmd/wasmcloud/run.go | 26 +++++++++++++++++------- cmd/wasmcloudd/handler.go | 35 +++++++++++++++++++++++++++++++- cmd/wasmcloudd/handler_invoke.go | 3 ++- cmd/wasmcloudd/models.go | 1 + cmd/wasmcloudd/policy.go | 14 +++++++++++++ executor/executor.go | 13 +++++++++--- go.mod | 3 +++ go.sum | 6 ++++++ 14 files changed, 166 insertions(+), 17 deletions(-) create mode 100644 cmd/internal/policy.go create mode 100644 cmd/wasmcloudd/policy.go diff --git a/cmd/internal/policy.go b/cmd/internal/policy.go new file mode 100644 index 0000000..641bd42 --- /dev/null +++ b/cmd/internal/policy.go @@ -0,0 +1,18 @@ +package internal + +import ( + "fmt" + + "github.com/hashicorp/go-multierror" + "within.website/olin/policy" +) + +func ValidatePolicy(pol policy.Policy) error { + var err error + + if pol.RamPageLimit > 2048 { + err = multierror.Append(err, fmt.Errorf("ram page limit is %d, which is over the limit of 2048", pol.RamPageLimit)) + } + + return err +} diff --git a/cmd/internal/topic.go b/cmd/internal/topic.go index 8bdfaa5..a32ea49 100644 --- a/cmd/internal/topic.go +++ b/cmd/internal/topic.go @@ -3,12 +3,13 @@ package internal import "github.com/rogpeppe/go-internal/txtar" // Topic name for wasmcloud -> executor communication -const TopicName = "wasmcloud-to-executor-09d7e475-71ac-4bdd-be37-050af7a81c58" +const TopicName = "exec" type ExecRequest struct { WASMCID string `json:"wasmcid"` Name string `json:"name"` Data []byte `json:"data"` + Policy []byte `json:"policy"` ABI ABI `json:"abi"` Env map[string]string `json:"env"` UUID string `json:"uuid"` diff --git a/cmd/internal/types.go b/cmd/internal/types.go index 4946f81..f8eec1b 100644 --- a/cmd/internal/types.go +++ b/cmd/internal/types.go @@ -8,6 +8,7 @@ const ( ) type Handler struct { - WASM []byte `json:"wasm"` - ABI string `json:"abi"` + WASM []byte `json:"wasm"` + Policy []byte `json:"policy"` + ABI string `json:"abi"` } diff --git a/cmd/wasmcloud-executor/main.go b/cmd/wasmcloud-executor/main.go index e7b8775..87ec727 100644 --- a/cmd/wasmcloud-executor/main.go +++ b/cmd/wasmcloud-executor/main.go @@ -21,6 +21,7 @@ import ( "github.com/rogpeppe/go-internal/txtar" "tulpa.dev/within/wasmcloud/cmd/internal" "tulpa.dev/within/wasmcloud/executor" + "within.website/olin/policy" ) var ( @@ -136,8 +137,17 @@ func waitForNewWASM(ctx context.Context, wg *sync.WaitGroup, sh *shell.Shell, nc LogSink: logBuf, } + pol, err := policy.Parse(er.Name+".policy", er.Policy) + if err != nil { + log.Printf("can't get policy: %v", err) + continue + } + + c.Policy = &pol + result, err := executor.Run(c) if err != nil { + fmt.Fprintln(logBuf, "can't run binary:", err) log.Printf("can't run binary: %v", err) } diff --git a/cmd/wasmcloud/handler_create.go b/cmd/wasmcloud/handler_create.go index 509c370..e2c8106 100644 --- a/cmd/wasmcloud/handler_create.go +++ b/cmd/wasmcloud/handler_create.go @@ -15,10 +15,11 @@ import ( "github.com/google/subcommands" "tulpa.dev/within/wasmcloud/cmd/internal" + "within.website/olin/policy" ) type handlerCreateCmd struct { - abi string + abi, policyFile string } func (handlerCreateCmd) Name() string { return "create" } @@ -38,6 +39,7 @@ Flags: func (h *handlerCreateCmd) SetFlags(fs *flag.FlagSet) { fs.StringVar(&h.abi, "abi", "cwa", "WebAssembly ABI to use for the handler") + fs.StringVar(&h.policyFile, "policy", "", "if set, use this policy file for the handler") } func (h handlerCreateCmd) Execute(ctx context.Context, fs *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus { @@ -57,6 +59,25 @@ func (h handlerCreateCmd) Execute(ctx context.Context, fs *flag.FlagSet, _ ...in WASM: data, } + if h.policyFile != "" { + data, err := ioutil.ReadFile(h.policyFile) + if err != nil { + log.Fatal(err) + } + + pol, err := policy.Parse(h.policyFile, data) + if err != nil { + log.Fatal(err) + } + + err = internal.ValidatePolicy(pol) + if err != nil { + log.Fatal(err) + } + + hdlr.Policy = data + } + bodyData, _ := json.Marshal(hdlr) req, err := http.NewRequestWithContext(ctx, http.MethodPost, *apiServer+"/api/handler/create", bytes.NewBuffer(bodyData)) if err != nil { diff --git a/cmd/wasmcloud/handler_update.go b/cmd/wasmcloud/handler_update.go index cf50bc1..3e961a5 100644 --- a/cmd/wasmcloud/handler_update.go +++ b/cmd/wasmcloud/handler_update.go @@ -15,10 +15,11 @@ import ( "github.com/google/subcommands" "tulpa.dev/within/wasmcloud/cmd/internal" + "within.website/olin/policy" ) type handlerUpdateCmd struct { - abi string + abi, policyFile string } func (handlerUpdateCmd) Name() string { return "update" } @@ -36,6 +37,7 @@ Flags: func (h *handlerUpdateCmd) SetFlags(fs *flag.FlagSet) { fs.StringVar(&h.abi, "abi", "cwa", "WebAssembly ABI to use for the handler") + fs.StringVar(&h.policyFile, "policy", "", "if set, use this policy file for the handler") } func (h handlerUpdateCmd) Execute(ctx context.Context, fs *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus { @@ -56,6 +58,25 @@ func (h handlerUpdateCmd) Execute(ctx context.Context, fs *flag.FlagSet, _ ...in WASM: data, } + if h.policyFile != "" { + data, err := ioutil.ReadFile(h.policyFile) + if err != nil { + log.Fatal(err) + } + + pol, err := policy.Parse(h.policyFile, data) + if err != nil { + log.Fatal(err) + } + + err = internal.ValidatePolicy(pol) + if err != nil { + log.Fatal(err) + } + + hdlr.Policy = data + } + bodyData, _ := json.Marshal(hdlr) req, err := http.NewRequestWithContext(ctx, http.MethodPost, *apiServer+"/api/handler/update?name="+hname, bytes.NewBuffer(bodyData)) if err != nil { diff --git a/cmd/wasmcloud/run.go b/cmd/wasmcloud/run.go index c3c8d01..06f3466 100644 --- a/cmd/wasmcloud/run.go +++ b/cmd/wasmcloud/run.go @@ -17,12 +17,12 @@ import ( "github.com/perlin-network/life/exec" "github.com/rogpeppe/go-internal/txtar" "tulpa.dev/within/wasmcloud/executor" + "within.website/olin/policy" ) type runCmd struct { funcName, abi string - gasLimit uint64 - ramLimit int + policyFile string } func (runCmd) Name() string { return "run" } @@ -33,7 +33,6 @@ func (runCmd) Usage() string { return `wasmcloud run [options] $ wasmcloud run olinfetch.wasm -$ wasmcloud run -abi dagger -func-name dagger_main hello_dagger.wasm Run a given webassembly binary and return the output. @@ -47,8 +46,7 @@ Flags: func (r *runCmd) SetFlags(fs *flag.FlagSet) { fs.StringVar(&r.funcName, "func-name", "_start", "entrypoint function to call") fs.StringVar(&r.abi, "abi", "cwa", "ABI to use") - fs.Uint64Var(&r.gasLimit, "gas-limit", 1048576, "number of wasm instructions per execution") - fs.IntVar(&r.ramLimit, "ram-limit", 128, "number of wasm pages that can be used") + fs.StringVar(&r.policyFile, "policy", "", "if set, the policy file to use") } func (r runCmd) Execute(ctx context.Context, fs *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus { @@ -86,9 +84,7 @@ func (r runCmd) Execute(ctx context.Context, fs *flag.FlagSet, _ ...interface{}) var stdin bytes.Buffer c := executor.Config{ VMConfig: exec.VMConfig{ - GasLimit: r.gasLimit, ReturnOnGasLimitExceeded: true, - MaxMemoryPages: r.ramLimit, }, Name: filepath.Base(fname), @@ -101,6 +97,22 @@ func (r runCmd) Execute(ctx context.Context, fs *flag.FlagSet, _ ...interface{}) LogSink: logBuf, } + if r.policyFile != "" { + data, err := ioutil.ReadFile(r.policyFile) + if err != nil { + log.Fatal(err) + } + + pol, err := policy.Parse(r.policyFile, data) + if err != nil { + log.Fatal(err) + } + + c.VMConfig.MaxMemoryPages = int(pol.RamPageLimit) + c.VMConfig.GasLimit = uint64(pol.GasLimit) + c.Policy = &pol + } + result, err := executor.Run(c) if err != nil { log.Printf("can't run binary: %v", err) diff --git a/cmd/wasmcloudd/handler.go b/cmd/wasmcloudd/handler.go index 4f066e5..1c8ab46 100644 --- a/cmd/wasmcloudd/handler.go +++ b/cmd/wasmcloudd/handler.go @@ -16,6 +16,7 @@ import ( "within.website/ln" "within.website/ln/opname" "within.website/olin/namegen" + "within.website/olin/policy" ) func deleteHandler(w http.ResponseWriter, r *http.Request, u *User) { @@ -72,6 +73,15 @@ func updateHandler(w http.ResponseWriter, r *http.Request, u *User) { return } + if len(uHdlr.Policy) != 0 { + err = validatePolicy(name, uHdlr.Policy) + if err != nil { + ln.Error(ctx, err) + http.Error(w, "policy validation failure", http.StatusBadRequest) + return + } + } + cid, err := uploadHandler(uHdlr) if err != nil { ln.Error(ctx, err) @@ -79,6 +89,7 @@ func updateHandler(w http.ResponseWriter, r *http.Request, u *User) { return } + hdlr.Policy = uHdlr.Policy hdlr.Path = cid if err := db.Save(&hdlr).Error; err != nil { ln.Error(ctx, err) @@ -89,6 +100,15 @@ func updateHandler(w http.ResponseWriter, r *http.Request, u *User) { json.NewEncoder(w).Encode(hdlr) } +func validatePolicy(name string, data []byte) error { + pol, err := policy.Parse(name+".policy", data) + if err != nil { + return err + } + + return internal.ValidatePolicy(pol) +} + func uploadHandler(hdlr internal.Handler) (string, error) { sh := shell.NewShell(*ipfsURL) @@ -135,6 +155,19 @@ func createHandler(w http.ResponseWriter, r *http.Request, u *User) { return } + n := namegen.Next() + + if len(hdlr.Policy) == 0 { + hdlr.Policy = []byte(defaultPolicy) + } + + err = validatePolicy(n, hdlr.Policy) + if err != nil { + ln.Error(ctx, err) + http.Error(w, "policy validation failure", http.StatusBadRequest) + return + } + cid, err := uploadHandler(hdlr) if err != nil { ln.Error(ctx, err) @@ -142,12 +175,12 @@ func createHandler(w http.ResponseWriter, r *http.Request, u *User) { return } - n := namegen.Next() h := Handler{ Name: n, User: *u, UserID: u.ID, Path: cid, + Policy: hdlr.Policy, } if err := db.Save(&h).Error; err != nil { diff --git a/cmd/wasmcloudd/handler_invoke.go b/cmd/wasmcloudd/handler_invoke.go index 3c87d25..c19f661 100644 --- a/cmd/wasmcloudd/handler_invoke.go +++ b/cmd/wasmcloudd/handler_invoke.go @@ -23,7 +23,8 @@ func runHandler(ctx context.Context, hdlr Handler, timeout time.Duration, messag "RUN_ID": execID, "MAGIC_CONCH": "yes", }, - UUID: execID, + UUID: execID, + Policy: hdlr.Policy, } encData, err := json.Marshal(er) diff --git a/cmd/wasmcloudd/models.go b/cmd/wasmcloudd/models.go index b2c7c16..596a361 100644 --- a/cmd/wasmcloudd/models.go +++ b/cmd/wasmcloudd/models.go @@ -37,6 +37,7 @@ type Handler struct { gorm.Model Name string `gorm:"unique;not null"` Path string + Policy []byte UserID uint `json:"-"` User User `json:"-"` } diff --git a/cmd/wasmcloudd/policy.go b/cmd/wasmcloudd/policy.go new file mode 100644 index 0000000..2b077de --- /dev/null +++ b/cmd/wasmcloudd/policy.go @@ -0,0 +1,14 @@ +package main + +const defaultPolicy = `## The default policy + +allow ( + ^null://$ + ^random://$ + ^zero://$ + ^https://xena.greedo.xeserv.us/files/hello_olin.txt$ +) + +ram-page-limit 128 +gas-limit 1048576 +` diff --git a/executor/executor.go b/executor/executor.go index 64cccaa..42539eb 100644 --- a/executor/executor.go +++ b/executor/executor.go @@ -15,6 +15,7 @@ import ( "github.com/perlin-network/life/exec" "github.com/rogpeppe/go-internal/txtar" "within.website/olin/abi/cwa" + "within.website/olin/policy" ) var ( @@ -24,6 +25,7 @@ var ( type Config struct { exec.VMConfig + Policy *policy.Policy Name string FuncName string Env map[string]string @@ -77,7 +79,8 @@ func Run(c Config) (*Result, error) { p.Stdout = c.Stdout p.Stderr = c.Stderr p.Logger = log.New(io.MultiWriter(c.LogSink, os.Stdout), c.Name+" ", log.LstdFlags) - p.HC.Transport = WasmcloudBrandingTransport(c.Name, p.HC.Transport) + p.HC.Transport = WasmcloudBrandingTransport(c.Name, http.DefaultTransport) + p.Policy = c.Policy gp := &compiler.SimpleGasPolicy{GasPerInstruction: 1} vm, err := exec.NewVirtualMachine(c.Binary, c.VMConfig, p, gp) @@ -92,9 +95,13 @@ func Run(c Config) (*Result, error) { pagesBefore := len(vm.Memory) / 65536 begin := time.Now() - ret, err := vm.Run(main) + ret, err := vm.RunWithGasLimit(main, int(c.Policy.GasLimit)) if err != nil { - return nil, err + vm.PrintStackTrace() + return &Result{ + Status: -2, + StartTime: begin, + }, err } dur := time.Since(begin) diff --git a/go.mod b/go.mod index 69e8a5c..17e9d8c 100644 --- a/go.mod +++ b/go.mod @@ -11,8 +11,10 @@ require ( github.com/google/uuid v1.1.1 github.com/gorilla/mux v1.7.3 github.com/gosuri/uitable v0.0.4 + github.com/hashicorp/go-multierror v1.0.0 github.com/ipfs/go-ipfs-api v0.0.2 github.com/jinzhu/gorm v1.9.11 + github.com/kr/pretty v0.1.0 github.com/lib/pq v1.1.1 github.com/manifoldco/promptui v0.6.0 github.com/mattn/go-sqlite3 v2.0.1+incompatible @@ -20,6 +22,7 @@ require ( github.com/nats-io/nats.go v1.9.1 github.com/perlin-network/life v0.0.0-20191203030451-05c0e0f7eaea github.com/rogpeppe/go-internal v1.5.0 + github.com/rogpeppe/gohack v1.0.2 // indirect golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392 within.website/ln v0.7.0 within.website/olin v0.4.1-0.20191214133128-bde4927fad6b diff --git a/go.sum b/go.sum index b32dab1..b711cb6 100644 --- a/go.sum +++ b/go.sum @@ -143,7 +143,9 @@ github.com/gxed/hashland/keccakpg v0.0.1 h1:wrk3uMNaMxbXiHibbPO4S0ymqJMm41WiudyF github.com/gxed/hashland/keccakpg v0.0.1/go.mod h1:kRzw3HkwxFU1mpmPP8v1WyQzwdGfmKFJ6tItnhQ67kU= github.com/gxed/hashland/murmur3 v0.0.1 h1:SheiaIt0sda5K+8FLz952/1iWS9zrnKsEJaOJu4ZbSc= github.com/gxed/hashland/murmur3 v0.0.1/go.mod h1:KjXop02n4/ckmZSnY2+HKcLud/tcmvhST0bie/0lS48= +github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= @@ -299,8 +301,11 @@ github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rogpeppe/go-internal v1.0.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.5.0 h1:Usqs0/lDK/NqTkvrmKSwA/3XkZAs7ZAW/eLeQ2MVBTw= github.com/rogpeppe/go-internal v1.5.0/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/gohack v1.0.2 h1:lYiGLFzvZC3RvzeE4GoUV3nTecDxTpVusVsQY4nAXGc= +github.com/rogpeppe/gohack v1.0.2/go.mod h1:DE8wqaJRPvHU0fden5cSYy7ar2dTbbccPT/eeOYcbcE= github.com/sendgrid/rest v2.4.1+incompatible/go.mod h1:kXX7q3jZtJXK5c5qK83bSGMdV6tsOE70KbHoqJls4lE= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= @@ -434,6 +439,7 @@ gopkg.in/alecthomas/kingpin.v3-unstable v3.0.0-20171010053543-63abe20a23e2/go.mo gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0 h1:0vLT13EuvQ0hNvakwLuFZ/jYrLp5F3kcWHXdRggjCE8= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=