cmd/gitbay-runner/workdir_test.go
94 lines · 2977 bytes
5 symbols in this file
1package main
2
3import (
4 "fmt"
5 "os"
6 "path/filepath"
7 "strings"
8 "testing"
9)
10
11// The default must not be a fixed name inside a world-writable directory:
12// another local user could create it first, and MkdirAll would accept
13// theirs. This is the process that clones repositories and exports build
14// secrets into step environments (go:S5445, #153).
15func TestDefaultWorkdirIsNotInSharedTmp(t *testing.T) {
16 got := defaultWorkdir()
17 cache, err := os.UserCacheDir()
18 if err != nil || cache == "" {
19 t.Skip("no user cache directory on this machine; the tmp fallback is the tested path")
20 }
21 if !strings.HasPrefix(got, cache) {
22 t.Errorf("default workdir %q is not under the user cache %q", got, cache)
23 }
24 if strings.HasPrefix(got, os.TempDir()+string(filepath.Separator)) {
25 t.Errorf("default workdir %q is still inside the shared temp directory", got)
26 }
27}
28
29func TestCheckWorkdirAcceptsAPrivateDirectory(t *testing.T) {
30 dir := filepath.Join(t.TempDir(), "work")
31 if err := os.MkdirAll(dir, 0o700); err != nil {
32 t.Fatal(err)
33 }
34 if err := checkWorkdir(dir); err != nil {
35 t.Fatalf("a private directory this user owns was refused: %v", err)
36 }
37}
38
39// A workspace we own that is merely too permissive is tightened, not
40// refused: every runner before this one made its workspace 0755, and
41// refusing would take the runner down on upgrade over a permission it is
42// entitled to change.
43func TestCheckWorkdirTightensOurOwnDirectory(t *testing.T) {
44 base := t.TempDir()
45 for _, mode := range []os.FileMode{0o755, 0o770, 0o777} {
46 dir := filepath.Join(base, fmt.Sprintf("mode-%o", mode))
47 if err := os.MkdirAll(dir, mode); err != nil {
48 t.Fatal(err)
49 }
50 if err := os.Chmod(dir, mode); err != nil { // MkdirAll applies umask
51 t.Fatal(err)
52 }
53 if err := checkWorkdir(dir); err != nil {
54 t.Fatalf("mode %04o: refused a directory we own: %v", mode, err)
55 }
56 fi, err := os.Stat(dir)
57 if err != nil {
58 t.Fatal(err)
59 }
60 if got := fi.Mode().Perm(); got != 0o700 {
61 t.Errorf("mode %04o was left at %04o, want 0700", mode, got)
62 }
63 }
64}
65
66// What cannot be repaired is refused: a symlink is not ours to correct,
67// and it is what an attacker leaves behind.
68func TestCheckWorkdirRefusesUnsafeDirectories(t *testing.T) {
69 base := t.TempDir()
70
71 target := filepath.Join(base, "elsewhere")
72 if err := os.MkdirAll(target, 0o700); err != nil {
73 t.Fatal(err)
74 }
75 link := filepath.Join(base, "link")
76 if err := os.Symlink(target, link); err != nil {
77 t.Skipf("symlinks unavailable: %v", err)
78 }
79 if err := checkWorkdir(link); err == nil {
80 t.Error("a symlinked workdir was accepted")
81 } else if !strings.Contains(err.Error(), "symlink") {
82 t.Errorf("symlink refusal does not say why: %v", err)
83 }
84}
85
86func TestCheckWorkdirRefusesAFile(t *testing.T) {
87 f := filepath.Join(t.TempDir(), "notadir")
88 if err := os.WriteFile(f, []byte("x"), 0o600); err != nil {
89 t.Fatal(err)
90 }
91 if err := checkWorkdir(f); err == nil {
92 t.Error("a regular file was accepted as a workdir")
93 }
94}