From 6cab1c5b0acc6348b1e1c5197edd3115107b1b60 Mon Sep 17 00:00:00 2001 From: Oliver Newman Date: Tue, 26 Sep 2023 14:25:28 -0400 Subject: [PATCH] [engine] Support static env var pass-throughs Allow users to selectively poke holes in the subprocess sandbox by specifying `pass_through_env` in shac.textproto. `pass_through_env` is a list of environment variables that should be passed through the sandbox, along with bits indicating whether the variable's value represents a file that should also be mounted and, if so, whether it should be writeable by subprocesses. We can immediately use this feature to grant Go checks in this repository access to $HOME so they can share the same go cache with the rest of the system (ideally we could use $GOCACHE, but it's not guaranteed to be set, and if it's not set it's inferred from $HOME). Same for tests that run `go run` since they can make use of the cache instead of doing a full recompile on every test run; as a result, the runtime of `go test ./internal/engine` went from ~4.2 seconds to ~1.2 seconds on my workstation. This is a medium-term workaround until we support declaring pass-throughs in Starlark, which would allow things like running `go env GOCACHE` to obtain and pass through just the $GOCACHE directory, while omitting the rest of $HOME. Change-Id: I0bcc9956c4c4e2e9cca292925c66f6aed6f07524 --- README.md | 2 + checks/common.star | 25 +- checks/go.star | 35 ++- internal/engine/run.go | 28 +- internal/engine/run_test.go | 106 ++++++++ internal/engine/runtime_ctx_os.go | 17 ++ internal/engine/shac.pb.go | 248 ++++++++++++------ internal/engine/shac.proto | 14 + .../testdata/ctx-os-exec-pass_through_env.go | 63 +++++ .../ctx-os-exec-pass_through_env.star | 36 +++ .../ctx-os-exec-10Mib-exceed.star | 1 - .../testdata/print/ctx-os-exec-10Mib.star | 1 - .../testdata/print/ctx-os-exec-stdin.star | 1 - internal/engine/version.go | 2 +- scripts/tests.sh | 12 +- shac.textproto | 20 +- 16 files changed, 489 insertions(+), 122 deletions(-) create mode 100644 internal/engine/testdata/ctx-os-exec-pass_through_env.go create mode 100644 internal/engine/testdata/ctx-os-exec-pass_through_env.star diff --git a/README.md b/README.md index 4060c08..cbfdc07 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,8 @@ Planned features/changes, in descending order by priority: * [ ] Built-in formatting of Starlark files * [ ] Configurable "pass-throughs" - non-default environment variables and mounts that can optionally be passed through to the sandbox + * [x] Passed-through environment variables statically declared in + shac.textproto * [ ] Add `glob` arguments to `ctx.scm.{all,affected}_files()` functions for easier filtering * [ ] Filesystem sandboxing on MacOS diff --git a/checks/common.star b/checks/common.star index a991f39..8a0e06e 100644 --- a/checks/common.star +++ b/checks/common.star @@ -14,21 +14,27 @@ def go_install(ctx, pkg, version): """Runs `go install`.""" - tool_name = pkg.split("/")[-1] - env = go_env(ctx, tool_name) + + env = go_env() + + # TODO(olivernewman): Stop using a separate GOPATH for each tool, and instead + # install the tools sequentially. Multiple concurrent `go install` runs on the + # same GOPATH results in race conditions. + env["GOBIN"] = ctx.scm.root + "/.tools/gobin" + + # TODO(olivernewman): Stop using a separate GOPATH for each tool, and instead + # install the tools sequentially. Multiple concurrent `go install` runs on the + # same GOPATH results in race conditions. ctx.os.exec( ["go", "install", "%s@%s" % (pkg, version)], allow_network = True, env = env, ).wait() + tool_name = pkg.split("/")[-1] return "%s/%s" % (env["GOBIN"], tool_name) -def go_env(ctx, key): - # TODO(olivernewman): Stop using a separate GOPATH for each tool, and instead - # install the tools sequentially. Multiple concurrent `go install` runs on the - # same GOPATH results in race conditions. - gopath = "%s/.tools/gopath/%s" % (ctx.scm.root, key) +def go_env(): return { # Disable cgo as it's not necessary and not all development platforms have # the necessary headers. @@ -38,11 +44,6 @@ def go_env(ctx, key): # to fail on some machines. "-buildvcs=false", ]), - "GOPATH": gopath, - "GOBIN": "%s/bin" % gopath, - # Cache within the directory to avoid writing to $HOME/.cache. - # TODO(olivernewman): Implement named caches. - "GOCACHE": "%s/.tools/gocache" % ctx.scm.root, # TODO(olivernewman): The default gopackagesdriver is broken within an # nsjail. "GOPACKAGESDRIVER": "off", diff --git a/checks/go.star b/checks/go.star index 517bf01..a279557 100644 --- a/checks/go.star +++ b/checks/go.star @@ -52,26 +52,34 @@ def _gosec(ctx, version = "v2.15.0", level = "error"): be rolled from time to time. level: level at which issues should be emitted. """ + affected_files = set(ctx.scm.affected_files()) exe = go_install(ctx, "github.com/securego/gosec/v2/cmd/gosec", version) res = ctx.os.exec([exe, "-fmt=json", "-quiet", "-exclude=G304", "-exclude-dir=.tools", "./..."], raise_on_failure = False).wait() if res.retcode: # Schema is https://pkg.go.dev/github.com/securego/gosec/v2#ReportInfo d = json.decode(res.stdout) o = len(ctx.scm.root) + 1 - for file, data in d["Golang errors"]: - ctx.emit.finding( - level = "error", - message = i["error"], - filepath = file[o:], - line = int(i["line"]), - col = int(i["column"]), - ) + for file, errs in d["Golang errors"].items(): + filepath = file[o:] + if filepath not in affected_files: + continue + for e in errs: + ctx.emit.finding( + level = "error", + message = e["error"], + filepath = filepath, + line = int(e["line"]), + col = int(e["column"]), + ) for i in d["Issues"]: line = i["line"].split("-")[0] + filepath = i["file"][o:] + if filepath not in affected_files: + continue ctx.emit.finding( level = level, message = i["rule_id"] + ": " + i["details"], - filepath = i["file"][o:], + filepath = filepath, line = int(line), col = int(i["column"]), ) @@ -91,7 +99,7 @@ def _ineffassign(ctx, version = "v0.0.0-20230107090616-13ace0543b28"): exe = go_install(ctx, "github.com/gordonklaus/ineffassign", version) res = ctx.os.exec( [exe, "./..."], - env = go_env(ctx, "ineffassign"), + env = go_env(), # ineffassign's README claims that it emits a retcode of 1 if it returns any # findings, but it actually emits a retcode of 3. # https://github.com/gordonklaus/ineffassign/blob/4cc7213b9bc8b868b2990c372f6fa057fa88b91c/ineffassign.go#L70 @@ -122,8 +130,7 @@ def _staticcheck(ctx, version = "v0.4.3"): will be rolled from time to time. """ exe = go_install(ctx, "honnef.co/go/tools/cmd/staticcheck", version) - env = go_env(ctx, "staticcheck") - env["STATICCHECK_CACHE"] = env["GOCACHE"] + env = go_env() res = ctx.os.exec( [exe, "-f=json", "./..."], ok_retcodes = [0, 1], @@ -172,7 +179,7 @@ def _shadow(ctx, version = "v0.7.0"): "-json", "./...", ], - env = go_env(ctx, "shadow"), + env = go_env(), ).wait() # Example output: @@ -221,7 +228,7 @@ def no_fork_without_lock(ctx): "-json", "./...", ], - env = go_env(ctx, "no_fork_without_lock"), + env = go_env(), ).wait().stdout) # Skip the "execsupport" package since it contains the wrappers around Run() diff --git a/internal/engine/run.go b/internal/engine/run.go index 705c07c..c92b1c3 100644 --- a/internal/engine/run.go +++ b/internal/engine/run.go @@ -311,18 +311,19 @@ func runInner(ctx context.Context, o *Options, tmpdir string) error { scm = &subdirSCM{s: scm, subdir: normalized} } return &shacState{ - allowNetwork: doc.AllowNetwork, - env: &env, - filter: o.Filter, - entryPoint: entryPoint, - r: o.Report, - root: root, - sandbox: sb, - scm: scm, - subdir: subdir, - tmpdir: filepath.Join(tmpdir, strconv.Itoa(idx)), - writableRoot: doc.WritableRoot, - vars: vars, + allowNetwork: doc.AllowNetwork, + env: &env, + filter: o.Filter, + entryPoint: entryPoint, + r: o.Report, + root: root, + sandbox: sb, + scm: scm, + subdir: subdir, + tmpdir: filepath.Join(tmpdir, strconv.Itoa(idx)), + writableRoot: doc.WritableRoot, + vars: vars, + passThroughEnv: doc.PassThroughEnv, } } var shacStates []*shacState @@ -542,7 +543,8 @@ type shacState struct { // mutated. They run checks and emit results (results and comments). checks []registeredCheck // filter controls which checks run. If nil, all checks will run. - filter CheckFilter + filter CheckFilter + passThroughEnv []*PassThroughEnv // Set when fail() is called. This happens only during the first phase, thus // no mutex is needed. diff --git a/internal/engine/run_test.go b/internal/engine/run_test.go index 15146ee..37e0a7f 100644 --- a/internal/engine/run_test.go +++ b/internal/engine/run_test.go @@ -16,6 +16,7 @@ package engine import ( "context" + "crypto/md5" "errors" "fmt" "io" @@ -551,6 +552,89 @@ func TestRun_Vars(t *testing.T) { } } +func TestRun_PassThroughEnv(t *testing.T) { + hash := fmt.Sprintf("%x", md5.Sum([]byte(t.Name()))) + + varPrefix := "TEST_" + hash + "_" + nonFileVarname := varPrefix + "VAR" + readOnlyDirVarname := varPrefix + "RO_DIR" + writeableDirVarname := varPrefix + "WRITEABLE_DIR" + env := map[string]string{ + nonFileVarname: "this is not a file", + readOnlyDirVarname: filepath.Join(t.TempDir(), "readonly"), + writeableDirVarname: filepath.Join(t.TempDir(), "writeable"), + } + mkdirAll(t, env[readOnlyDirVarname]) + mkdirAll(t, env[writeableDirVarname]) + + for k, v := range env { + t.Setenv(k, v) + } + + config := &Document{ + PassThroughEnv: []*PassThroughEnv{ + { + Name: nonFileVarname, + }, + { + Name: readOnlyDirVarname, + IsFile: true, + }, + { + Name: writeableDirVarname, + IsFile: true, + Writeable: true, + }, + { + // Additionally give access to HOME, which contains the + // Go cache, so checks that run `go run` can use cached + // artifacts. + Name: "HOME", + IsFile: true, + Writeable: true, + }, + }, + Vars: []*Var{{Name: "VAR_PREFIX"}}, + } + root := t.TempDir() + writeFile(t, root, "shac.textproto", prototext.Format(config)) + + main := "ctx-os-exec-pass_through_env.star" + copyFile(t, root, filepath.Join("testdata", main)) + copyFile(t, root, filepath.Join("testdata", "ctx-os-exec-pass_through_env.go")) + + r := reportPrint{reportNoPrint: reportNoPrint{t: t}} + o := Options{ + Report: &r, + Dir: root, + EntryPoint: main, + Vars: map[string]string{"VAR_PREFIX": varPrefix}, + } + + const filesystemSandboxed = runtime.GOOS == "linux" && (runtime.GOARCH == "amd64" || runtime.GOARCH == "arm64") + + wantLines := []string{ + "[//ctx-os-exec-pass_through_env.star:25] non-file env var: this is not a file", + "read-only dir env var: " + env[readOnlyDirVarname], + "writeable dir env var: " + env[writeableDirVarname], + "able to write to writeable dir", + } + if filesystemSandboxed { + wantLines = append(wantLines, fmt.Sprintf( + "error writing to read-only dir: open %s: read-only file system", + filepath.Join(env[readOnlyDirVarname], "foo.txt"))) + } else { + wantLines = append(wantLines, "able to write to read-only dir") + } + + if err := Run(context.Background(), &o); err != nil { + t.Fatal(err) + } + if diff := cmp.Diff(strings.Join(wantLines, "\n")+"\n", r.b.String()); diff != "" { + t.Fatalf("mismatch (-want +got):\n%s", diff) + } +} + func TestRun_SCM_Raw(t *testing.T) { t.Parallel() root := t.TempDir() @@ -1686,6 +1770,17 @@ func TestTestDataFailOrThrow(t *testing.T) { i := i t.Run(data[i].name, func(t *testing.T) { t.Parallel() + writeFile(t, root, "shac.textproto", prototext.Format(&Document{ + PassThroughEnv: []*PassThroughEnv{ + { + // Give access to HOME, which contains the Go cache, so + // checks that run `go run` can use cached artifacts. + Name: "HOME", + IsFile: true, + Writeable: true, + }, + }, + })) o := Options{Report: &reportNoPrint{t: t}, Dir: root, EntryPoint: data[i].name} err := Run(context.Background(), &o) if err == nil { @@ -2096,6 +2191,17 @@ func TestTestDataPrint(t *testing.T) { i := i t.Run(data[i].name, func(t *testing.T) { t.Parallel() + writeFile(t, p, "shac.textproto", prototext.Format(&Document{ + PassThroughEnv: []*PassThroughEnv{ + { + // Give access to HOME, which contains the Go cache, so + // checks that run `go run` can use cached artifacts. + Name: "HOME", + IsFile: true, + Writeable: true, + }, + }, + })) testStarlarkPrint(t, p, data[i].name, false, data[i].ignoreOrder, data[i].want) }) } diff --git a/internal/engine/runtime_ctx_os.go b/internal/engine/runtime_ctx_os.go index 8189bb6..be399a4 100644 --- a/internal/engine/runtime_ctx_os.go +++ b/internal/engine/runtime_ctx_os.go @@ -237,6 +237,22 @@ func ctxOsExec(ctx context.Context, s *shacState, name string, args starlark.Tup env["PATH"], }, string(os.PathListSeparator)) } + + var passThroughMounts []sandbox.Mount + for _, pte := range s.passThroughEnv { + val, ok := os.LookupEnv(pte.Name) + if !ok { + continue + } + env[pte.Name] = val + if pte.IsFile { + passThroughMounts = append(passThroughMounts, sandbox.Mount{ + Path: val, + Writable: pte.Writeable, + }) + } + } + for _, item := range argenv.Items() { k, ok := item[0].(starlark.String) if !ok { @@ -342,6 +358,7 @@ func ctxOsExec(ctx context.Context, s *shacState, name string, args starlark.Tup // this executable. {Path: filepath.Dir(tempDir), Writable: true}, } + config.Mounts = append(config.Mounts, passThroughMounts...) // TODO(olivernewman): This is necessary because checks for shac itself // assume Go is pre-installed. Switch to a hermetic Go installation that diff --git a/internal/engine/shac.pb.go b/internal/engine/shac.pb.go index 918e914..ac6684c 100644 --- a/internal/engine/shac.pb.go +++ b/internal/engine/shac.pb.go @@ -60,6 +60,8 @@ type Document struct { // are implemented. WritableRoot bool `protobuf:"varint,7,opt,name=writable_root,json=writableRoot,proto3" json:"writable_root,omitempty"` Vars []*Var `protobuf:"bytes,8,rep,name=vars,proto3" json:"vars,omitempty"` + // Environment variables to pass through the sandbox. + PassThroughEnv []*PassThroughEnv `protobuf:"bytes,9,rep,name=pass_through_env,json=passThroughEnv,proto3" json:"pass_through_env,omitempty"` } func (x *Document) Reset() { @@ -150,6 +152,13 @@ func (x *Document) GetVars() []*Var { return nil } +func (x *Document) GetPassThroughEnv() []*PassThroughEnv { + if x != nil { + return x.PassThroughEnv + } + return nil +} + // Var specifies a variable that may be passed into checks at runtime by the // --var flag and accessed via `ctx.vars.get(name)`. // @@ -224,6 +233,75 @@ func (x *Var) GetDefault() string { return "" } +// PassThroughEnv specifies an environment variable that should be passed +// through into the sandbox. +type PassThroughEnv struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The name of the environment variable, e.g. "FOO". + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // Whether the environment variable's value is a file path that sandboxed + // processes should be granted access to. + IsFile bool `protobuf:"varint,2,opt,name=is_file,json=isFile,proto3" json:"is_file,omitempty"` + // If is_file is true, whether to mount the file/directory as writeable. + Writeable bool `protobuf:"varint,3,opt,name=writeable,proto3" json:"writeable,omitempty"` +} + +func (x *PassThroughEnv) Reset() { + *x = PassThroughEnv{} + if protoimpl.UnsafeEnabled { + mi := &file_shac_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PassThroughEnv) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PassThroughEnv) ProtoMessage() {} + +func (x *PassThroughEnv) ProtoReflect() protoreflect.Message { + mi := &file_shac_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PassThroughEnv.ProtoReflect.Descriptor instead. +func (*PassThroughEnv) Descriptor() ([]byte, []int) { + return file_shac_proto_rawDescGZIP(), []int{2} +} + +func (x *PassThroughEnv) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *PassThroughEnv) GetIsFile() bool { + if x != nil { + return x.IsFile + } + return false +} + +func (x *PassThroughEnv) GetWriteable() bool { + if x != nil { + return x.Writeable + } + return false +} + // Requirements lists all the external dependencies, both direct and transitive // (indirect). type Requirements struct { @@ -240,7 +318,7 @@ type Requirements struct { func (x *Requirements) Reset() { *x = Requirements{} if protoimpl.UnsafeEnabled { - mi := &file_shac_proto_msgTypes[2] + mi := &file_shac_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -253,7 +331,7 @@ func (x *Requirements) String() string { func (*Requirements) ProtoMessage() {} func (x *Requirements) ProtoReflect() protoreflect.Message { - mi := &file_shac_proto_msgTypes[2] + mi := &file_shac_proto_msgTypes[3] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -266,7 +344,7 @@ func (x *Requirements) ProtoReflect() protoreflect.Message { // Deprecated: Use Requirements.ProtoReflect.Descriptor instead. func (*Requirements) Descriptor() ([]byte, []int) { - return file_shac_proto_rawDescGZIP(), []int{2} + return file_shac_proto_rawDescGZIP(), []int{3} } func (x *Requirements) GetDirect() []*Dependency { @@ -303,7 +381,7 @@ type Dependency struct { func (x *Dependency) Reset() { *x = Dependency{} if protoimpl.UnsafeEnabled { - mi := &file_shac_proto_msgTypes[3] + mi := &file_shac_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -316,7 +394,7 @@ func (x *Dependency) String() string { func (*Dependency) ProtoMessage() {} func (x *Dependency) ProtoReflect() protoreflect.Message { - mi := &file_shac_proto_msgTypes[3] + mi := &file_shac_proto_msgTypes[4] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -329,7 +407,7 @@ func (x *Dependency) ProtoReflect() protoreflect.Message { // Deprecated: Use Dependency.ProtoReflect.Descriptor instead. func (*Dependency) Descriptor() ([]byte, []int) { - return file_shac_proto_rawDescGZIP(), []int{3} + return file_shac_proto_rawDescGZIP(), []int{4} } func (x *Dependency) GetUrl() string { @@ -365,7 +443,7 @@ type Sum struct { func (x *Sum) Reset() { *x = Sum{} if protoimpl.UnsafeEnabled { - mi := &file_shac_proto_msgTypes[4] + mi := &file_shac_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -378,7 +456,7 @@ func (x *Sum) String() string { func (*Sum) ProtoMessage() {} func (x *Sum) ProtoReflect() protoreflect.Message { - mi := &file_shac_proto_msgTypes[4] + mi := &file_shac_proto_msgTypes[5] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -391,7 +469,7 @@ func (x *Sum) ProtoReflect() protoreflect.Message { // Deprecated: Use Sum.ProtoReflect.Descriptor instead. func (*Sum) Descriptor() ([]byte, []int) { - return file_shac_proto_rawDescGZIP(), []int{4} + return file_shac_proto_rawDescGZIP(), []int{5} } func (x *Sum) GetKnown() []*Known { @@ -414,7 +492,7 @@ type Known struct { func (x *Known) Reset() { *x = Known{} if protoimpl.UnsafeEnabled { - mi := &file_shac_proto_msgTypes[5] + mi := &file_shac_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -427,7 +505,7 @@ func (x *Known) String() string { func (*Known) ProtoMessage() {} func (x *Known) ProtoReflect() protoreflect.Message { - mi := &file_shac_proto_msgTypes[5] + mi := &file_shac_proto_msgTypes[6] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -440,7 +518,7 @@ func (x *Known) ProtoReflect() protoreflect.Message { // Deprecated: Use Known.ProtoReflect.Descriptor instead. func (*Known) Descriptor() ([]byte, []int) { - return file_shac_proto_rawDescGZIP(), []int{5} + return file_shac_proto_rawDescGZIP(), []int{6} } func (x *Known) GetUrl() string { @@ -473,7 +551,7 @@ type VersionDigest struct { func (x *VersionDigest) Reset() { *x = VersionDigest{} if protoimpl.UnsafeEnabled { - mi := &file_shac_proto_msgTypes[6] + mi := &file_shac_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -486,7 +564,7 @@ func (x *VersionDigest) String() string { func (*VersionDigest) ProtoMessage() {} func (x *VersionDigest) ProtoReflect() protoreflect.Message { - mi := &file_shac_proto_msgTypes[6] + mi := &file_shac_proto_msgTypes[7] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -499,7 +577,7 @@ func (x *VersionDigest) ProtoReflect() protoreflect.Message { // Deprecated: Use VersionDigest.ProtoReflect.Descriptor instead. func (*VersionDigest) Descriptor() ([]byte, []int) { - return file_shac_proto_rawDescGZIP(), []int{6} + return file_shac_proto_rawDescGZIP(), []int{7} } func (x *VersionDigest) GetVersion() string { @@ -520,7 +598,7 @@ var File_shac_proto protoreflect.FileDescriptor var file_shac_proto_rawDesc = []byte{ 0x0a, 0x0a, 0x73, 0x68, 0x61, 0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x06, 0x65, 0x6e, - 0x67, 0x69, 0x6e, 0x65, 0x22, 0xb1, 0x02, 0x0a, 0x08, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, + 0x67, 0x69, 0x6e, 0x65, 0x22, 0xf3, 0x02, 0x0a, 0x08, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x6d, 0x69, 0x6e, 0x5f, 0x73, 0x68, 0x61, 0x63, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6d, 0x69, 0x6e, 0x53, 0x68, 0x61, 0x63, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x61, @@ -539,39 +617,49 @@ var file_shac_proto_rawDesc = []byte{ 0x6f, 0x6f, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x77, 0x72, 0x69, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x6f, 0x6f, 0x74, 0x12, 0x1f, 0x0a, 0x04, 0x76, 0x61, 0x72, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x65, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x2e, 0x56, - 0x61, 0x72, 0x52, 0x04, 0x76, 0x61, 0x72, 0x73, 0x22, 0x55, 0x0a, 0x03, 0x56, 0x61, 0x72, 0x12, - 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, - 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, - 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, - 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x22, - 0x6a, 0x0a, 0x0c, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, - 0x2a, 0x0a, 0x06, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x12, 0x2e, 0x65, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x2e, 0x44, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x65, - 0x6e, 0x63, 0x79, 0x52, 0x06, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x12, 0x2e, 0x0a, 0x08, 0x69, - 0x6e, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, - 0x65, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x2e, 0x44, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x65, 0x6e, 0x63, - 0x79, 0x52, 0x08, 0x69, 0x6e, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x22, 0x4e, 0x0a, 0x0a, 0x44, - 0x65, 0x70, 0x65, 0x6e, 0x64, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x14, 0x0a, 0x05, 0x61, - 0x6c, 0x69, 0x61, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, 0x6c, 0x69, 0x61, - 0x73, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x2a, 0x0a, 0x03, 0x53, - 0x75, 0x6d, 0x12, 0x23, 0x0a, 0x05, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x18, 0x01, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x0d, 0x2e, 0x65, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x2e, 0x4b, 0x6e, 0x6f, 0x77, 0x6e, - 0x52, 0x05, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x22, 0x44, 0x0a, 0x05, 0x4b, 0x6e, 0x6f, 0x77, 0x6e, - 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, - 0x72, 0x6c, 0x12, 0x29, 0x0a, 0x04, 0x73, 0x65, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x15, 0x2e, 0x65, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x2e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, - 0x6e, 0x44, 0x69, 0x67, 0x65, 0x73, 0x74, 0x52, 0x04, 0x73, 0x65, 0x65, 0x6e, 0x22, 0x41, 0x0a, - 0x0d, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x44, 0x69, 0x67, 0x65, 0x73, 0x74, 0x12, 0x18, - 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x69, 0x67, 0x65, - 0x73, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x69, 0x67, 0x65, 0x73, 0x74, - 0x42, 0x32, 0x5a, 0x30, 0x67, 0x6f, 0x2e, 0x66, 0x75, 0x63, 0x68, 0x73, 0x69, 0x61, 0x2e, 0x64, - 0x65, 0x76, 0x2f, 0x73, 0x68, 0x61, 0x63, 0x2d, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x2f, - 0x73, 0x68, 0x61, 0x63, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x65, 0x6e, - 0x67, 0x69, 0x6e, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x61, 0x72, 0x52, 0x04, 0x76, 0x61, 0x72, 0x73, 0x12, 0x40, 0x0a, 0x10, 0x70, 0x61, 0x73, 0x73, + 0x5f, 0x74, 0x68, 0x72, 0x6f, 0x75, 0x67, 0x68, 0x5f, 0x65, 0x6e, 0x76, 0x18, 0x09, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x65, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x2e, 0x50, 0x61, 0x73, 0x73, + 0x54, 0x68, 0x72, 0x6f, 0x75, 0x67, 0x68, 0x45, 0x6e, 0x76, 0x52, 0x0e, 0x70, 0x61, 0x73, 0x73, + 0x54, 0x68, 0x72, 0x6f, 0x75, 0x67, 0x68, 0x45, 0x6e, 0x76, 0x22, 0x55, 0x0a, 0x03, 0x56, 0x61, + 0x72, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, + 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, + 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x64, 0x65, 0x66, 0x61, 0x75, + 0x6c, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, + 0x74, 0x22, 0x5b, 0x0a, 0x0e, 0x50, 0x61, 0x73, 0x73, 0x54, 0x68, 0x72, 0x6f, 0x75, 0x67, 0x68, + 0x45, 0x6e, 0x76, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x69, 0x73, 0x5f, 0x66, 0x69, + 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x69, 0x73, 0x46, 0x69, 0x6c, 0x65, + 0x12, 0x1c, 0x0a, 0x09, 0x77, 0x72, 0x69, 0x74, 0x65, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x09, 0x77, 0x72, 0x69, 0x74, 0x65, 0x61, 0x62, 0x6c, 0x65, 0x22, 0x6a, + 0x0a, 0x0c, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x2a, + 0x0a, 0x06, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, + 0x2e, 0x65, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x2e, 0x44, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x65, 0x6e, + 0x63, 0x79, 0x52, 0x06, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x12, 0x2e, 0x0a, 0x08, 0x69, 0x6e, + 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x65, + 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x2e, 0x44, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x65, 0x6e, 0x63, 0x79, + 0x52, 0x08, 0x69, 0x6e, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x22, 0x4e, 0x0a, 0x0a, 0x44, 0x65, + 0x70, 0x65, 0x6e, 0x64, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x6c, + 0x69, 0x61, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, 0x6c, 0x69, 0x61, 0x73, + 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x2a, 0x0a, 0x03, 0x53, 0x75, + 0x6d, 0x12, 0x23, 0x0a, 0x05, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x0d, 0x2e, 0x65, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x2e, 0x4b, 0x6e, 0x6f, 0x77, 0x6e, 0x52, + 0x05, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x22, 0x44, 0x0a, 0x05, 0x4b, 0x6e, 0x6f, 0x77, 0x6e, 0x12, + 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, + 0x6c, 0x12, 0x29, 0x0a, 0x04, 0x73, 0x65, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x15, 0x2e, 0x65, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x2e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, + 0x44, 0x69, 0x67, 0x65, 0x73, 0x74, 0x52, 0x04, 0x73, 0x65, 0x65, 0x6e, 0x22, 0x41, 0x0a, 0x0d, + 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x44, 0x69, 0x67, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, + 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, + 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x69, 0x67, 0x65, 0x73, + 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x69, 0x67, 0x65, 0x73, 0x74, 0x42, + 0x32, 0x5a, 0x30, 0x67, 0x6f, 0x2e, 0x66, 0x75, 0x63, 0x68, 0x73, 0x69, 0x61, 0x2e, 0x64, 0x65, + 0x76, 0x2f, 0x73, 0x68, 0x61, 0x63, 0x2d, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x2f, 0x73, + 0x68, 0x61, 0x63, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x65, 0x6e, 0x67, + 0x69, 0x6e, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -586,29 +674,31 @@ func file_shac_proto_rawDescGZIP() []byte { return file_shac_proto_rawDescData } -var file_shac_proto_msgTypes = make([]protoimpl.MessageInfo, 7) +var file_shac_proto_msgTypes = make([]protoimpl.MessageInfo, 8) var file_shac_proto_goTypes = []interface{}{ - (*Document)(nil), // 0: engine.Document - (*Var)(nil), // 1: engine.Var - (*Requirements)(nil), // 2: engine.Requirements - (*Dependency)(nil), // 3: engine.Dependency - (*Sum)(nil), // 4: engine.Sum - (*Known)(nil), // 5: engine.Known - (*VersionDigest)(nil), // 6: engine.VersionDigest + (*Document)(nil), // 0: engine.Document + (*Var)(nil), // 1: engine.Var + (*PassThroughEnv)(nil), // 2: engine.PassThroughEnv + (*Requirements)(nil), // 3: engine.Requirements + (*Dependency)(nil), // 4: engine.Dependency + (*Sum)(nil), // 5: engine.Sum + (*Known)(nil), // 6: engine.Known + (*VersionDigest)(nil), // 7: engine.VersionDigest } var file_shac_proto_depIdxs = []int32{ - 2, // 0: engine.Document.requirements:type_name -> engine.Requirements - 4, // 1: engine.Document.sum:type_name -> engine.Sum + 3, // 0: engine.Document.requirements:type_name -> engine.Requirements + 5, // 1: engine.Document.sum:type_name -> engine.Sum 1, // 2: engine.Document.vars:type_name -> engine.Var - 3, // 3: engine.Requirements.direct:type_name -> engine.Dependency - 3, // 4: engine.Requirements.indirect:type_name -> engine.Dependency - 5, // 5: engine.Sum.known:type_name -> engine.Known - 6, // 6: engine.Known.seen:type_name -> engine.VersionDigest - 7, // [7:7] is the sub-list for method output_type - 7, // [7:7] is the sub-list for method input_type - 7, // [7:7] is the sub-list for extension type_name - 7, // [7:7] is the sub-list for extension extendee - 0, // [0:7] is the sub-list for field type_name + 2, // 3: engine.Document.pass_through_env:type_name -> engine.PassThroughEnv + 4, // 4: engine.Requirements.direct:type_name -> engine.Dependency + 4, // 5: engine.Requirements.indirect:type_name -> engine.Dependency + 6, // 6: engine.Sum.known:type_name -> engine.Known + 7, // 7: engine.Known.seen:type_name -> engine.VersionDigest + 8, // [8:8] is the sub-list for method output_type + 8, // [8:8] is the sub-list for method input_type + 8, // [8:8] is the sub-list for extension type_name + 8, // [8:8] is the sub-list for extension extendee + 0, // [0:8] is the sub-list for field type_name } func init() { file_shac_proto_init() } @@ -642,7 +732,7 @@ func file_shac_proto_init() { } } file_shac_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Requirements); i { + switch v := v.(*PassThroughEnv); i { case 0: return &v.state case 1: @@ -654,7 +744,7 @@ func file_shac_proto_init() { } } file_shac_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Dependency); i { + switch v := v.(*Requirements); i { case 0: return &v.state case 1: @@ -666,7 +756,7 @@ func file_shac_proto_init() { } } file_shac_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Sum); i { + switch v := v.(*Dependency); i { case 0: return &v.state case 1: @@ -678,7 +768,7 @@ func file_shac_proto_init() { } } file_shac_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Known); i { + switch v := v.(*Sum); i { case 0: return &v.state case 1: @@ -690,6 +780,18 @@ func file_shac_proto_init() { } } file_shac_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Known); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_shac_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*VersionDigest); i { case 0: return &v.state @@ -708,7 +810,7 @@ func file_shac_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_shac_proto_rawDesc, NumEnums: 0, - NumMessages: 7, + NumMessages: 8, NumExtensions: 0, NumServices: 0, }, diff --git a/internal/engine/shac.proto b/internal/engine/shac.proto index cfc6db6..56699f1 100644 --- a/internal/engine/shac.proto +++ b/internal/engine/shac.proto @@ -40,6 +40,8 @@ message Document { // are implemented. bool writable_root = 7; repeated Var vars = 8; + // Environment variables to pass through the sandbox. + repeated PassThroughEnv pass_through_env = 9; } // Var specifies a variable that may be passed into checks at runtime by the @@ -59,6 +61,18 @@ message Var { string default = 3; } +// PassThroughEnv specifies an environment variable that should be passed +// through into the sandbox. +message PassThroughEnv { + // The name of the environment variable, e.g. "FOO". + string name = 1; + // Whether the environment variable's value is a file path that sandboxed + // processes should be granted access to. + bool is_file = 2; + // If is_file is true, whether to mount the file/directory as writeable. + bool writeable = 3; +} + // Requirements lists all the external dependencies, both direct and transitive // (indirect). message Requirements { diff --git a/internal/engine/testdata/ctx-os-exec-pass_through_env.go b/internal/engine/testdata/ctx-os-exec-pass_through_env.go new file mode 100644 index 0000000..5d12508 --- /dev/null +++ b/internal/engine/testdata/ctx-os-exec-pass_through_env.go @@ -0,0 +1,63 @@ +// Copyright 2023 The Shac Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build ignore + +package main + +import ( + "fmt" + "log" + "os" + "path/filepath" +) + +func main() { + varPrefix := os.Args[1] + + nonFileVarname := varPrefix + "VAR" + readOnlyDirVarname := varPrefix + "RO_DIR" + writeableDirVarname := varPrefix + "WRITEABLE_DIR" + + fmt.Printf("non-file env var: %s\n", mustGetEnv(nonFileVarname)) + fmt.Printf("read-only dir env var: %s\n", mustGetEnv(readOnlyDirVarname)) + fmt.Printf("writeable dir env var: %s\n", mustGetEnv(writeableDirVarname)) + + // Make sure the writeable dir is writeable. + if _, err := os.ReadDir(mustGetEnv(writeableDirVarname)); err != nil { + log.Panicf("failed to read writeable dir: %s", err) + } + if err := os.WriteFile(filepath.Join(mustGetEnv(writeableDirVarname), "foo.txt"), []byte("hi"), 0o600); err == nil { + fmt.Println("able to write to writeable dir") + } else { + log.Panicf("failed to write to writeable dir: %s", err) + } + + if _, err := os.ReadDir(mustGetEnv(readOnlyDirVarname)); err != nil { + log.Panicf("failed to read read-only dir: %s", err) + } + if err := os.WriteFile(filepath.Join(mustGetEnv(readOnlyDirVarname), "foo.txt"), []byte("hi"), 0o600); err == nil { + fmt.Println("able to write to read-only dir") + } else { + fmt.Printf("error writing to read-only dir: %s\n", err) + } +} + +func mustGetEnv(name string) string { + val, ok := os.LookupEnv(name) + if !ok { + log.Panicf("env var %s is not set", name) + } + return val +} diff --git a/internal/engine/testdata/ctx-os-exec-pass_through_env.star b/internal/engine/testdata/ctx-os-exec-pass_through_env.star new file mode 100644 index 0000000..e1e64a8 --- /dev/null +++ b/internal/engine/testdata/ctx-os-exec-pass_through_env.star @@ -0,0 +1,36 @@ +# Copyright 2023 The Shac Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +def cb(ctx): + res = ctx.os.exec( + [ + "go", + "run", + "ctx-os-exec-pass_through_env.go", + ctx.vars.get("VAR_PREFIX"), + ], + env = _go_env(ctx), + ).wait() + print(res.stdout.rstrip()) + +def _go_env(ctx): + return { + "CGO_ENABLED": "0", + "GOPACKAGESDRIVER": "off", + # Explicitly set GOROOT to prevent warnings about GOROOT and GOPATH being + # equal when they're both empty. + "GOROOT": ctx.os.exec(["go", "env", "GOROOT"]).wait().stdout.strip(), + } + +shac.register_check(cb) diff --git a/internal/engine/testdata/fail_or_throw/ctx-os-exec-10Mib-exceed.star b/internal/engine/testdata/fail_or_throw/ctx-os-exec-10Mib-exceed.star index b2174f8..5331290 100644 --- a/internal/engine/testdata/fail_or_throw/ctx-os-exec-10Mib-exceed.star +++ b/internal/engine/testdata/fail_or_throw/ctx-os-exec-10Mib-exceed.star @@ -19,7 +19,6 @@ def cb(ctx): def _go_env(ctx): return { "CGO_ENABLED": "0", - "GOCACHE": ctx.io.tempdir() + "/gocache", "GOPACKAGESDRIVER": "off", # Explicitly set GOROOT to prevent warnings about GOROOT and GOPATH being # equal when they're both empty. diff --git a/internal/engine/testdata/print/ctx-os-exec-10Mib.star b/internal/engine/testdata/print/ctx-os-exec-10Mib.star index 2023931..eba656f 100644 --- a/internal/engine/testdata/print/ctx-os-exec-10Mib.star +++ b/internal/engine/testdata/print/ctx-os-exec-10Mib.star @@ -19,7 +19,6 @@ def cb(ctx): def _go_env(ctx): return { "CGO_ENABLED": "0", - "GOCACHE": ctx.io.tempdir() + "/gocache", "GOPACKAGESDRIVER": "off", # Explicitly set GOROOT to prevent warnings about GOROOT and GOPATH being # equal when they're both empty. diff --git a/internal/engine/testdata/print/ctx-os-exec-stdin.star b/internal/engine/testdata/print/ctx-os-exec-stdin.star index 6aa27cd..1939da0 100644 --- a/internal/engine/testdata/print/ctx-os-exec-stdin.star +++ b/internal/engine/testdata/print/ctx-os-exec-stdin.star @@ -32,7 +32,6 @@ def cb(ctx): def _go_env(ctx): return { "CGO_ENABLED": "0", - "GOCACHE": ctx.io.tempdir() + "/gocache", "GOPACKAGESDRIVER": "off", } diff --git a/internal/engine/version.go b/internal/engine/version.go index 8daf77c..ef48033 100644 --- a/internal/engine/version.go +++ b/internal/engine/version.go @@ -26,7 +26,7 @@ var ( // Version is the current tool version. // // TODO(maruel): Add proper version, preferably from git tag. - Version = shacVersion{0, 1, 9} + Version = shacVersion{0, 1, 10} ) func (v shacVersion) String() string { diff --git a/scripts/tests.sh b/scripts/tests.sh index d7276f2..d499fab 100755 --- a/scripts/tests.sh +++ b/scripts/tests.sh @@ -39,12 +39,14 @@ if ! command -v "go" > /dev/null; then fi echo "- Testing with coverage" -go test -cover ./... - -echo "" -echo "- Benchmarks" -go test -bench=. -run=^$ -cpu 1 ./... +go test -count=1 -cover ./... echo "" echo "- Running 'shac check'" go run . check -v + +# Benchmarks are the slowest step, so run them last in case the user only cares +# about previous steps and wants to ctrl-C. +echo "" +echo "- Benchmarks" +go test -count=1 -bench=. -run=^$ -cpu 1 ./... diff --git a/shac.textproto b/shac.textproto index 31054f1..4d247cb 100644 --- a/shac.textproto +++ b/shac.textproto @@ -15,7 +15,7 @@ # TODO(olivernewman): Build github.com/protocolbuffers/txtpbfmt into shac and # enforce formatting of shac.textproto files in all repos that use shac. -min_shac_version: "0.1.9" +min_shac_version: "0.1.10" allow_network: False ignore: "/vendor/" # Vendored code for test data only. @@ -32,3 +32,21 @@ vars: [ default: "foo" } ] +pass_through_env: [ + { + # Provide Go commands access to the Go cache to speed up compilation. + name: "GOCACHE", + is_file: true + writeable: true + }, + { + # The Go cache directory is computed based on $HOME in the absence of + # $GOCACHE. + # TODO(olivernewman): Implement support for constructing pass-throughs + # using Starlark, and pass through the value returned by `go env + # GOCACHE` instead of all of $HOME. + name: "HOME", + is_file: true + writeable: true + } +]