@synopackageland/cli 0.3.9 → 0.4.0
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.
- package/README.md +20 -9
- package/dist/bin.js +1 -2
- package/dist/build-spk/generator.d.ts +0 -3
- package/dist/build-spk/generator.js +135 -287
- package/dist/build-spk/native-lifecycle.js +0 -3
- package/dist/build-spk/native-payload.js +0 -1
- package/dist/cli.js +171 -151
- package/dist/compose/analyze.d.ts +1 -2
- package/dist/compose/analyze.js +5 -48
- package/dist/contract/unsupported-types.d.ts +10 -0
- package/dist/contract/unsupported-types.js +49 -0
- package/dist/contract/validate.js +25 -2
- package/dist/contract/web-launch.js +0 -2
- package/dist/deploy/deploy.d.ts +1 -4
- package/dist/deploy/deploy.js +57 -156
- package/dist/deploy/homes-values.js +1 -1
- package/dist/deploy/plan.d.ts +18 -0
- package/dist/deploy/plan.js +54 -0
- package/dist/deploy/port-preflight.d.ts +21 -0
- package/dist/deploy/port-preflight.js +42 -0
- package/dist/deploy/spk-version.d.ts +2 -14
- package/dist/deploy/spk-version.js +25 -111
- package/dist/dev.d.ts +1 -3
- package/dist/dev.js +14 -28
- package/dist/error-registry.js +810 -857
- package/dist/host/dsm-client.js +23 -263
- package/dist/host/dsm-port-profile.d.ts +47 -0
- package/dist/host/dsm-port-profile.js +206 -0
- package/dist/host/port-info-lookup.d.ts +9 -0
- package/dist/host/port-info-lookup.js +87 -0
- package/dist/host/port-info.d.ts +33 -0
- package/dist/host/port-info.js +66 -0
- package/dist/host/types.d.ts +6 -78
- package/dist/index.d.ts +0 -9
- package/dist/index.js +0 -6
- package/dist/info.d.ts +0 -1
- package/dist/info.js +3 -10
- package/dist/init.d.ts +11 -1
- package/dist/init.js +112 -4
- package/dist/log.js +2 -3
- package/dist/native/validate.js +0 -1
- package/dist/standalone/binary.d.ts +9 -0
- package/dist/standalone/binary.js +67 -0
- package/dist/standalone/build.d.ts +25 -0
- package/dist/standalone/build.js +108 -0
- package/dist/standalone/dsm-window.d.ts +26 -0
- package/dist/standalone/dsm-window.js +251 -0
- package/dist/standalone/helper.d.ts +31 -0
- package/dist/standalone/helper.js +104 -0
- package/dist/standalone/i18n.d.ts +28 -0
- package/dist/standalone/i18n.js +161 -0
- package/dist/standalone/inspect-spk.d.ts +21 -0
- package/dist/standalone/inspect-spk.js +160 -0
- package/dist/standalone/manifest.d.ts +71 -0
- package/dist/standalone/manifest.js +276 -0
- package/dist/standalone/root-messages.d.ts +7 -0
- package/dist/standalone/root-messages.js +250 -0
- package/dist/standalone/root-ops.d.ts +42 -0
- package/dist/standalone/root-ops.js +101 -0
- package/dist/standalone/root-window.d.ts +35 -0
- package/dist/standalone/root-window.js +290 -0
- package/dist/standalone/spk.d.ts +22 -0
- package/dist/standalone/spk.js +205 -0
- package/dist/templates/binary/synology-app.yaml +7 -0
- package/dist/templates/hello-files/compose.yaml +0 -1
- package/dist/templates/hello-web/compose.yaml +13 -10
- package/dist/templates/hello-web/html/app.js +1 -23
- package/dist/templates/hello-web/html/index.html +1 -3
- package/dist/templates/hello-web-page/compose.yaml +13 -10
- package/dist/templates/hello-web-page/html/app.js +1 -23
- package/dist/templates/hello-web-page/html/index.html +1 -3
- package/dist/templates/native-go/README.md +268 -0
- package/dist/templates/native-go/backend/cmd/app/main.go +49 -0
- package/dist/templates/native-go/backend/cmd/helper/main.go +38 -0
- package/dist/templates/native-go/backend/go.mod +3 -0
- package/dist/templates/native-go/backend/internal/api/errors.go +33 -0
- package/dist/templates/native-go/backend/internal/api/registry.go +94 -0
- package/dist/templates/native-go/backend/internal/api/registry_test.go +329 -0
- package/dist/templates/native-go/backend/internal/api/server.go +155 -0
- package/dist/templates/native-go/backend/internal/app/root_none.go +9 -0
- package/dist/templates/native-go/backend/internal/app/routes.go +127 -0
- package/dist/templates/native-go/backend/internal/app/routes_test.go +167 -0
- package/dist/templates/native-go/backend/internal/dsm/auth.go +118 -0
- package/dist/templates/native-go/backend/internal/dsm/auth_test.go +139 -0
- package/dist/templates/native-go/backend/internal/helper/control.go +219 -0
- package/dist/templates/native-go/backend/internal/helper/control_test.go +242 -0
- package/dist/templates/native-go/backend/internal/helper/peer_linux.go +44 -0
- package/dist/templates/native-go/backend/internal/helper/peer_other.go +7 -0
- package/dist/templates/native-go/backend/internal/helper/process_linux.go +167 -0
- package/dist/templates/native-go/backend/internal/helper/process_other.go +5 -0
- package/dist/templates/native-go/backend/internal/helper/server_linux.go +83 -0
- package/dist/templates/native-go/backend/internal/helper/status.go +271 -0
- package/dist/templates/native-go/backend/internal/helper/status_test.go +205 -0
- package/dist/templates/native-go/backend/internal/helper/supervision.go +84 -0
- package/dist/templates/native-go/backend/internal/helper/supervision_test.go +124 -0
- package/dist/templates/native-go/backend/internal/helper/supervisor_linux.go +192 -0
- package/dist/templates/native-go/backend/internal/helper/supervisor_linux_test.go +302 -0
- package/dist/templates/native-go/dsm-package.yaml +58 -0
- package/dist/templates/native-go/gitignore +1 -0
- package/dist/templates/native-go/i18n/glossary.json +662 -0
- package/dist/templates/native-go/i18n/messages/de.json +28 -0
- package/dist/templates/native-go/i18n/messages/en.json +28 -0
- package/dist/templates/native-go/i18n/messages/es.json +28 -0
- package/dist/templates/native-go/i18n/messages/fr.json +28 -0
- package/dist/templates/native-go/i18n/messages/it.json +28 -0
- package/dist/templates/native-go/i18n/messages/ja.json +28 -0
- package/dist/templates/native-go/i18n/messages/ko.json +28 -0
- package/dist/templates/native-go/i18n/messages/zh-CN.json +28 -0
- package/dist/templates/native-go/i18n/messages/zh-TW.json +28 -0
- package/dist/templates/native-go/ui/app.css +35 -0
- package/dist/templates/native-go/ui/app.js +94 -0
- package/dist/templates/native-go-better-auth/backend/internal/app/identity.go +208 -0
- package/dist/templates/native-go-better-auth/backend/internal/app/identity_test.go +113 -0
- package/dist/templates/native-go-better-auth/node/app.mjs +72 -0
- package/dist/templates/native-go-better-auth/node/build.mjs +2 -0
- package/dist/templates/native-go-better-auth/node/package-lock.json +862 -0
- package/dist/templates/native-go-better-auth/node/package.json +1 -0
- package/dist/templates/native-go-better-auth/node/test/auth.test.mjs +31 -0
- package/dist/templates/native-go-integration/backend/internal/app/identity_none.go +6 -0
- package/dist/templates/native-go-integration/backend/internal/app/personal.go +203 -0
- package/dist/templates/native-go-integration/backend/internal/app/personal_test.go +119 -0
- package/dist/templates/native-go-root/backend/internal/app/root.go +107 -0
- package/dist/templates/native-go-root/backend/internal/app/root_test.go +124 -0
- package/dist/templates/native-go-root/root/whoami.sh +6 -0
- package/dist/templates/native-go-vue/README.md +110 -0
- package/dist/templates/native-go-vue/frontend/DESIGN.md +78 -0
- package/dist/templates/native-go-vue/frontend/package-lock.json +2507 -0
- package/dist/templates/native-go-vue/frontend/package.json +6 -0
- package/dist/templates/native-go-vue/frontend/src/App.vue +132 -0
- package/dist/templates/native-go-vue/frontend/src/context.ts +17 -0
- package/dist/templates/native-go-vue/frontend/src/main.ts +26 -0
- package/dist/templates/native-go-vue/frontend/src/style.css +14 -0
- package/dist/templates/native-go-vue/frontend/test/apps.test.ts +148 -0
- package/dist/templates/native-go-vue/frontend/tsconfig.json +1 -0
- package/dist/templates/native-go-vue/frontend/vite.config.ts +8 -0
- package/dist/templates/native-go-vue/gitignore +2 -0
- package/dist/types.d.ts +1 -1
- package/dist/validate.js +11 -7
- package/package.json +3 -7
- package/schema/synology-app.schema.json +36 -37
- package/skills/SKILL.md +4 -48
- package/skills/reference/create.md +11 -83
- package/skills/reference/deploy.md +7 -78
- package/skills/reference/diagnose.md +4 -101
- package/skills/reference/files.md +5 -41
- package/skills/reference/identity.md +3 -29
- package/skills/reference/wrap.md +0 -2
- package/templates/binary/synology-app.yaml +7 -0
- package/templates/hello-files/compose.yaml +0 -1
- package/templates/hello-web/compose.yaml +13 -10
- package/templates/hello-web/html/app.js +1 -23
- package/templates/hello-web/html/index.html +1 -3
- package/templates/hello-web-page/compose.yaml +13 -10
- package/templates/hello-web-page/html/app.js +1 -23
- package/templates/hello-web-page/html/index.html +1 -3
- package/templates/native-go/README.md +268 -0
- package/templates/native-go/backend/cmd/app/main.go +49 -0
- package/templates/native-go/backend/cmd/helper/main.go +38 -0
- package/templates/native-go/backend/go.mod +3 -0
- package/templates/native-go/backend/internal/api/errors.go +33 -0
- package/templates/native-go/backend/internal/api/registry.go +94 -0
- package/templates/native-go/backend/internal/api/registry_test.go +329 -0
- package/templates/native-go/backend/internal/api/server.go +155 -0
- package/templates/native-go/backend/internal/app/root_none.go +9 -0
- package/templates/native-go/backend/internal/app/routes.go +127 -0
- package/templates/native-go/backend/internal/app/routes_test.go +167 -0
- package/templates/native-go/backend/internal/dsm/auth.go +118 -0
- package/templates/native-go/backend/internal/dsm/auth_test.go +139 -0
- package/templates/native-go/backend/internal/helper/control.go +219 -0
- package/templates/native-go/backend/internal/helper/control_test.go +242 -0
- package/templates/native-go/backend/internal/helper/peer_linux.go +44 -0
- package/templates/native-go/backend/internal/helper/peer_other.go +7 -0
- package/templates/native-go/backend/internal/helper/process_linux.go +167 -0
- package/templates/native-go/backend/internal/helper/process_other.go +5 -0
- package/templates/native-go/backend/internal/helper/server_linux.go +83 -0
- package/templates/native-go/backend/internal/helper/status.go +271 -0
- package/templates/native-go/backend/internal/helper/status_test.go +205 -0
- package/templates/native-go/backend/internal/helper/supervision.go +84 -0
- package/templates/native-go/backend/internal/helper/supervision_test.go +124 -0
- package/templates/native-go/backend/internal/helper/supervisor_linux.go +192 -0
- package/templates/native-go/backend/internal/helper/supervisor_linux_test.go +302 -0
- package/templates/native-go/dsm-package.yaml +58 -0
- package/templates/native-go/gitignore +1 -0
- package/templates/native-go/i18n/glossary.json +662 -0
- package/templates/native-go/i18n/messages/de.json +28 -0
- package/templates/native-go/i18n/messages/en.json +28 -0
- package/templates/native-go/i18n/messages/es.json +28 -0
- package/templates/native-go/i18n/messages/fr.json +28 -0
- package/templates/native-go/i18n/messages/it.json +28 -0
- package/templates/native-go/i18n/messages/ja.json +28 -0
- package/templates/native-go/i18n/messages/ko.json +28 -0
- package/templates/native-go/i18n/messages/zh-CN.json +28 -0
- package/templates/native-go/i18n/messages/zh-TW.json +28 -0
- package/templates/native-go/ui/app.css +35 -0
- package/templates/native-go/ui/app.js +94 -0
- package/templates/native-go-better-auth/backend/internal/app/identity.go +208 -0
- package/templates/native-go-better-auth/backend/internal/app/identity_test.go +113 -0
- package/templates/native-go-better-auth/node/app.mjs +72 -0
- package/templates/native-go-better-auth/node/build.mjs +2 -0
- package/templates/native-go-better-auth/node/package-lock.json +862 -0
- package/templates/native-go-better-auth/node/package.json +1 -0
- package/templates/native-go-better-auth/node/test/auth.test.mjs +31 -0
- package/templates/native-go-integration/backend/internal/app/identity_none.go +6 -0
- package/templates/native-go-integration/backend/internal/app/personal.go +203 -0
- package/templates/native-go-integration/backend/internal/app/personal_test.go +119 -0
- package/templates/native-go-root/backend/internal/app/root.go +107 -0
- package/templates/native-go-root/backend/internal/app/root_test.go +124 -0
- package/templates/native-go-root/root/whoami.sh +6 -0
- package/templates/native-go-vue/README.md +110 -0
- package/templates/native-go-vue/frontend/DESIGN.md +78 -0
- package/templates/native-go-vue/frontend/package-lock.json +2507 -0
- package/templates/native-go-vue/frontend/package.json +6 -0
- package/templates/native-go-vue/frontend/src/App.vue +132 -0
- package/templates/native-go-vue/frontend/src/context.ts +17 -0
- package/templates/native-go-vue/frontend/src/main.ts +26 -0
- package/templates/native-go-vue/frontend/src/style.css +14 -0
- package/templates/native-go-vue/frontend/test/apps.test.ts +148 -0
- package/templates/native-go-vue/frontend/tsconfig.json +1 -0
- package/templates/native-go-vue/frontend/vite.config.ts +8 -0
- package/templates/native-go-vue/gitignore +2 -0
- package/dist/attestation/app-package.d.ts +0 -86
- package/dist/attestation/app-package.js +0 -206
- package/dist/attestation/install-on-host.d.ts +0 -14
- package/dist/attestation/install-on-host.js +0 -52
- package/dist/attestation/keyring.d.ts +0 -21
- package/dist/attestation/keyring.js +0 -168
- package/dist/attestation/platform-service.d.ts +0 -70
- package/dist/attestation/platform-service.js +0 -250
- package/dist/build-spk/broker-glue.d.ts +0 -16
- package/dist/build-spk/broker-glue.js +0 -40
- package/dist/deploy/catalog-publish.d.ts +0 -3
- package/dist/deploy/catalog-publish.js +0 -7
- package/dist/doctor.d.ts +0 -22
- package/dist/doctor.js +0 -721
- package/dist/error-collector.d.ts +0 -2
- package/dist/error-collector.js +0 -46
- package/dist/host/broker-paths.d.ts +0 -1
- package/dist/host/broker-paths.js +0 -1
- package/dist/host/broker-upgrade-preflight.d.ts +0 -61
- package/dist/host/broker-upgrade-preflight.js +0 -156
- package/dist/host/digests.d.ts +0 -5
- package/dist/host/digests.js +0 -63
- package/dist/host/fake-nas.d.ts +0 -41
- package/dist/host/fake-nas.js +0 -174
- package/dist/mock-broker/bootstrap.d.ts +0 -2
- package/dist/mock-broker/bootstrap.js +0 -8
- package/dist/mock-broker/files.d.ts +0 -60
- package/dist/mock-broker/files.js +0 -408
- package/dist/mock-broker/oidc.d.ts +0 -31
- package/dist/mock-broker/oidc.js +0 -161
- package/dist/mock-broker/path.d.ts +0 -9
- package/dist/mock-broker/path.js +0 -50
- package/dist/mock-broker/server.d.ts +0 -19
- package/dist/mock-broker/server.js +0 -350
- package/dist/publish/build-ledger.d.ts +0 -22
- package/dist/publish/build-ledger.js +0 -26
- package/dist/publish/community-path.d.ts +0 -6
- package/dist/publish/community-path.js +0 -41
- package/dist/publish/curated-release.d.ts +0 -78
- package/dist/publish/curated-release.js +0 -883
- package/dist/publish/file-build-ledger.d.ts +0 -18
- package/dist/publish/file-build-ledger.js +0 -268
- package/dist/publish/github-pr.d.ts +0 -25
- package/dist/publish/github-pr.js +0 -85
- package/dist/publish/github-repo.d.ts +0 -8
- package/dist/publish/github-repo.js +0 -21
- package/dist/publish/platform-service-release.d.ts +0 -60
- package/dist/publish/platform-service-release.js +0 -333
- package/dist/publish/preflight.d.ts +0 -24
- package/dist/publish/preflight.js +0 -138
- package/dist/publish/publish.d.ts +0 -27
- package/dist/publish/publish.js +0 -136
- package/dist/publish/sync-definition.d.ts +0 -4
- package/dist/publish/sync-definition.js +0 -48
- package/dist/release-bin.d.ts +0 -2
- package/dist/release-bin.js +0 -23
- package/dist/templates/hello-web/html/sdk.js +0 -68
- package/dist/templates/hello-web-page/html/sdk.js +0 -68
- package/dist/templates/native/synology-app.yaml +0 -7
- package/dist/test-host.d.ts +0 -15
- package/dist/test-host.js +0 -37
- package/templates/hello-web/html/sdk.js +0 -68
- package/templates/hello-web-page/html/sdk.js +0 -68
- package/templates/native/synology-app.yaml +0 -7
- /package/dist/templates/{native → binary}/native.yaml +0 -0
- /package/templates/{native → binary}/native.yaml +0 -0
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
package helper
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
"encoding/json"
|
|
6
|
+
"net"
|
|
7
|
+
"os"
|
|
8
|
+
"path/filepath"
|
|
9
|
+
"strings"
|
|
10
|
+
"testing"
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
func fixture(t *testing.T) Client {
|
|
14
|
+
t.Helper()
|
|
15
|
+
i := Inventory{Package: "Fixture", Version: "1.0.0-0001", Protocol: 1, PayloadDigest: strings.Repeat("a", 64), RegistryDigest: strings.Repeat("b", 64)}
|
|
16
|
+
run, err := os.MkdirTemp("/tmp", "m1a-")
|
|
17
|
+
if err != nil {
|
|
18
|
+
t.Fatal(err)
|
|
19
|
+
}
|
|
20
|
+
t.Cleanup(func() { os.RemoveAll(run) })
|
|
21
|
+
return Client{Expected: i, StateDir: t.TempDir(), RunDir: run, Owner: uint32(os.Getuid()), Peer: func(*net.UnixConn) error { return nil }}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
func record(t *testing.T, c Client, r Record) {
|
|
25
|
+
t.Helper()
|
|
26
|
+
b, _ := json.Marshal(r)
|
|
27
|
+
if err := os.WriteFile(filepath.Join(c.StateDir, "approval.json"), b, 0644); err != nil {
|
|
28
|
+
t.Fatal(err)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
func live(t *testing.T, c Client, i Inventory, reply func(Request) Response) {
|
|
33
|
+
t.Helper()
|
|
34
|
+
l, err := net.ListenUnix("unix", &net.UnixAddr{Name: filepath.Join(c.RunDir, "helper.sock"), Net: "unix"})
|
|
35
|
+
if err != nil {
|
|
36
|
+
t.Fatal(err)
|
|
37
|
+
}
|
|
38
|
+
t.Cleanup(func() { l.Close() })
|
|
39
|
+
go func() {
|
|
40
|
+
for {
|
|
41
|
+
conn, err := l.AcceptUnix()
|
|
42
|
+
if err != nil {
|
|
43
|
+
return
|
|
44
|
+
}
|
|
45
|
+
go func() {
|
|
46
|
+
defer conn.Close()
|
|
47
|
+
var req Request
|
|
48
|
+
if json.NewDecoder(conn).Decode(&req) != nil {
|
|
49
|
+
return
|
|
50
|
+
}
|
|
51
|
+
r := Respond(i, false, req)
|
|
52
|
+
if reply != nil {
|
|
53
|
+
r = reply(req)
|
|
54
|
+
}
|
|
55
|
+
json.NewEncoder(conn).Encode(r)
|
|
56
|
+
}()
|
|
57
|
+
}
|
|
58
|
+
}()
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
func TestSixStatesAndPriority(t *testing.T) {
|
|
62
|
+
c := fixture(t)
|
|
63
|
+
check := func(want string) {
|
|
64
|
+
t.Helper()
|
|
65
|
+
s := c.Status(context.Background())
|
|
66
|
+
if s.State != want {
|
|
67
|
+
t.Fatalf("want %s, got %+v", want, s)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
c.Expected = Inventory{}
|
|
71
|
+
check("not-enabled")
|
|
72
|
+
c.Expected = fixture(t).Expected
|
|
73
|
+
check("needs-approval")
|
|
74
|
+
record(t, c, Record{Inventory: c.Expected, Enabled: true})
|
|
75
|
+
check("stopped")
|
|
76
|
+
live(t, c, c.Expected, nil)
|
|
77
|
+
check("ready")
|
|
78
|
+
old := c.Expected
|
|
79
|
+
old.Protocol++
|
|
80
|
+
record(t, c, Record{Inventory: old, Enabled: true})
|
|
81
|
+
check("incompatible")
|
|
82
|
+
record(t, c, Record{Inventory: old, Enabled: false})
|
|
83
|
+
check("not-enabled")
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
func TestLiveProbeCannotBeReplacedByApprovalOrHistoricalOutput(t *testing.T) {
|
|
87
|
+
c := fixture(t)
|
|
88
|
+
record(t, c, Record{Inventory: c.Expected, Enabled: true})
|
|
89
|
+
if err := os.WriteFile(filepath.Join(c.RunDir, "helper.sock"), []byte("ready"), 0644); err != nil {
|
|
90
|
+
t.Fatal(err)
|
|
91
|
+
}
|
|
92
|
+
if got := c.Status(context.Background()).State; got != "incompatible" {
|
|
93
|
+
t.Fatal(got)
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
func TestHandshakeFailuresAreRedacted(t *testing.T) {
|
|
98
|
+
for _, kind := range []string{"nonce", "protocol", "payload", "worker", "version", "peer"} {
|
|
99
|
+
t.Run(kind, func(t *testing.T) {
|
|
100
|
+
c := fixture(t)
|
|
101
|
+
record(t, c, Record{Inventory: c.Expected, Enabled: true})
|
|
102
|
+
live(t, c, c.Expected, func(req Request) Response {
|
|
103
|
+
r := Respond(c.Expected, false, req)
|
|
104
|
+
switch kind {
|
|
105
|
+
case "nonce":
|
|
106
|
+
r.Nonce = "replay"
|
|
107
|
+
case "protocol":
|
|
108
|
+
r.Inventory.Protocol++
|
|
109
|
+
case "payload":
|
|
110
|
+
r.Inventory.PayloadDigest = "foreign"
|
|
111
|
+
case "worker":
|
|
112
|
+
r.Inventory.RegistryDigest = "foreign"
|
|
113
|
+
case "version":
|
|
114
|
+
r.Inventory.Version = "old"
|
|
115
|
+
}
|
|
116
|
+
return r
|
|
117
|
+
})
|
|
118
|
+
if kind == "peer" {
|
|
119
|
+
c.Peer = func(*net.UnixConn) error { return os.ErrPermission }
|
|
120
|
+
}
|
|
121
|
+
s := c.Status(context.Background())
|
|
122
|
+
if s.State != "incompatible" {
|
|
123
|
+
t.Fatalf("%+v", s)
|
|
124
|
+
}
|
|
125
|
+
b, _ := json.Marshal(s)
|
|
126
|
+
if strings.Contains(string(b), c.StateDir) || strings.Contains(string(b), "foreign") {
|
|
127
|
+
t.Fatal(string(b))
|
|
128
|
+
}
|
|
129
|
+
})
|
|
130
|
+
}
|
|
131
|
+
c := fixture(t)
|
|
132
|
+
record(t, c, Record{Inventory: c.Expected, Enabled: true})
|
|
133
|
+
live(t, c, c.Expected, func(req Request) Response { return Response{Inventory: c.Expected, Nonce: req.Nonce, Error: "failed"} })
|
|
134
|
+
if c.Status(context.Background()).State != "failed" {
|
|
135
|
+
t.Fatal("health failure must be failed")
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
func TestUntrustedPersistentStateFailsClosed(t *testing.T) {
|
|
140
|
+
for _, kind := range []string{"writable", "symlink", "malformed"} {
|
|
141
|
+
t.Run(kind, func(t *testing.T) {
|
|
142
|
+
c := fixture(t)
|
|
143
|
+
record(t, c, Record{Inventory: c.Expected, Enabled: true})
|
|
144
|
+
p := filepath.Join(c.StateDir, "approval.json")
|
|
145
|
+
switch kind {
|
|
146
|
+
case "writable":
|
|
147
|
+
os.Chmod(p, 0666)
|
|
148
|
+
case "symlink":
|
|
149
|
+
os.Rename(p, p+".real")
|
|
150
|
+
os.Symlink(p+".real", p)
|
|
151
|
+
case "malformed":
|
|
152
|
+
os.WriteFile(p, []byte(`{"enabled":false}`), 0644)
|
|
153
|
+
}
|
|
154
|
+
if c.Status(context.Background()).State != "incompatible" {
|
|
155
|
+
t.Fatal("untrusted state accepted")
|
|
156
|
+
}
|
|
157
|
+
})
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
func TestOldHelperRejectsNewInventoryAndAllUnimplementedWork(t *testing.T) {
|
|
162
|
+
i := fixture(t).Expected
|
|
163
|
+
for _, field := range []string{"protocol", "payload", "workers", "version", "package"} {
|
|
164
|
+
new := i
|
|
165
|
+
switch field {
|
|
166
|
+
case "protocol":
|
|
167
|
+
new.Protocol++
|
|
168
|
+
case "payload":
|
|
169
|
+
new.PayloadDigest = strings.Repeat("c", 64)
|
|
170
|
+
case "workers":
|
|
171
|
+
new.RegistryDigest = strings.Repeat("c", 64)
|
|
172
|
+
case "version":
|
|
173
|
+
new.Version = "2.0.0-0001"
|
|
174
|
+
case "package":
|
|
175
|
+
new.Package = "Other"
|
|
176
|
+
}
|
|
177
|
+
r := Respond(i, false, Request{Inventory: new, Op: "work", Nonce: "test"})
|
|
178
|
+
if r.Error != "incompatible" || r.OK {
|
|
179
|
+
t.Fatalf("%s: %+v", field, r)
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
if r := Respond(i, true, Request{Inventory: i, Op: "health"}); r.OK || r.Error != "disabled" {
|
|
183
|
+
t.Fatal(r)
|
|
184
|
+
}
|
|
185
|
+
if r := Respond(i, false, Request{Inventory: i, Op: "work"}); r.OK || r.Error != "unsupported" {
|
|
186
|
+
t.Fatal(r)
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
func TestMissingEnableFieldIsNotARevocationRecord(t *testing.T) {
|
|
191
|
+
c := fixture(t)
|
|
192
|
+
b, _ := json.Marshal(map[string]any{"inventory": c.Expected})
|
|
193
|
+
os.WriteFile(filepath.Join(c.StateDir, "approval.json"), b, 0644)
|
|
194
|
+
if c.Status(context.Background()).State != "incompatible" {
|
|
195
|
+
t.Fatal("incomplete approval treated as trusted disable")
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
func TestProcessRecordIsNotReadableByOrdinaryOSUsers(t *testing.T) {
|
|
200
|
+
c, _ := controlFixture(t)
|
|
201
|
+
s, err := os.Stat(filepath.Join(c.RunDir, "process.json"))
|
|
202
|
+
if err != nil || s.Mode().Perm() != 0600 {
|
|
203
|
+
t.Fatalf("process record permissions: %v %v", s, err)
|
|
204
|
+
}
|
|
205
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
package helper
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"bytes"
|
|
5
|
+
"context"
|
|
6
|
+
"encoding/json"
|
|
7
|
+
"io"
|
|
8
|
+
"syscall"
|
|
9
|
+
"time"
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
// Also runs after exec: the parent can die between fork's prctl and exec.
|
|
13
|
+
func armParentDeath(expected int, set func() error, parent func() int) error {
|
|
14
|
+
if expected <= 1 || parent() != expected {
|
|
15
|
+
return errTrust
|
|
16
|
+
}
|
|
17
|
+
if err := set(); err != nil {
|
|
18
|
+
return err
|
|
19
|
+
}
|
|
20
|
+
if parent() != expected {
|
|
21
|
+
return errTrust
|
|
22
|
+
}
|
|
23
|
+
return nil
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
type supervisedChild interface {
|
|
27
|
+
exited() (bool, error)
|
|
28
|
+
signal(syscall.Signal) error
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Caller is the sole reaper and signal owner. A zombie retains its PID until
|
|
32
|
+
// exited() reaps it; a background Wait goroutine would invalidate this proof.
|
|
33
|
+
func stopChild(ctx context.Context, child supervisedChild, grace time.Duration) error {
|
|
34
|
+
if err := ctx.Err(); err != nil {
|
|
35
|
+
return err
|
|
36
|
+
}
|
|
37
|
+
done, err := child.exited()
|
|
38
|
+
if err != nil || done {
|
|
39
|
+
return err
|
|
40
|
+
}
|
|
41
|
+
if err := child.signal(syscall.SIGTERM); err != nil {
|
|
42
|
+
return err
|
|
43
|
+
}
|
|
44
|
+
escalate := time.Now().Add(grace)
|
|
45
|
+
killed := false
|
|
46
|
+
for {
|
|
47
|
+
if err := ctx.Err(); err != nil {
|
|
48
|
+
return err
|
|
49
|
+
}
|
|
50
|
+
done, err := child.exited()
|
|
51
|
+
if err != nil || done {
|
|
52
|
+
return err
|
|
53
|
+
}
|
|
54
|
+
if !killed && !time.Now().Before(escalate) {
|
|
55
|
+
if err := child.signal(syscall.SIGKILL); err != nil {
|
|
56
|
+
return err
|
|
57
|
+
}
|
|
58
|
+
killed = true
|
|
59
|
+
}
|
|
60
|
+
timer := time.NewTimer(time.Millisecond)
|
|
61
|
+
select {
|
|
62
|
+
case <-ctx.Done():
|
|
63
|
+
timer.Stop()
|
|
64
|
+
return ctx.Err()
|
|
65
|
+
case <-timer.C:
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Read to actual connection EOF. A LimitReader's synthetic EOF at the size
|
|
71
|
+
// cap can never certify supervisor exit, so reaching that cap is a refusal.
|
|
72
|
+
func readSupervisorReply(reader io.Reader, want Inventory, nonce string) error {
|
|
73
|
+
b, err := io.ReadAll(io.LimitReader(reader, 4097))
|
|
74
|
+
if err != nil || len(b) > 4096 {
|
|
75
|
+
return errTrust
|
|
76
|
+
}
|
|
77
|
+
var res Response
|
|
78
|
+
d := json.NewDecoder(bytes.NewReader(b))
|
|
79
|
+
d.DisallowUnknownFields()
|
|
80
|
+
if d.Decode(&res) != nil || d.Decode(new(any)) != io.EOF || !res.OK || res.Error != "" || res.Inventory != want || res.Nonce != nonce {
|
|
81
|
+
return errTrust
|
|
82
|
+
}
|
|
83
|
+
return nil
|
|
84
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
package helper
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"bytes"
|
|
5
|
+
"context"
|
|
6
|
+
"encoding/json"
|
|
7
|
+
"strings"
|
|
8
|
+
"syscall"
|
|
9
|
+
"testing"
|
|
10
|
+
"time"
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
func TestParentDeathSetupChecksBeforeAndAfterPrctl(t *testing.T) {
|
|
14
|
+
for _, tc := range []struct {
|
|
15
|
+
before, after int
|
|
16
|
+
setErr error
|
|
17
|
+
ok bool
|
|
18
|
+
}{
|
|
19
|
+
{42, 42, nil, true}, {1, 1, nil, false}, {42, 1, nil, false}, {42, 42, syscall.EPERM, false},
|
|
20
|
+
} {
|
|
21
|
+
calls, sets := 0, 0
|
|
22
|
+
err := armParentDeath(42, func() error { sets++; return tc.setErr }, func() int {
|
|
23
|
+
calls++
|
|
24
|
+
if calls == 1 {
|
|
25
|
+
return tc.before
|
|
26
|
+
}
|
|
27
|
+
return tc.after
|
|
28
|
+
})
|
|
29
|
+
if (err == nil) != tc.ok {
|
|
30
|
+
t.Fatalf("%+v: %v", tc, err)
|
|
31
|
+
}
|
|
32
|
+
if tc.before != 42 && sets != 0 {
|
|
33
|
+
t.Fatal("already reparented child armed")
|
|
34
|
+
}
|
|
35
|
+
if tc.ok && calls != 2 {
|
|
36
|
+
t.Fatal("parent race not checked")
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
type childFixture struct {
|
|
42
|
+
reaped bool
|
|
43
|
+
err error
|
|
44
|
+
signals []syscall.Signal
|
|
45
|
+
exitOn syscall.Signal
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
func (c *childFixture) exited() (bool, error) { return c.reaped, c.err }
|
|
49
|
+
func (c *childFixture) signal(s syscall.Signal) error {
|
|
50
|
+
if c.reaped {
|
|
51
|
+
panic("signalled a reaped child")
|
|
52
|
+
}
|
|
53
|
+
c.signals = append(c.signals, s)
|
|
54
|
+
if s == c.exitOn {
|
|
55
|
+
c.reaped = true
|
|
56
|
+
}
|
|
57
|
+
return nil
|
|
58
|
+
}
|
|
59
|
+
func TestOnlyUnreapedOwnedChildCanBeSignalled(t *testing.T) {
|
|
60
|
+
for _, tc := range []struct {
|
|
61
|
+
reaped bool
|
|
62
|
+
err error
|
|
63
|
+
exit syscall.Signal
|
|
64
|
+
count int
|
|
65
|
+
ok bool
|
|
66
|
+
}{
|
|
67
|
+
{true, nil, 0, 0, true}, {false, syscall.ECHILD, 0, 0, false}, {false, nil, syscall.SIGTERM, 1, true}, {false, nil, syscall.SIGKILL, 2, true}, {false, nil, 0, 2, false},
|
|
68
|
+
} {
|
|
69
|
+
c := &childFixture{reaped: tc.reaped, err: tc.err, exitOn: tc.exit}
|
|
70
|
+
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Millisecond)
|
|
71
|
+
err := stopChild(ctx, c, time.Millisecond)
|
|
72
|
+
cancel()
|
|
73
|
+
if (err == nil) != tc.ok || len(c.signals) != tc.count {
|
|
74
|
+
t.Fatalf("%+v: err=%v signals=%v", tc, err, c.signals)
|
|
75
|
+
}
|
|
76
|
+
if c.reaped {
|
|
77
|
+
n := len(c.signals)
|
|
78
|
+
if stopChild(context.Background(), c, time.Millisecond) != nil || len(c.signals) != n {
|
|
79
|
+
t.Fatal("reaped PID reused")
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
func TestCancelledStopCannotSignal(t *testing.T) {
|
|
85
|
+
ctx, cancel := context.WithCancel(context.Background())
|
|
86
|
+
cancel()
|
|
87
|
+
c := &childFixture{}
|
|
88
|
+
if stopChild(ctx, c, time.Millisecond) == nil || len(c.signals) != 0 {
|
|
89
|
+
t.Fatal("cancelled operation signalled")
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
func TestSupervisorReplyRequiresRealEOFAndStrictBoundedAcknowledgement(t *testing.T) {
|
|
94
|
+
i := fixture(t).Expected
|
|
95
|
+
nonce := strings.Repeat("c", 64)
|
|
96
|
+
good, _ := json.Marshal(Response{Inventory: i, Nonce: nonce, OK: true})
|
|
97
|
+
for _, tc := range []struct {
|
|
98
|
+
data []byte
|
|
99
|
+
ok bool
|
|
100
|
+
}{
|
|
101
|
+
{good, true}, {append(append([]byte{}, good...), []byte(" {}")...), false},
|
|
102
|
+
{bytes.Replace(good, []byte(nonce), []byte(strings.Repeat("d", 64)), 1), false},
|
|
103
|
+
{append(append([]byte{}, good[:len(good)-1]...), []byte(",\"unknown\":true}")...), false},
|
|
104
|
+
{append(append([]byte{}, good...), bytes.Repeat([]byte(" "), 4097)...), false},
|
|
105
|
+
} {
|
|
106
|
+
if (readSupervisorReply(bytes.NewReader(tc.data), i, nonce) == nil) != tc.ok {
|
|
107
|
+
t.Fatalf("reply accepted=%v expected=%v", !tc.ok, tc.ok)
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if readSupervisorReply(&brokenReplyReader{data: good}, i, nonce) == nil {
|
|
111
|
+
t.Fatal("read error treated as cooperative exit")
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
type brokenReplyReader struct{ data []byte }
|
|
116
|
+
|
|
117
|
+
func (r *brokenReplyReader) Read(p []byte) (int, error) {
|
|
118
|
+
if len(r.data) == 0 {
|
|
119
|
+
return 0, syscall.ECONNRESET
|
|
120
|
+
}
|
|
121
|
+
n := copy(p, r.data)
|
|
122
|
+
r.data = r.data[n:]
|
|
123
|
+
return n, nil
|
|
124
|
+
}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
package helper
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
"crypto/rand"
|
|
6
|
+
"encoding/hex"
|
|
7
|
+
"encoding/json"
|
|
8
|
+
"errors"
|
|
9
|
+
"io"
|
|
10
|
+
"net"
|
|
11
|
+
"os"
|
|
12
|
+
"os/exec"
|
|
13
|
+
"path/filepath"
|
|
14
|
+
"runtime"
|
|
15
|
+
"strconv"
|
|
16
|
+
"syscall"
|
|
17
|
+
"time"
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
func setParentDeath() error {
|
|
21
|
+
_, _, err := syscall.Syscall6(syscall.SYS_PRCTL, syscall.PR_SET_PDEATHSIG, uintptr(syscall.SIGKILL), 0, 0, 0, 0)
|
|
22
|
+
if err != 0 {
|
|
23
|
+
return err
|
|
24
|
+
}
|
|
25
|
+
return nil
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
type ownedLinuxChild struct {
|
|
29
|
+
process *os.Process
|
|
30
|
+
reaped bool
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
func (c *ownedLinuxChild) exited() (bool, error) {
|
|
34
|
+
if c.reaped {
|
|
35
|
+
return true, nil
|
|
36
|
+
}
|
|
37
|
+
var status syscall.WaitStatus
|
|
38
|
+
pid, err := syscall.Wait4(c.process.Pid, &status, syscall.WNOHANG, nil)
|
|
39
|
+
if errors.Is(err, syscall.EINTR) {
|
|
40
|
+
return false, nil
|
|
41
|
+
}
|
|
42
|
+
if err != nil {
|
|
43
|
+
return false, err
|
|
44
|
+
} // ECHILD never permits signalling.
|
|
45
|
+
if pid == c.process.Pid {
|
|
46
|
+
c.reaped = true
|
|
47
|
+
c.process.Release()
|
|
48
|
+
}
|
|
49
|
+
return c.reaped, nil
|
|
50
|
+
}
|
|
51
|
+
func (c *ownedLinuxChild) signal(s syscall.Signal) error {
|
|
52
|
+
if c.reaped {
|
|
53
|
+
return errTrust
|
|
54
|
+
}
|
|
55
|
+
// This PID came from our own Start, and ONLY this event loop calls Wait4.
|
|
56
|
+
// Even if it exits here it remains an unreaped zombie, pinning its PID.
|
|
57
|
+
return syscall.Kill(c.process.Pid, s)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
type supervisorRequest struct {
|
|
61
|
+
Process Process `json:"process"`
|
|
62
|
+
Op string `json:"op"`
|
|
63
|
+
Nonce string `json:"nonce"`
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// The thread creating the child must remain alive: PDEATHSIG is tied to that
|
|
67
|
+
// Linux thread, not merely the supervisor's thread group / Go process.
|
|
68
|
+
func superviseLinux(c Client, binary string) error {
|
|
69
|
+
runtime.LockOSThread()
|
|
70
|
+
defer runtime.UnlockOSThread()
|
|
71
|
+
r, err := readRecord(c.StateDir, 0)
|
|
72
|
+
if err != nil || !r.Enabled || r.Inventory != c.Expected {
|
|
73
|
+
return errTrust
|
|
74
|
+
}
|
|
75
|
+
identity, err := inspectSupervisor(os.Getpid(), c.Expected)
|
|
76
|
+
if err != nil {
|
|
77
|
+
return err
|
|
78
|
+
}
|
|
79
|
+
if err := writeProcess(c.RunDir, identity); err != nil {
|
|
80
|
+
return err
|
|
81
|
+
}
|
|
82
|
+
socket := filepath.Join(c.RunDir, "supervisor.sock")
|
|
83
|
+
l, err := net.ListenUnix("unix", &net.UnixAddr{Name: socket, Net: "unix"})
|
|
84
|
+
if err != nil {
|
|
85
|
+
return err
|
|
86
|
+
}
|
|
87
|
+
l.SetUnlinkOnClose(false)
|
|
88
|
+
defer l.Close()
|
|
89
|
+
if err := os.Chmod(socket, 0600); err != nil {
|
|
90
|
+
return err
|
|
91
|
+
}
|
|
92
|
+
cmd := exec.Command(binary, "serve-child")
|
|
93
|
+
cmd.Env = []string{"PATH=/usr/sbin:/usr/bin:/sbin:/bin", "SYNOPKGLAND_PARENT=" + strconv.Itoa(os.Getpid())}
|
|
94
|
+
// Go sets prctl before exec and rechecks the parent immediately afterward.
|
|
95
|
+
cmd.SysProcAttr = &syscall.SysProcAttr{Pdeathsig: syscall.SIGKILL}
|
|
96
|
+
if err := cmd.Start(); err != nil {
|
|
97
|
+
return err
|
|
98
|
+
}
|
|
99
|
+
child := &ownedLinuxChild{process: cmd.Process}
|
|
100
|
+
// No cmd.Wait goroutine: reaping and signalling are serialized below.
|
|
101
|
+
for {
|
|
102
|
+
if _, err := child.exited(); err != nil {
|
|
103
|
+
return err
|
|
104
|
+
}
|
|
105
|
+
l.SetDeadline(time.Now().Add(20 * time.Millisecond))
|
|
106
|
+
conn, err := l.AcceptUnix()
|
|
107
|
+
if err != nil {
|
|
108
|
+
if e, ok := err.(net.Error); ok && e.Timeout() {
|
|
109
|
+
continue
|
|
110
|
+
}
|
|
111
|
+
return err
|
|
112
|
+
}
|
|
113
|
+
conn.SetDeadline(time.Now().Add(3 * time.Second))
|
|
114
|
+
peer, err := peerCredentials(conn)
|
|
115
|
+
if err != nil || peer.Uid != 0 {
|
|
116
|
+
conn.Close()
|
|
117
|
+
continue
|
|
118
|
+
}
|
|
119
|
+
var req supervisorRequest
|
|
120
|
+
d := json.NewDecoder(io.LimitReader(conn, 4097))
|
|
121
|
+
d.DisallowUnknownFields()
|
|
122
|
+
if d.Decode(&req) != nil || req.Process != identity || req.Op != "stop" || len(req.Nonce) != 64 || !digestPattern.MatchString(req.Nonce) {
|
|
123
|
+
conn.Close()
|
|
124
|
+
continue
|
|
125
|
+
}
|
|
126
|
+
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
|
127
|
+
err = stopChild(ctx, child, time.Second)
|
|
128
|
+
cancel()
|
|
129
|
+
// Kernel process-exit fd cleanup is not ordered: close the listener
|
|
130
|
+
// before ACK, so connection EOF cannot race a still-listening socket.
|
|
131
|
+
if err == nil {
|
|
132
|
+
err = l.Close()
|
|
133
|
+
}
|
|
134
|
+
res := Response{Inventory: c.Expected, Nonce: req.Nonce, OK: err == nil}
|
|
135
|
+
if err != nil {
|
|
136
|
+
res.Error = "cleanup-required"
|
|
137
|
+
}
|
|
138
|
+
json.NewEncoder(conn).Encode(res)
|
|
139
|
+
if err == nil {
|
|
140
|
+
// Keep this connection open until OS exit. An ACK plus EOF proves both
|
|
141
|
+
// that the child was reaped and this supervisor finished cooperatively.
|
|
142
|
+
os.Exit(0)
|
|
143
|
+
}
|
|
144
|
+
conn.Close()
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
func (p LinuxProcesses) Stop(want Process) error {
|
|
149
|
+
if want.Mode != "supervised" {
|
|
150
|
+
return errTrust
|
|
151
|
+
}
|
|
152
|
+
// Loss/reboot/PID reuse is not sufficient proof of child teardown. Preserve
|
|
153
|
+
// the record for fixed admin repair; never signal this recorded PID.
|
|
154
|
+
actual, err := inspectSupervisor(want.PID, want.Inventory)
|
|
155
|
+
if err != nil || actual != want {
|
|
156
|
+
return errTrust
|
|
157
|
+
}
|
|
158
|
+
socket := filepath.Join(p.RunDir, "supervisor.sock")
|
|
159
|
+
st, err := os.Lstat(socket)
|
|
160
|
+
if err != nil {
|
|
161
|
+
return err
|
|
162
|
+
}
|
|
163
|
+
stat, ok := st.Sys().(*syscall.Stat_t)
|
|
164
|
+
if !ok || stat.Uid != 0 || st.Mode()&os.ModeSocket == 0 || st.Mode().Perm() != 0600 {
|
|
165
|
+
return errTrust
|
|
166
|
+
}
|
|
167
|
+
conn, err := net.DialTimeout("unix", socket, time.Second)
|
|
168
|
+
if err != nil {
|
|
169
|
+
return err
|
|
170
|
+
}
|
|
171
|
+
defer conn.Close()
|
|
172
|
+
peer, err := peerCredentials(conn.(*net.UnixConn))
|
|
173
|
+
if err != nil || peer.Uid != 0 || int(peer.Pid) != want.PID {
|
|
174
|
+
return errTrust
|
|
175
|
+
}
|
|
176
|
+
// Recheck after binding the connection too. A connected socket cannot be
|
|
177
|
+
// substituted by a later process that reused the path or PID.
|
|
178
|
+
actual, err = inspectSupervisor(want.PID, want.Inventory)
|
|
179
|
+
if err != nil || actual != want {
|
|
180
|
+
return errTrust
|
|
181
|
+
}
|
|
182
|
+
conn.SetDeadline(time.Now().Add(3 * time.Second))
|
|
183
|
+
var nonce [32]byte
|
|
184
|
+
if _, err := rand.Read(nonce[:]); err != nil {
|
|
185
|
+
return err
|
|
186
|
+
}
|
|
187
|
+
req := supervisorRequest{Process: want, Op: "stop", Nonce: hex.EncodeToString(nonce[:])}
|
|
188
|
+
if err := json.NewEncoder(conn).Encode(req); err != nil {
|
|
189
|
+
return err
|
|
190
|
+
}
|
|
191
|
+
return readSupervisorReply(conn, want.Inventory, req.Nonce)
|
|
192
|
+
}
|