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
116 lines
3.3 KiB
Go
116 lines
3.3 KiB
Go
// Copyright © 2023 Ory Corp
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package jsonnetsecure
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"io"
|
|
|
|
"github.com/pkg/errors"
|
|
"github.com/spf13/cobra"
|
|
|
|
"github.com/ory/x/landlockx"
|
|
)
|
|
|
|
const (
|
|
GiB uint64 = 1024 * 1024 * 1024
|
|
// Generous limit on virtual memory including the peak memory allocated by the Go runtime, the Jsonnet VM,
|
|
// and the Jsonnet script.
|
|
// This number was acquired by running:
|
|
// Found by trial and error with:
|
|
// `ulimit -Sv 1048576 && echo '{"Snippet": "{user_id: std.repeat(\'a\', 1000)}"}' | kratos jsonnet -0`
|
|
// NOTE: Ideally we'd like to limit RSS but that is not possible on Linux with `ulimit/setrlimit(2)` - only with cgroups.
|
|
virtualMemoryLimitBytes = 2 * GiB
|
|
)
|
|
|
|
func NewJsonnetCmd() *cobra.Command {
|
|
var null bool
|
|
cmd := &cobra.Command{
|
|
Use: "jsonnet",
|
|
Short: "Run Jsonnet as a CLI command",
|
|
Hidden: true,
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
// Lock the worker down to its already-open stdio: the
|
|
// only I/O it does is read snippets/parameters from stdin
|
|
// and write JSON to stdout/stderr. Denying all path-based
|
|
// filesystem access prevents a malicious snippet from
|
|
// touching the operator's file system even if the
|
|
// jsonnet VM ever exposed a path-import primitive.
|
|
if err := landlockx.ApplyEmpty(nil); err != nil {
|
|
return errors.Wrap(err, "failed to apply empty landlock sandbox")
|
|
}
|
|
|
|
// This could fail because current limits are lower than what we tried to set,
|
|
// so we still continue in this case.
|
|
SetVirtualMemoryLimit(virtualMemoryLimitBytes)
|
|
|
|
if null {
|
|
return scan(cmd.OutOrStdout(), cmd.InOrStdin())
|
|
}
|
|
|
|
input, err := io.ReadAll(cmd.InOrStdin())
|
|
if err != nil {
|
|
return errors.Wrap(err, "failed to read from stdin")
|
|
}
|
|
|
|
json, err := eval(input)
|
|
if err != nil {
|
|
return errors.Wrap(err, "failed to evaluate jsonnet")
|
|
}
|
|
|
|
if _, err := io.WriteString(cmd.OutOrStdout(), json); err != nil {
|
|
return errors.Wrap(err, "failed to write json output")
|
|
}
|
|
return nil
|
|
},
|
|
}
|
|
cmd.Flags().BoolVarP(&null, "null", "0", false,
|
|
`Read multiple snippets and parameters from stdin separated by null bytes.
|
|
Output will be in the same order as inputs, separated by null bytes.
|
|
Evaluation errors will also be reported to stdout, separated by null bytes.
|
|
Non-recoverable errors are written to stderr and the program will terminate with a non-zero exit code.`)
|
|
|
|
return cmd
|
|
}
|
|
|
|
func scan(w io.Writer, r io.Reader) error {
|
|
scanner := bufio.NewScanner(r)
|
|
scanner.Split(splitNull)
|
|
for scanner.Scan() {
|
|
json, err := eval(scanner.Bytes())
|
|
if err != nil {
|
|
json = fmt.Sprintf("ERROR: %s", err)
|
|
}
|
|
if _, err := fmt.Fprintf(w, "%s%c", json, 0); err != nil {
|
|
return errors.Wrap(err, "failed to write json output")
|
|
}
|
|
}
|
|
return errors.Wrap(scanner.Err(), "failed to read from stdin")
|
|
}
|
|
|
|
func eval(input []byte) (json string, err error) {
|
|
var params processParameters
|
|
if err := params.Decode(input); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
vm := MakeInProcessVM()
|
|
|
|
for _, it := range params.ExtCodes {
|
|
vm.ExtCode(it.Key, it.Value)
|
|
}
|
|
for _, it := range params.ExtVars {
|
|
vm.ExtVar(it.Key, it.Value)
|
|
}
|
|
for _, it := range params.TLACodes {
|
|
vm.TLACode(it.Key, it.Value)
|
|
}
|
|
for _, it := range params.TLAVars {
|
|
vm.TLAVar(it.Key, it.Value)
|
|
}
|
|
|
|
return vm.EvaluateAnonymousSnippet(params.Filename, params.Snippet)
|
|
}
|