-
Notifications
You must be signed in to change notification settings - Fork 30
Expand file tree
/
Copy pathsudo_test.go
More file actions
186 lines (164 loc) · 5.86 KB
/
Copy pathsudo_test.go
File metadata and controls
186 lines (164 loc) · 5.86 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
package main
import (
"os"
"path"
"strings"
"sync"
"testing"
"time"
)
const testSudoersContent = "runner ALL=(ALL) NOPASSWD:ALL\n"
// newTestSudo returns a Sudo wired to a fake sudoers file and non-existent
// socket paths so tests never touch the machine's real sudoers or sockets.
// All seams are per-instance fields: the background container-cleanup
// goroutine may outlive the test, so it must never read package-level state
// that a test cleanup restores.
func newTestSudo(t *testing.T, runCmd func(cmd string, args ...string)) (*Sudo, string) {
t.Helper()
dir := t.TempDir()
sudoersPath := path.Join(dir, "runner")
// 0640 rather than the real file's 0440: the agent truncates as root
// (which bypasses permission checks), but tests run unprivileged.
if err := os.WriteFile(sudoersPath, []byte(testSudoersContent), 0640); err != nil {
t.Fatalf("failed to create fake sudoers file: %v", err)
}
return &Sudo{
sudoersFilePathOverride: sudoersPath,
dockerSockPathOverride: path.Join(dir, "no-such-docker.sock"),
containerdSockOverride: path.Join(dir, "no-such-containerd.sock"),
runCmd: runCmd,
}, sudoersPath
}
func Test_disableSudoAndContainers_RevokesSudoBeforeContainerCleanup(t *testing.T) {
type observed struct {
command string
sudoersSize int64
}
firstCmd := make(chan observed, 1)
release := make(chan struct{})
var mu sync.Mutex
var commands []string
sudo, sudoersPath := newTestSudo(t, nil)
sudo.runCmd = func(cmd string, args ...string) {
full := strings.Join(append([]string{cmd}, args...), " ")
var size int64 = -1
if fi, err := os.Stat(sudoersPath); err == nil {
size = fi.Size()
}
mu.Lock()
commands = append(commands, full)
mu.Unlock()
select {
case firstCmd <- observed{command: full, sudoersSize: size}:
default:
}
<-release
}
t.Cleanup(func() { close(release) })
done := make(chan error, 1)
go func() {
done <- sudo.disableSudoAndContainers(t.TempDir())
}()
// Sudo revocation must not wait on container teardown, which is
// simulated here by a command runner that blocks forever.
select {
case err := <-done:
if err != nil {
t.Fatalf("disableSudoAndContainers returned error: %v", err)
}
case <-time.After(2 * time.Second):
t.Fatal("disableSudoAndContainers blocked on container teardown; sudo revocation must complete first")
}
fi, err := os.Stat(sudoersPath)
if err != nil {
t.Fatalf("failed to stat sudoers file: %v", err)
}
if fi.Size() != 0 {
t.Fatalf("sudoers file not truncated after disableSudoAndContainers returned; size = %d", fi.Size())
}
// The first container-cleanup command must observe an already-truncated
// sudoers file, and must be the package purge (daemon stop) rather than
// a directory delete.
select {
case obs := <-firstCmd:
if obs.sudoersSize != 0 {
t.Fatalf("first container-cleanup command %q ran before sudoers truncate; observed size = %d", obs.command, obs.sudoersSize)
}
if !strings.HasPrefix(obs.command, "apt-get purge") {
t.Fatalf("first container-cleanup command = %q, want package purge before directory deletion", obs.command)
}
case <-time.After(2 * time.Second):
t.Fatal("no container-cleanup command observed")
}
}
func Test_disableSudoAndContainers_ErrorSurfacesWhenSudoersMissing(t *testing.T) {
dir := t.TempDir()
sudo := &Sudo{
sudoersFilePathOverride: path.Join(dir, "does-not-exist"),
dockerSockPathOverride: path.Join(dir, "no-such-docker.sock"),
containerdSockOverride: path.Join(dir, "no-such-containerd.sock"),
runCmd: func(cmd string, args ...string) {},
}
err := sudo.disableSudoAndContainers(t.TempDir())
if err == nil {
t.Fatal("expected error when sudoers backup fails, got nil")
}
if !strings.Contains(err.Error(), "error disabling sudo and containers") {
t.Fatalf("error = %q, want it to contain %q", err.Error(), "error disabling sudo and containers")
}
}
// Socket permissions must be revoked synchronously (before return), via chmod.
func Test_disableSudoAndContainers_RemovesSocketPermissions(t *testing.T) {
sudo, _ := newTestSudo(t, func(cmd string, args ...string) {})
sockDir := t.TempDir()
sudo.dockerSockPathOverride = path.Join(sockDir, "docker.sock")
sudo.containerdSockOverride = path.Join(sockDir, "containerd.sock")
for _, p := range []string{sudo.dockerSockPathOverride, sudo.containerdSockOverride} {
if err := os.WriteFile(p, nil, 0660); err != nil {
t.Fatalf("failed to create fake socket %s: %v", p, err)
}
}
if err := sudo.disableSudoAndContainers(t.TempDir()); err != nil {
t.Fatalf("disableSudoAndContainers returned error: %v", err)
}
for _, p := range []string{sudo.dockerSockPathOverride, sudo.containerdSockOverride} {
fi, err := os.Stat(p)
if err != nil {
t.Fatalf("failed to stat %s: %v", p, err)
}
if fi.Mode().Perm() != 0 {
t.Fatalf("%s permissions not removed: %v", p, fi.Mode().Perm())
}
}
}
func Test_disableSudo_BackupAndRevert(t *testing.T) {
sudo, sudoersPath := newTestSudo(t, func(cmd string, args ...string) {})
tempDir := t.TempDir()
if err := sudo.disableSudo(tempDir); err != nil {
t.Fatalf("disableSudo returned error: %v", err)
}
backup, err := os.ReadFile(sudo.SudoersBackUpPath)
if err != nil {
t.Fatalf("failed to read backup: %v", err)
}
if string(backup) != testSudoersContent {
t.Fatalf("backup content = %q, want %q", string(backup), testSudoersContent)
}
fi, err := os.Stat(sudoersPath)
if err != nil {
t.Fatalf("failed to stat sudoers file: %v", err)
}
if fi.Size() != 0 {
t.Fatalf("sudoers file not truncated; size = %d", fi.Size())
}
if err := sudo.revertDisableSudo(); err != nil {
t.Fatalf("revertDisableSudo returned error: %v", err)
}
restored, err := os.ReadFile(sudoersPath)
if err != nil {
t.Fatalf("failed to read restored sudoers file: %v", err)
}
if string(restored) != testSudoersContent {
t.Fatalf("restored content = %q, want %q", string(restored), testSudoersContent)
}
}