mesh/delivery superseded: a newer head of the same pull request
mesh/merge-gate pass: builds build-agent, mesh-controller, route-proxy → ace, g14, novox, shanks; no bus step; every machine composes with the change as it…
mesh/repo-check fail: its merge-check.sh failed: --- FAIL: TestAnUnreadableStoreClearsNothing (0.01s)
The review of hq issue 339 found the PEM exception too wide (any module, any key, any label, anything base64), \v, \f, NEL and the Unicode separators still let a value end a line in some readers, and the spool, /opt, the container runtimes' data and an account's .ssh still placeable. Lines are now taken only in step-ca's root setting, as certificates encoding/pem decodes and x509 parses; every line end is refused; and those paths are the machine's own. The test certificate is a real one, made for the tests with its key thrown away.
130 lines
6.7 KiB
Go
130 lines
6.7 KiB
Go
package catalogue
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// No placement and no access at the machine's own system or the mesh's state, however it is spelled (novox/hq
|
|
// issue 339); a module's own place elsewhere is taken.
|
|
func TestAPlacementOrAnAccessAtTheMachinesOwnIsRefused(t *testing.T) {
|
|
m := Manifest{Module: "notes", Resources: []map[string]any{{"id": "data", "type": "directory"}},
|
|
Accesses: []Access{{ID: "media"}}}
|
|
for _, path := range []string{"/", "/etc", "/etc/", "/etc/sudoers.d", "/usr/bin", "/root", "/var", "/var/lib",
|
|
"/home", "/home/", "/run", "/run/user/1000", "/var/lib/mesh/x", "/var/lib/mesh-host", "/var/lib/mesh-host/identity",
|
|
"/srv/../etc", "//etc", "/proc/1", "/sys", "/dev", "/boot/efi", "/bin", "/sbin", "/lib", "/lib64"} {
|
|
layers := []Layer{{From: "laptop", Values: map[string]any{PlacesSetting: map[string]any{"data": path}}}}
|
|
if _, err := Places(m, layers); err == nil || !strings.Contains(err.Error(), "issue 339") {
|
|
t.Errorf("a place at %s: %v", path, err)
|
|
}
|
|
layers = []Layer{{From: "laptop", Values: map[string]any{AccessesSetting: map[string]any{"media": path}}}}
|
|
if _, err := AccessPlaces(m, layers); err == nil || !strings.Contains(err.Error(), "issue 339") {
|
|
t.Errorf("an access at %s: %v", path, err)
|
|
}
|
|
}
|
|
for _, path := range []string{"/srv/notes", "/mnt/plex/data", "/storage/media", "/services/media/movies",
|
|
"/var/lib/notes/data", "/home/restic/repo", "/var/lib/mesh-store"} {
|
|
layers := []Layer{{From: "laptop", Values: map[string]any{PlacesSetting: map[string]any{"data": path}}}}
|
|
if got, err := Places(m, layers); err != nil || got["data"].Path != path {
|
|
t.Errorf("a place at %s: %v %v", path, got, err)
|
|
}
|
|
layers = []Layer{{From: "laptop", Values: map[string]any{AccessesSetting: map[string]any{"media": path}}}}
|
|
if got, err := AccessPlaces(m, layers); err != nil || got["media"] != path {
|
|
t.Errorf("an access at %s: %v %v", path, got, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// A line break, a carriage return or a NUL in any string of any setting is refused, at any depth, keys too —
|
|
// where a layer is judged, which is both where it is kept and where it is composed (novox/hq issue 339).
|
|
func TestASettingHoldsOneLine(t *testing.T) {
|
|
m := Manifest{Module: "mailu"}
|
|
for _, v := range []any{"a\nDEBUG=1", "a\rb", "a\x00b", []any{"ok", "x\ny"},
|
|
map[string]any{"k": []any{"ok", map[string]any{"deep": "x\ny"}}}, map[string]any{"k\nx": "v"}} {
|
|
if err := JudgeSettings(m, []Layer{{From: "home", Values: map[string]any{"v": v}}}, false); err == nil ||
|
|
!strings.Contains(err.Error(), "line break") {
|
|
t.Errorf("%q: %v", v, err)
|
|
}
|
|
}
|
|
if err := JudgeSettings(m, []Layer{{From: "home", Values: map[string]any{"v\nPATH": "x"}}}, false); err == nil {
|
|
t.Error("a line break in a key was taken")
|
|
}
|
|
if err := JudgeSettings(m, []Layer{{From: "home", Values: map[string]any{"v": "one line", "n": 3.0,
|
|
"l": []any{"a", "b"}}}}, false); err != nil {
|
|
t.Errorf("one line each: %v", err)
|
|
}
|
|
}
|
|
|
|
// Lines are allowed in one setting only: step-ca's `root`, as certificates that encoding/pem decodes and
|
|
// x509.ParseCertificate parses (novox/hq issue 339). Any other module or key, another label, or a block that is
|
|
// not a certificate is refused like any other line break.
|
|
func TestOnlyTheAuthoritysRootMayHoldLines(t *testing.T) {
|
|
ca := Manifest{Module: "step-ca"}
|
|
judge := func(m Manifest, key, v string) error {
|
|
return JudgeSettings(m, []Layer{{From: "anchor", Values: map[string]any{key: v}}}, false)
|
|
}
|
|
for _, ok := range []string{servedRoot, servedRoot + servedRoot, strings.TrimSuffix(servedRoot, "\n")} {
|
|
if err := judge(ca, "root", ok); err != nil {
|
|
t.Errorf("the authority's root: %v", err)
|
|
}
|
|
}
|
|
body := strings.TrimSuffix(strings.TrimPrefix(servedRoot, "-----BEGIN CERTIFICATE-----\n"), "-----END CERTIFICATE-----\n")
|
|
for name, bad := range map[string]string{
|
|
"a line after it": servedRoot + "PATH=/tmp\n",
|
|
"a line before it": "PATH=\n" + servedRoot,
|
|
"another label": "-----BEGIN PRIVATE KEY-----\n" + body + "-----END PRIVATE KEY-----\n",
|
|
"headers": "-----BEGIN CERTIFICATE-----\nProc-Type: 4,ENCRYPTED\n\n" + body + "-----END CERTIFICATE-----\n",
|
|
"base64 that is no cert": "-----BEGIN CERTIFICATE-----\nMIIBeDCCAR2gAwIBAgIQfake000000000000000000000000\n-----END CERTIFICATE-----\n",
|
|
"carriage returns": strings.ReplaceAll(servedRoot, "\n", "\r\n"),
|
|
} {
|
|
if err := judge(ca, "root", bad); err == nil {
|
|
t.Errorf("%s was taken", name)
|
|
}
|
|
}
|
|
if err := judge(ca, "other", servedRoot); err == nil {
|
|
t.Error("a certificate in another key of the authority was taken")
|
|
}
|
|
if err := judge(Manifest{Module: "mailu"}, "root", servedRoot); err == nil {
|
|
t.Error("a certificate in another module's setting was taken")
|
|
}
|
|
if err := JudgeSettings(ca, []Layer{{From: "anchor", Values: map[string]any{"root": []any{servedRoot}}}}, false); err == nil {
|
|
t.Error("a certificate below the top of the setting was taken")
|
|
}
|
|
}
|
|
|
|
// Every character a reader takes as the end of a line is refused, not only \n and \r: vertical tab, form feed,
|
|
// NEL and the Unicode line and paragraph separators (novox/hq issue 339).
|
|
func TestEveryLineEndIsRefused(t *testing.T) {
|
|
m := Manifest{Module: "mailu"}
|
|
for _, v := range []string{"a\vb", "a\fb", "a\u0085b", "a\u2028b", "a\u2029b"} {
|
|
if err := JudgeSettings(m, []Layer{{From: "home", Values: map[string]any{"v": v}}}, false); err == nil ||
|
|
!strings.Contains(err.Error(), "line break") {
|
|
t.Errorf("%q: %v", v, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// The review's additions: the spool, the container runtimes' data, /opt, and any home's .ssh.
|
|
func TestTheRuntimesDataAndAnyHomesSSHAreTheMachinesOwn(t *testing.T) {
|
|
m := Manifest{Module: "notes", Resources: []map[string]any{{"id": "data", "type": "directory"}},
|
|
Accesses: []Access{{ID: "media"}}}
|
|
for _, path := range []string{"/var/spool", "/var/spool/cron", "/var/lib/docker", "/var/lib/docker/volumes",
|
|
"/var/lib/containers/storage", "/opt", "/opt/app", "/home/alice/.ssh", "/home/alice/.ssh/keys",
|
|
"/srv/backup/.ssh", "/root/.ssh"} {
|
|
layers := []Layer{{From: "laptop", Values: map[string]any{PlacesSetting: map[string]any{"data": path}}}}
|
|
if _, err := Places(m, layers); err == nil || !strings.Contains(err.Error(), "issue 339") {
|
|
t.Errorf("a place at %s: %v", path, err)
|
|
}
|
|
layers = []Layer{{From: "laptop", Values: map[string]any{AccessesSetting: map[string]any{"media": path}}}}
|
|
if _, err := AccessPlaces(m, layers); err == nil || !strings.Contains(err.Error(), "issue 339") {
|
|
t.Errorf("an access at %s: %v", path, err)
|
|
}
|
|
}
|
|
for _, path := range []string{"/var/lib/dockerish", "/home/alice/ssh", "/home/alice/.sshd-notes", "/storage/media"} {
|
|
layers := []Layer{{From: "laptop", Values: map[string]any{PlacesSetting: map[string]any{"data": path}}}}
|
|
if _, err := Places(m, layers); err != nil {
|
|
t.Errorf("a place at %s: %v", path, err)
|
|
}
|
|
}
|
|
}
|