Some checks failed
CI / Run lints and checks (push) Failing after 47s
CI / Run tests (push) Failing after 1s
CI / Run race tests (push) Failing after 5s
CI / Build CLI docs (push) Has been skipped
CI / Run buf actions (push) Failing after 5s
CodeQL / Analyze (go) (push) Failing after 3s
CodeQL / Analyze (javascript) (push) Failing after 2s
Docker Image Scanners / scanners (push) Failing after 3s
Format / format (push) Failing after 3s
Synchronize Issue Labels / Synchronize Issue Labels (push) Has been skipped
CI / Generate release (push) Has been skipped
CI / Draft newsletter (push) Has been skipped
CI / Pending approval Slack notification (push) Has been skipped
CI / Send newsletter (push) Has been skipped
154 lines
4.3 KiB
Go
154 lines
4.3 KiB
Go
package jsonnetsecure
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"os/exec"
|
|
"path"
|
|
"runtime"
|
|
"testing"
|
|
|
|
"github.com/google/go-jsonnet"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
type (
|
|
VM interface {
|
|
EvaluateAnonymousSnippet(filename string, snippet string) (json string, formattedErr error)
|
|
ExtCode(key string, val string)
|
|
ExtVar(key string, val string)
|
|
TLACode(key string, val string)
|
|
TLAVar(key string, val string)
|
|
}
|
|
|
|
kv struct {
|
|
Key, Value string
|
|
}
|
|
processParameters struct {
|
|
Filename, Snippet string
|
|
TLACodes, TLAVars, ExtCodes, ExtVars []kv
|
|
}
|
|
|
|
vmOptions struct {
|
|
jsonnetBinaryPath string
|
|
args []string
|
|
ctx context.Context
|
|
pool *pool
|
|
}
|
|
|
|
Option func(o *vmOptions)
|
|
)
|
|
|
|
func (pp *processParameters) EncodeTo(w io.Writer) error {
|
|
return json.NewEncoder(w).Encode(pp)
|
|
}
|
|
|
|
func (pp *processParameters) Decode(d []byte) error {
|
|
return json.Unmarshal(d, pp)
|
|
}
|
|
|
|
func newVMOptions() *vmOptions {
|
|
jsonnetBinaryPath, _ := os.Executable()
|
|
return &vmOptions{
|
|
jsonnetBinaryPath: jsonnetBinaryPath,
|
|
ctx: context.Background(),
|
|
}
|
|
}
|
|
|
|
func WithContext(ctx context.Context) Option {
|
|
return func(o *vmOptions) {
|
|
o.ctx = ctx
|
|
}
|
|
}
|
|
|
|
func WithJsonnetBinary(jsonnetBinaryPath string) Option {
|
|
return func(o *vmOptions) {
|
|
o.jsonnetBinaryPath = jsonnetBinaryPath
|
|
}
|
|
}
|
|
|
|
func WithProcessArgs(args ...string) Option {
|
|
return func(o *vmOptions) {
|
|
o.args = args
|
|
}
|
|
}
|
|
|
|
// ErrNoProcessPool is returned by MakeSecureVM when called without a process
|
|
// pool. It is a distinct error because the alternative — quietly returning an
|
|
// in-process VM — would strip the isolation callers of this package rely on.
|
|
var ErrNoProcessPool = errors.New("jsonnetsecure: a process pool is required; use MakeInProcessVM to evaluate in this process without isolation")
|
|
|
|
// MakeSecureVM returns a VM that evaluates snippets in a worker process taken
|
|
// from p, so that a snippet which exhausts memory, spins on the CPU, or crashes
|
|
// takes down only that worker.
|
|
//
|
|
// p is a required argument rather than an option because a VM without a pool
|
|
// offers no isolation at all. Passing a nil pool returns ErrNoProcessPool.
|
|
func MakeSecureVM(p Pool, opts ...Option) (VM, error) {
|
|
// A nil *pool inside a non-nil Pool interface is not reachable from outside
|
|
// this package (Pool has an unexported method), but check the concrete
|
|
// value anyway so a future in-package mistake cannot slip through.
|
|
concrete, _ := p.(*pool)
|
|
if p == nil || concrete == nil {
|
|
return nil, errors.WithStack(ErrNoProcessPool)
|
|
}
|
|
|
|
options := newVMOptions()
|
|
for _, o := range opts {
|
|
o(options)
|
|
}
|
|
options.pool = concrete
|
|
|
|
return NewProcessPoolVM(options), nil
|
|
}
|
|
|
|
// MakeInProcessVM returns a Jsonnet VM that evaluates in the calling process
|
|
// with imports disabled. It provides no isolation: a malicious or buggy snippet
|
|
// can exhaust this process's memory and CPU, so it is only safe for trusted
|
|
// input. The two legitimate uses are the jsonnet subcommand, which is itself
|
|
// the isolation boundary, and offline CLI linting. Everything that evaluates
|
|
// tenant-supplied Jsonnet must use MakeSecureVM.
|
|
func MakeInProcessVM() *jsonnet.VM {
|
|
vm := jsonnet.MakeVM()
|
|
vm.Importer(new(ErrorImporter))
|
|
return vm
|
|
}
|
|
|
|
// ErrorImporter errors when calling "import".
|
|
type ErrorImporter struct{}
|
|
|
|
// Import fetches data from a map entry.
|
|
// All paths are treated as absolute keys.
|
|
func (importer *ErrorImporter) Import(importedFrom, importedPath string) (contents jsonnet.Contents, foundAt string, err error) {
|
|
return jsonnet.Contents{}, "", fmt.Errorf("import not available %v", importedPath)
|
|
}
|
|
|
|
func JsonnetTestBinary(t testing.TB) string {
|
|
t.Helper()
|
|
|
|
// We can force the usage of a given jsonnet executable.
|
|
// Useful to test different versions, or run the tests under wine.
|
|
if s := os.Getenv("ORY_JSONNET_PATH"); s != "" {
|
|
return s
|
|
}
|
|
|
|
var stderr bytes.Buffer
|
|
// Using `t.TempDir()` results in permissions errors on Windows, sometimes.
|
|
outPath := path.Join(os.TempDir(), "jsonnet")
|
|
if runtime.GOOS == "windows" {
|
|
outPath = outPath + ".exe"
|
|
}
|
|
cmd := exec.Command("go", "build", "-o", outPath, "github.com/ory/x/jsonnetsecure/cmd")
|
|
cmd.Stderr = &stderr
|
|
|
|
if err := cmd.Run(); err != nil || stderr.Len() != 0 {
|
|
t.Fatalf("building the Go binary returned error: %v\n%s", err, stderr.String())
|
|
}
|
|
|
|
return outPath
|
|
}
|