@synopackageland/cli 0.14.1 → 0.15.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. package/CHANGELOG.md +20 -1
  2. package/README.md +33 -18
  3. package/dist/build-spk/generator.d.ts +3 -2
  4. package/dist/build-spk/generator.js +6 -846
  5. package/dist/build-spk/resource-worker.d.ts +2 -14
  6. package/dist/build-spk/resource-worker.js +3 -166
  7. package/dist/cli.js +208 -56
  8. package/dist/compose/analyze.d.ts +3 -61
  9. package/dist/compose/analyze.js +35 -776
  10. package/dist/compose/env.d.ts +5 -3
  11. package/dist/compose/env.js +17 -25
  12. package/dist/compose/runner.js +23 -6
  13. package/dist/contract/unsupported-types.js +1 -1
  14. package/dist/contract/validate.js +2 -2
  15. package/dist/contract/web-launch.d.ts +1 -12
  16. package/dist/contract/web-launch.js +10 -74
  17. package/dist/ctl/cli.d.ts +36 -0
  18. package/dist/ctl/cli.js +218 -0
  19. package/dist/ctl/descriptor.d.ts +81 -0
  20. package/dist/ctl/descriptor.js +93 -0
  21. package/dist/ctl/dsm-auth.d.ts +10 -0
  22. package/dist/ctl/dsm-auth.js +30 -0
  23. package/dist/ctl/errors.d.ts +17 -0
  24. package/dist/ctl/errors.js +32 -0
  25. package/dist/ctl/flows.d.ts +11 -0
  26. package/dist/ctl/flows.js +52 -0
  27. package/dist/ctl/input.d.ts +20 -0
  28. package/dist/ctl/input.js +128 -0
  29. package/dist/ctl/json.d.ts +2 -0
  30. package/dist/ctl/json.js +38 -0
  31. package/dist/ctl/redaction.d.ts +15 -0
  32. package/dist/ctl/redaction.js +198 -0
  33. package/dist/ctl/service.d.ts +38 -0
  34. package/dist/ctl/service.js +156 -0
  35. package/dist/ctl/store.d.ts +51 -0
  36. package/dist/ctl/store.js +93 -0
  37. package/dist/ctl/transfers.d.ts +14 -0
  38. package/dist/ctl/transfers.js +74 -0
  39. package/dist/ctl/transport.d.ts +11 -0
  40. package/dist/ctl/transport.js +79 -0
  41. package/dist/deploy/deploy.d.ts +4 -3
  42. package/dist/deploy/deploy.js +5 -210
  43. package/dist/deploy/plan.js +26 -7
  44. package/dist/dev.js +5 -4
  45. package/dist/e2e/dsm.d.ts +2 -2
  46. package/dist/e2e/dsm.js +10 -6
  47. package/dist/error-registry.js +1020 -834
  48. package/dist/host/share-permission-lookup.d.ts +1 -1
  49. package/dist/host/share-permission.d.ts +1 -1
  50. package/dist/host/share-permission.js +1 -1
  51. package/dist/info.js +120 -28
  52. package/dist/init.js +188 -64
  53. package/dist/inspect.js +21 -6
  54. package/dist/runtime/executor-digests.json +5 -5
  55. package/dist/skill.js +51 -9
  56. package/dist/standalone/api-description-inspect.d.ts +4 -0
  57. package/dist/standalone/api-description-inspect.js +12 -0
  58. package/dist/standalone/api-description.d.ts +7 -0
  59. package/dist/standalone/api-description.js +73 -0
  60. package/dist/standalone/build.d.ts +3 -0
  61. package/dist/standalone/build.js +140 -33
  62. package/dist/standalone/compose-errors.d.ts +3 -0
  63. package/dist/standalone/compose-errors.js +243 -0
  64. package/dist/standalone/compose.d.ts +97 -10
  65. package/dist/standalone/compose.js +306 -75
  66. package/dist/standalone/daemon.js +111 -29
  67. package/dist/standalone/dsm-window.js +39 -8
  68. package/dist/standalone/identity-gateway.d.ts +9 -2
  69. package/dist/standalone/identity-gateway.js +282 -66
  70. package/dist/standalone/inspect-spk.d.ts +12 -0
  71. package/dist/standalone/inspect-spk.js +666 -136
  72. package/dist/standalone/lifecycle.js +124 -42
  73. package/dist/standalone/manifest.d.ts +8 -1
  74. package/dist/standalone/manifest.js +303 -86
  75. package/dist/standalone/resource.js +6 -2
  76. package/dist/standalone/spk.d.ts +4 -1
  77. package/dist/standalone/spk.js +138 -39
  78. package/dist/template-paths.d.ts +0 -2
  79. package/dist/template-paths.js +0 -5
  80. package/dist/test-command.d.ts +1 -1
  81. package/dist/test-command.js +1 -1
  82. package/dist/validate.d.ts +3 -3
  83. package/dist/validate.js +32 -140
  84. package/package.json +5 -3
  85. package/schema/synopkgland-api-v1.schema.json +423 -0
  86. package/skills/SKILL.md +4 -2
  87. package/skills/reference/compose.md +349 -0
  88. package/skills/reference/create.md +9 -18
  89. package/skills/reference/ctl.md +41 -0
  90. package/skills/reference/deploy.md +1 -1
  91. package/skills/reference/files.md +5 -1
  92. package/skills/reference/identity-gateway.md +8 -144
  93. package/skills/reference/wrap.md +10 -39
  94. package/assets/go/daemon/fixture_linux_test.go +0 -15
  95. package/assets/go/daemon/fixture_other_test.go +0 -7
  96. package/assets/go/daemon/go.mod +0 -5
  97. package/assets/go/daemon/main.go +0 -636
  98. package/assets/go/daemon/main_test.go +0 -315
  99. package/assets/go/daemon/network.go +0 -107
  100. package/assets/go/daemon/network_test.go +0 -86
  101. package/assets/go/daemon/process_linux.go +0 -55
  102. package/assets/go/daemon/process_linux_test.go +0 -161
  103. package/assets/go/daemon/process_other.go +0 -14
  104. package/assets/go/daemon/testdata/child.go +0 -44
  105. package/assets/go/gateway/auth.go +0 -101
  106. package/assets/go/gateway/auth_test.go +0 -216
  107. package/assets/go/gateway/cgi.go +0 -133
  108. package/assets/go/gateway/cgi_test.go +0 -156
  109. package/assets/go/gateway/csrf.go +0 -73
  110. package/assets/go/gateway/csrf_test.go +0 -155
  111. package/assets/go/gateway/directory.go +0 -108
  112. package/assets/go/gateway/directory_test.go +0 -202
  113. package/assets/go/gateway/gateway.go +0 -412
  114. package/assets/go/gateway/gateway_test.go +0 -676
  115. package/assets/go/gateway/go.mod +0 -5
  116. package/assets/go/gateway/groups.go +0 -66
  117. package/assets/go/gateway/main.go +0 -45
  118. package/dist/build-spk/gateway.d.ts +0 -31
  119. package/dist/build-spk/gateway.js +0 -60
  120. package/dist/compose/privilege.d.ts +0 -14
  121. package/dist/compose/privilege.js +0 -102
