Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

vole
Clone

Read only, no credentials:

https://rodent.software/vole.git

Write over SSH with a registered key:

git clone ssh://git@rodent.software:2222/vole.git

A free and open-source forge that lives in your Git repo.

Code

395 commit(s) on main

update readme Keenan Nemetz · 37m ago 32290114
history blame raw 5.2 KiB
1package secure
2
3import (
4 "os"
5 "path/filepath"
6 "testing"
7)
8
9// mode reports the permission bits of path, which is what every test here is
10// about.
11func mode(t *testing.T, path string) os.FileMode {
12 t.Helper()
13 st, err := os.Stat(path)
14 if err != nil {
15 t.Fatal(err)
16 }
17 return st.Mode().Perm()
18}
19
20// TestHardenFileTightensAnInheritedMode is the upgrade case: a file created by an
21// older Vole is 0644, and starting the instance must fix it.
22func TestHardenFileTightensAnInheritedMode(t *testing.T) {
23 t.Parallel()
24 path := filepath.Join(t.TempDir(), "users.db")
25 if err := os.WriteFile(path, []byte("keys"), 0o644); err != nil {
26 t.Fatal(err)
27 }
28 // A umask can hide the mode we asked for, so set it explicitly.
29 if err := os.Chmod(path, 0o644); err != nil {
30 t.Fatal(err)
31 }
32 changed, err := HardenFile(path)
33 if err != nil {
34 t.Fatal(err)
35 }
36 if !changed {
37 t.Fatal("a 0644 file was reported as already correct")
38 }
39 if got := mode(t, path); got != FileMode {
40 t.Fatalf("mode = %04o, want %04o", got, FileMode)
41 }
42 // The content is untouched.
43 data, err := os.ReadFile(path)
44 if err != nil {
45 t.Fatal(err)
46 }
47 if string(data) != "keys" {
48 t.Fatalf("content = %q", data)
49 }
50 // And a second pass is a no-op, so startup does not churn.
51 changed, err = HardenFile(path)
52 if err != nil {
53 t.Fatal(err)
54 }
55 if changed {
56 t.Fatal("an already tight file was reported as changed")
57 }
58}
59
60// TestHardenDirKeepsGroupAccess: 0750 with a dedicated group is a deliberate
61// deployment, so only world access is removed.
62func TestHardenDirKeepsGroupAccess(t *testing.T) {
63 t.Parallel()
64 dir := t.TempDir()
65
66 group := filepath.Join(dir, "group")
67 if err := os.Mkdir(group, 0o750); err != nil {
68 t.Fatal(err)
69 }
70 if err := os.Chmod(group, 0o750); err != nil {
71 t.Fatal(err)
72 }
73 changed, err := HardenDir(group)
74 if err != nil {
75 t.Fatal(err)
76 }
77 if changed {
78 t.Fatal("0750 was tightened; group access is allowed")
79 }
80
81 world := filepath.Join(dir, "world")
82 if err := os.Mkdir(world, 0o755); err != nil {
83 t.Fatal(err)
84 }
85 if err := os.Chmod(world, 0o755); err != nil {
86 t.Fatal(err)
87 }
88 changed, err = HardenDir(world)
89 if err != nil {
90 t.Fatal(err)
91 }
92 if !changed {
93 t.Fatal("0755 was left world-accessible")
94 }
95 if got := mode(t, world); got&0o007 != 0 {
96 t.Fatalf("mode = %04o, still world-accessible", got)
97 }
98}
99
100func TestMkdirCreatesAndTightens(t *testing.T) {
101 t.Parallel()
102 dir := filepath.Join(t.TempDir(), "data", "nested")
103 if err := Mkdir(dir); err != nil {
104 t.Fatal(err)
105 }
106 if got := mode(t, dir); got != DirMode {
107 t.Fatalf("mode = %04o, want %04o", got, DirMode)
108 }
109 if err := os.Chmod(dir, 0o777); err != nil {
110 t.Fatal(err)
111 }
112 if err := Mkdir(dir); err != nil {
113 t.Fatal(err)
114 }
115 if got := mode(t, dir); got&0o007 != 0 {
116 t.Fatalf("mode = %04o, still world-accessible", got)
117 }
118}
119
120// TestOpenAppendTightensTheLogItWrites is the audit-log case: an existing log
121// inherits its mode from whoever created it first.
122func TestOpenAppendTightensTheLogItWrites(t *testing.T) {
123 t.Parallel()
124 path := filepath.Join(t.TempDir(), "audit.log")
125 if err := os.WriteFile(path, []byte("first\n"), 0o644); err != nil {
126 t.Fatal(err)
127 }
128 if err := os.Chmod(path, 0o644); err != nil {
129 t.Fatal(err)
130 }
131 f, err := OpenAppend(path)
132 if err != nil {
133 t.Fatal(err)
134 }
135 if _, err := f.WriteString("second\n"); err != nil {
136 t.Fatal(err)
137 }
138 f.Close()
139
140 if got := mode(t, path); got != FileMode {
141 t.Fatalf("mode = %04o, want %04o", got, FileMode)
142 }
143 data, err := os.ReadFile(path)
144 if err != nil {
145 t.Fatal(err)
146 }
147 if string(data) != "first\nsecond\n" {
148 t.Fatalf("log = %q; appending did not keep the earlier lines", data)
149 }
150}
151
152func TestOpenAppendCreatesAtFileMode(t *testing.T) {
153 t.Parallel()
154 path := filepath.Join(t.TempDir(), "new.log")
155 f, err := OpenAppend(path)
156 if err != nil {
157 t.Fatal(err)
158 }
159 f.Close()
160 if got := mode(t, path); got != FileMode {
161 t.Fatalf("mode = %04o, want %04o", got, FileMode)
162 }
163}
164
165// TestHardenReportsOnlyWhatChanged is what the startup log lists: an operator
166// should read the paths that moved, not every secret the instance has.
167func TestHardenReportsOnlyWhatChanged(t *testing.T) {
168 t.Parallel()
169 dir := t.TempDir()
170 loose := filepath.Join(dir, "loose.db")
171 tight := filepath.Join(dir, "tight.db")
172 missing := filepath.Join(dir, "missing.db")
173 for _, p := range []string{loose, tight} {
174 if err := os.WriteFile(p, []byte("x"), 0o600); err != nil {
175 t.Fatal(err)
176 }
177 }
178 if err := os.Chmod(loose, 0o644); err != nil {
179 t.Fatal(err)
180 }
181
182 results, err := Harden(loose, tight, missing, dir)
183 if err != nil {
184 t.Fatal(err)
185 }
186 changed := map[string]bool{}
187 for _, r := range results {
188 changed[r.Path] = true
189 }
190 if !changed[loose] {
191 t.Error("the 0644 file was not reported")
192 }
193 if changed[tight] {
194 t.Error("an already-private file was reported")
195 }
196 if changed[missing] {
197 t.Error("a missing file was reported")
198 }
199}
200
201func TestHardenFileRefusesADirectory(t *testing.T) {
202 t.Parallel()
203 if _, err := HardenFile(t.TempDir()); err == nil {
204 t.Fatal("HardenFile accepted a directory")
205 }
206}
207
208func TestHardenDirRefusesAFile(t *testing.T) {
209 t.Parallel()
210 path := filepath.Join(t.TempDir(), "file")
211 if err := os.WriteFile(path, nil, 0o600); err != nil {
212 t.Fatal(err)
213 }
214 if _, err := HardenDir(path); err == nil {
215 t.Fatal("HardenDir accepted a file")
216 }
217}
218