@@ -1,315 +0,0 @@
1
- package main
2
-
3
- import (
4
- "context"
5
- "encoding/json"
6
- "os"
7
- "os/exec"
8
- "os/user"
9
- "path/filepath"
10
- "runtime"
11
- "strings"
12
- "syscall"
13
- "testing"
14
- "time"
15
- )
16
-
17
- func fixture(t *testing.T, mode string) (string, string, string) {
18
- t.Helper()
19
- root, err := os.MkdirTemp("/tmp", "c1-runtime-")
20
- if err != nil {
21
- t.Fatal(err)
22
- }
23
- t.Cleanup(func() { os.RemoveAll(root) })
24
- dest, data := filepath.Join(root, "target"), filepath.Join(root, "var")
25
- if err := os.MkdirAll(filepath.Join(dest, "daemon/payload"), 0755); err != nil {
26
- t.Fatal(err)
27
- }
28
- if err := os.Mkdir(data, 0700); err != nil {
29
- t.Fatal(err)
30
- }
31
- control, child := os.Getenv("DAEMON_CONTROL_BIN"), os.Getenv("DAEMON_CHILD_BIN")
32
- if control == "" {
33
- control, child = filepath.Join(root, "control"), filepath.Join(root, "child")
34
- for _, spec := range [][]string{{"build", "-o", control, "."}, {"build", "-o", child, "./testdata/child.go"}} {
35
- if raw, err := exec.Command("go", spec...).CombinedOutput(); err != nil {
36
- t.Fatalf("%v: %s", err, raw)
37
- }
38
- }
39
- }
40
- raw, err := os.ReadFile(child)
41
- if err != nil {
42
- t.Fatal(err)
43
- }
44
- app := filepath.Join(dest, "daemon/payload/app")
45
- if err := os.WriteFile(app, raw, 0755); err != nil {
46
- t.Fatal(err)
47
- }
48
- // macOS verifies a newly written executable on its first exec, which takes
49
- // seconds on a loaded host; pay it here, not inside a one-second budget.
50
- if raw, err := exec.Command(app, "cleanup").CombinedOutput(); err != nil {
51
- t.Fatalf("payload warm-up: %v %s", err, raw)
52
- }
53
- cfg := config{Package: "DaemonFixture", Exec: "app", Args: []string{"${SYNOPKG_PKGVAR}/a space", "$(touch BAD)'", "${SYNOPKG_PKGDEST}"}, Env: map[string]string{"APP_MODE": mode}, Timeouts: deadlines{Start: 2, Stop: 2, Status: 1, Uninstall: 2}}
54
- if os.Geteuid() == 0 {
55
- cfg.Package = "nobody"
56
- } else {
57
- u, err := user.Current()
58
- if err != nil {
59
- t.Fatal(err)
60
- }
61
- cfg.Package = u.Username
62
- }
63
- raw, _ = json.Marshal(cfg)
64
- if err := os.WriteFile(filepath.Join(dest, "daemon/config.json"), raw, 0644); err != nil {
65
- t.Fatal(err)
66
- }
67
- if os.Geteuid() == 0 {
68
- // Only inside the disposable root VM: run the shipped manager as nobody.
69
- if runtime.GOOS != "linux" {
70
- t.Fatal("root fixture requires Linux VM")
71
- }
72
- filepath.Walk(root, func(path string, _ os.FileInfo, err error) error {
73
- if err == nil {
74
- return os.Chown(path, 65534, 65534)
75
- }
76
- return err
77
- })
78
- os.Chmod(root, 0755)
79
- }
80
- t.Cleanup(func() { runControl(control, "stop", dest, data) })
81
- return control, dest, data
82
- }
83
-
84
- func runControl(control, action, dest, data string) (int, string) {
85
- ctx, cancel := context.WithTimeout(context.Background(), 8*time.Second)
86
- defer cancel()
87
- cmd := exec.CommandContext(ctx, control, action, dest, data)
88
- cmd.Env = append(os.Environ(), "UNLISTED_SECRET=must-not-leak", "LD_PRELOAD=must-not-leak")
89
- dropFixtureUser(cmd)
90
- raw, err := cmd.CombinedOutput()
91
- if err == nil {
92
- return 0, string(raw)
93
- }
94
- if e, ok := err.(*exec.ExitError); ok {
95
- return e.ExitCode(), string(raw)
96
- }
97
- return -1, err.Error()
98
- }
99
-
100
- // Readiness only proves the child stayed alive for a second, not that it reached
101
- // main: on a loaded host a stop can kill it before it writes its line. Spawn
102
- // identity therefore comes from the inventory, and log content is awaited.
103
- func recordedChild(t *testing.T, data string) int {
104
- t.Helper()
105
- raw, err := os.ReadFile(filepath.Join(data, "daemon/daemon.pid"))
106
- if err != nil {
107
- t.Fatal(err)
108
- }
109
- var r record
110
- if err := json.Unmarshal(raw, &r); err != nil || r.Child < 1 {
111
- t.Fatalf("daemon inventory: %v %s", err, raw)
112
- }
113
- return r.Child
114
- }
115
-
116
- func awaitChildLines(t *testing.T, data string, want int) {
117
- t.Helper()
118
- path := filepath.Join(data, "daemon/daemon.log")
119
- for deadline := time.Now().Add(10 * time.Second); ; time.Sleep(20 * time.Millisecond) {
120
- raw, _ := os.ReadFile(path)
121
- if got := strings.Count(string(raw), `"uid":`); got >= want || time.Now().After(deadline) {
122
- if got != want {
123
- t.Fatalf("child lines=%d want=%d: %s", got, want, raw)
124
- }
125
- return
126
- }
127
- }
128
- }
129
-
130
- func TestDaemonLifecycle(t *testing.T) {
131
- control, dest, data := fixture(t, "normal")
132
- step := func(action string, want int) {
133
- t.Helper()
134
- if rc, out := runControl(control, action, dest, data); rc != want {
135
- t.Fatalf("%s rc=%d want=%d: %s", action, rc, want, out)
136
- }
137
- }
138
- step("status", 3)
139
- step("stop", 0)
140
- step("start", 0)
141
- awaitChildLines(t, data, 1)
142
- first := recordedChild(t, data)
143
- step("status", 0)
144
- step("start", 0)
145
- if child := recordedChild(t, data); child != first {
146
- t.Fatalf("duplicate start spawned another child: %d replaced %d", child, first)
147
- }
148
- step("restart", 0)
149
- if child := recordedChild(t, data); child == first {
150
- t.Fatalf("restart kept child %d", child)
151
- }
152
- awaitChildLines(t, data, 2)
153
- step("stop", 0)
154
- step("stop", 0)
155
- step("status", 3)
156
- step("unknown", 1)
157
- raw, err := os.ReadFile(filepath.Join(data, "daemon/daemon.log"))
158
- if err != nil {
159
- t.Fatal(err)
160
- }
161
- if strings.Count(string(raw), `"uid":`) != 2 {
162
- t.Fatalf("unexpected child spawn: %s", raw)
163
- }
164
- if strings.Contains(string(raw), "must-not-leak") {
165
- t.Fatalf("ambient environment leaked: %s", raw)
166
- }
167
- if !strings.Contains(string(raw), "$(touch BAD)'") || !strings.Contains(string(raw), "/a space") {
168
- t.Fatalf("argv changed: %s", raw)
169
- }
170
- stat, _ := os.Stat(filepath.Join(data, "daemon/daemon.log"))
171
- if stat.Mode().Perm() != 0600 {
172
- t.Fatal("log permissions")
173
- }
174
- stat, err = os.Stat(filepath.Join(data, "daemon/tmp"))
175
- if err != nil || stat.Mode().Perm() != 0700 {
176
- t.Fatal("private TMPDIR missing", err)
177
- }
178
- }
179
-
180
- func TestImmediateExitAndEscalation(t *testing.T) {
181
- for _, mode := range []string{"exit", "ignore"} {
182
- t.Run(mode, func(t *testing.T) {
183
- control, dest, data := fixture(t, mode)
184
- rc, out := runControl(control, "start", dest, data)
185
- if mode == "exit" {
186
- if rc != 1 {
187
- t.Fatalf("false readiness: %d %s", rc, out)
188
- }
189
- } else {
190
- if rc != 0 {
191
- t.Fatalf("start: %d %s", rc, out)
192
- }
193
- if rc, out = runControl(control, "stop", dest, data); rc != 0 {
194
- t.Fatalf("escalation: %d %s", rc, out)
195
- }
196
- }
197
- if rc, out = runControl(control, "status", dest, data); rc != 3 {
198
- t.Fatalf("residue: %d %s", rc, out)
199
- }
200
- })
201
- }
202
- }
203
-
204
- func TestForeignStateAndRootRefusal(t *testing.T) {
205
- control, dest, data := fixture(t, "normal")
206
- if os.Geteuid() == 0 {
207
- raw, err := exec.Command(control, "start", dest, data).CombinedOutput()
208
- if err == nil || !strings.Contains(string(raw), "package account") {
209
- t.Fatalf("root accepted: %s %v", raw, err)
210
- }
211
- }
212
- os.Mkdir(filepath.Join(data, "daemon"), 0700)
213
- if os.Geteuid() == 0 {
214
- os.Chown(filepath.Join(data, "daemon"), 65534, 65534)
215
- }
216
- os.Symlink(filepath.Join(data, "victim"), filepath.Join(data, "daemon/daemon.log"))
217
- if rc, out := runControl(control, "start", dest, data); rc != 1 {
218
- t.Fatalf("symlink accepted: %d %s", rc, out)
219
- }
220
- if _, err := os.Stat(filepath.Join(data, "victim")); !os.IsNotExist(err) {
221
- t.Fatal("followed log symlink")
222
- }
223
- }
224
-
225
- func TestUninstallCleanup(t *testing.T) {
226
- for _, mode := range []string{"cleanup", "cleanup-fail", "cleanup-hang"} {
227
- t.Run(mode, func(t *testing.T) {
228
- control, dest, data := fixture(t, "normal")
229
- path := filepath.Join(dest, "daemon/config.json")
230
- raw, _ := os.ReadFile(path)
231
- var cfg config
232
- json.Unmarshal(raw, &cfg)
233
- cfg.UninstallArgs = []string{mode, "${SYNOPKG_PKGVAR}"}
234
- raw, _ = json.Marshal(cfg)
235
- os.WriteFile(path, raw, 0644)
236
- if rc, out := runControl(control, "start", dest, data); rc != 0 {
237
- t.Fatalf("start: %d %s", rc, out)
238
- }
239
- want := 1
240
- if mode == "cleanup" {
241
- want = 0
242
- }
243
- if rc, out := runControl(control, "uninstall", dest, data); rc != want {
244
- t.Fatalf("cleanup rc=%d want=%d: %s", rc, want, out)
245
- }
246
- if rc, out := runControl(control, "status", dest, data); rc != 3 {
247
- t.Fatalf("cleanup must not restart daemon: %d %s", rc, out)
248
- }
249
- })
250
- }
251
- }
252
-
253
- // A revoke on uninstall can outlast the stop deadline on a loaded NAS; it has
254
- // its own budget instead of Stop-1.
255
- func TestUninstallCommandHasItsOwnBudget(t *testing.T) {
256
- control, dest, data := fixture(t, "normal")
257
- path := filepath.Join(dest, "daemon/config.json")
258
- raw, _ := os.ReadFile(path)
259
- var cfg config
260
- json.Unmarshal(raw, &cfg)
261
- cfg.UninstallArgs = []string{"cleanup-slow"}
262
- cfg.Timeouts.Uninstall = 5
263
- raw, _ = json.Marshal(cfg)
264
- os.WriteFile(path, raw, 0644)
265
- if rc, out := runControl(control, "start", dest, data); rc != 0 {
266
- t.Fatalf("start: %d %s", rc, out)
267
- }
268
- if rc, out := runControl(control, "uninstall", dest, data); rc != 0 {
269
- t.Fatalf("slow uninstall within its budget failed: %d %s", rc, out)
270
- }
271
- }
272
-
273
- func TestPackageAccountIdentity(t *testing.T) {
274
- control, dest, data := fixture(t, "normal")
275
- path := filepath.Join(dest, "daemon/config.json")
276
- raw, _ := os.ReadFile(path)
277
- var cfg config
278
- json.Unmarshal(raw, &cfg)
279
- cfg.Package = "root"
280
- raw, _ = json.Marshal(cfg)
281
- os.WriteFile(path, raw, 0644)
282
- rc, out := runControl(control, "start", dest, data)
283
- if rc == 0 {
284
- runControl(control, "stop", dest, data)
285
- }
286
- if rc != 1 {
287
- t.Fatalf("other account accepted: %d %s", rc, out)
288
- }
289
- }
290
-
291
- func TestFIFOInventoryCannotBlockStatus(t *testing.T) {
292
- control, dest, data := fixture(t, "normal")
293
- state := filepath.Join(data, "daemon")
294
- os.Mkdir(state, 0700)
295
- path := filepath.Join(state, "daemon.pid")
296
- t.Cleanup(func() { os.Remove(path) })
297
- if err := syscall.Mkfifo(path, 0600); err != nil {
298
- t.Fatal(err)
299
- }
300
- if os.Geteuid() == 0 {
301
- os.Chown(state, 65534, 65534)
302
- os.Chown(path, 65534, 65534)
303
- }
304
- ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
305
- defer cancel()
306
- cmd := exec.CommandContext(ctx, control, "status", dest, data)
307
- dropFixtureUser(cmd)
308
- raw, err := cmd.CombinedOutput()
309
- if ctx.Err() != nil {
310
- t.Fatal("status blocked on FIFO")
311
- }
312
- if e, ok := err.(*exec.ExitError); !ok || e.ExitCode() != 1 {
313
- t.Fatalf("FIFO status: %s %v", raw, err)
314
- }
315
- }
@@ -1,107 +0,0 @@
1
- package main
2
-
3
- import (
4
- "bufio"
5
- "errors"
6
- "fmt"
7
- "os"
8
- "path/filepath"
9
- "strconv"
10
- "strings"
11
- )
12
-
13
- // Host-network containers use the package UID. The kernel tables, rather than
14
- // an application's claimed environment setting, prove its listener bindings.
15
- // This is a configuration guard, not a network namespace or a firewall.
16
- type networkPolicy struct {
17
- Ports []int `json:"ports"`
18
- PublicPorts []int `json:"public_ports"`
19
- }
20
-
21
- func containsPort(ports []int, port int) bool {
22
- for _, p := range ports {
23
- if p == port {
24
- return true
25
- }
26
- }
27
- return false
28
- }
29
-
30
- func checkSocketTable(raw string, uid int, p *networkPolicy, udp bool) error {
31
- _, err := socketPorts(raw, uid, p, udp)
32
- return err
33
- }
34
-
35
- func socketPorts(raw string, uid int, p *networkPolicy, udp bool) (map[int]bool, error) {
36
- found := map[int]bool{}
37
- scan := bufio.NewScanner(strings.NewReader(raw))
38
- if !scan.Scan() {
39
- return nil, errors.New("missing socket table header")
40
- }
41
- for scan.Scan() {
42
- f := strings.Fields(scan.Text())
43
- if len(f) == 0 {
44
- continue
45
- }
46
- if len(f) < 10 {
47
- return nil, errors.New("invalid socket table")
48
- }
49
- owner, err := strconv.Atoi(f[7])
50
- if err != nil {
51
- return nil, err
52
- }
53
- if owner != uid {
54
- continue
55
- }
56
- if (!udp && f[3] != "0A") || (udp && f[3] != "07") {
57
- continue
58
- }
59
- endpoint := strings.Split(f[1], ":")
60
- if len(endpoint) != 2 {
61
- return nil, errors.New("invalid socket endpoint")
62
- }
63
- port64, err := strconv.ParseInt(endpoint[1], 16, 32)
64
- if err != nil {
65
- return nil, err
66
- }
67
- port := int(port64)
68
- if !udp && containsPort(p.PublicPorts, port) {
69
- continue
70
- }
71
- // /proc/net's IPv4 and IPv6 words are little-endian on supported DSM arches.
72
- loopback := endpoint[0] == "0100007F" || endpoint[0] == "00000000000000000000000001000000"
73
- if !loopback {
74
- return nil, fmt.Errorf("compose package listener on port %d is not bound to loopback", port)
75
- }
76
- if !udp && containsPort(p.Ports, port) && endpoint[0] == "0100007F" {
77
- found[port] = true
78
- }
79
- }
80
- return found, scan.Err()
81
- }
82
-
83
- func checkNetwork(dir string, uid int, p *networkPolicy) error {
84
- if p == nil {
85
- return nil
86
- }
87
- found := map[int]bool{}
88
- for _, name := range []string{"tcp", "tcp6", "udp", "udp6"} {
89
- raw, err := os.ReadFile(filepath.Join(dir, name))
90
- if err != nil {
91
- return err
92
- }
93
- ports, err := socketPorts(string(raw), uid, p, strings.HasPrefix(name, "udp"))
94
- if err != nil {
95
- return err
96
- }
97
- for port := range ports {
98
- found[port] = true
99
- }
100
- }
101
- for _, port := range p.Ports {
102
- if !found[port] {
103
- return fmt.Errorf("compose upstream port %d is not ready as the package UID on 127.0.0.1", port)
104
- }
105
- }
106
- return nil
107
- }
@@ -1,86 +0,0 @@
1
- package main
2
-
3
- import (
4
- "fmt"
5
- "os"
6
- "path/filepath"
7
- "testing"
8
- )
9
-
10
- func TestComposeNetworkPolicy(t *testing.T) {
11
- uid := os.Geteuid()
12
- for _, tc := range []struct {
13
- name, addr, state, port string
14
- ok bool
15
- }{
16
- {"loopback", "0100007F", "0A", "6D64", true},
17
- {"wildcard", "00000000", "0A", "6D64", false},
18
- {"other loopback", "0200007F", "0A", "6D64", false},
19
- {"public gateway", "00000000", "0A", "6D60", true},
20
- {"unknown port", "00000000", "0A", "6D66", false},
21
- {"connected", "00000000", "01", "6D66", true},
22
- } {
23
- t.Run(tc.name, func(t *testing.T) {
24
- line := socketFixture(tc.addr, tc.port, tc.state, uid)
25
- err := checkSocketTable(line, uid, &networkPolicy{Ports: []int{28004}, PublicPorts: []int{28000}}, false)
26
- if (err == nil) != tc.ok {
27
- t.Fatalf("got %v, expected ok %v", err, tc.ok)
28
- }
29
- })
30
- }
31
- }
32
-
33
- func TestComposeNetworkRequiresOwnedUpstream(t *testing.T) {
34
- dir := t.TempDir()
35
- for _, name := range []string{"tcp", "tcp6", "udp", "udp6"} {
36
- os.WriteFile(filepath.Join(dir, name), []byte("header\n"), 0600)
37
- }
38
- p := &networkPolicy{Ports: []int{28004}, PublicPorts: []int{28000}}
39
- if err := checkNetwork(dir, os.Geteuid(), p); err == nil {
40
- t.Fatal("missing upstream accepted")
41
- }
42
- os.WriteFile(filepath.Join(dir, "tcp"), []byte(socketFixture("0100007F", "6D64", "0A", os.Geteuid()+1)), 0600)
43
- if err := checkNetwork(dir, os.Geteuid(), p); err == nil {
44
- t.Fatal("foreign upstream accepted")
45
- }
46
- os.WriteFile(filepath.Join(dir, "tcp"), []byte(socketFixture("0100007F", "6D64", "0A", os.Geteuid())), 0600)
47
- if err := checkNetwork(dir, os.Geteuid(), p); err != nil {
48
- t.Fatal(err)
49
- }
50
- os.WriteFile(filepath.Join(dir, "udp"), []byte(socketFixture("00000000", "6D66", "07", os.Geteuid())), 0600)
51
- if err := checkNetwork(dir, os.Geteuid(), p); err == nil {
52
- t.Fatal("UDP wildcard accepted")
53
- }
54
- }
55
-
56
- func socketFixture(addr, port, state string, uid int) string {
57
- return fmt.Sprintf("header\n 0: %s:%s 00000000:0000 %s 0:0 0:0 0 %d 0 1\n", addr, port, state, uid)
58
- }
59
-
60
- func TestComposeUDPConnectedIsNotAListener(t *testing.T) {
61
- raw := socketFixture("EA53110A", "BEEF", "01", os.Geteuid())
62
- if err := checkSocketTable(raw, os.Geteuid(), &networkPolicy{Ports: []int{28004}}, true); err != nil {
63
- t.Fatal(err)
64
- }
65
- }
66
-
67
- func TestComposeIPv6AndUDPPublicPort(t *testing.T) {
68
- p := &networkPolicy{Ports: []int{28004}, PublicPorts: []int{28000}}
69
- for _, tc := range []struct {
70
- addr, port string
71
- udp, ok bool
72
- }{
73
- {"00000000000000000000000001000000", "6D64", false, true},
74
- {"00000000000000000000000000000000", "6D64", false, false},
75
- {"00000000", "6D60", true, false},
76
- } {
77
- state := "0A"
78
- if tc.udp {
79
- state = "07"
80
- }
81
- err := checkSocketTable(socketFixture(tc.addr, tc.port, state, os.Geteuid()), os.Geteuid(), p, tc.udp)
82
- if (err == nil) != tc.ok {
83
- t.Fatalf("%+v: %v", tc, err)
84
- }
85
- }
86
- }
@@ -1,55 +0,0 @@
1
- //go:build linux
2
-
3
- package main
4
-
5
- import (
6
- "errors"
7
- "net"
8
- "os"
9
- "strconv"
10
- "syscall"
11
- )
12
-
13
- func childAttributes() *syscall.SysProcAttr { return &syscall.SysProcAttr{Pdeathsig: syscall.SIGKILL} }
14
- func processBirth(pid int) string {
15
- raw, err := os.ReadFile("/proc/" + strconv.Itoa(pid) + "/stat")
16
- if err != nil {
17
- return ""
18
- }
19
- birth, _ := parseBirth(string(raw))
20
- return birth
21
- }
22
- func recordedChildAlive(pid int, birth string) bool {
23
- raw, err := os.ReadFile("/proc/" + strconv.Itoa(pid) + "/stat")
24
- if os.IsNotExist(err) {
25
- return false
26
- }
27
- if err != nil {
28
- return true
29
- }
30
- actual, alive := parseBirth(string(raw))
31
- if actual == "" {
32
- return true
33
- }
34
- return alive && (birth == "" || actual == birth)
35
- }
36
- func checkPeer(conn *net.UnixConn, pid int) error {
37
- fd, err := conn.SyscallConn()
38
- if err != nil {
39
- return err
40
- }
41
- var peer *syscall.Ucred
42
- var getErr error
43
- if err := fd.Control(func(raw uintptr) {
44
- peer, getErr = syscall.GetsockoptUcred(int(raw), syscall.SOL_SOCKET, syscall.SO_PEERCRED)
45
- }); err != nil {
46
- return err
47
- }
48
- if getErr != nil {
49
- return getErr
50
- }
51
- if peer.Uid != uint32(os.Geteuid()) || peer.Pid != int32(pid) {
52
- return errors.New("foreign daemon supervisor")
53
- }
54
- return nil
55
- }