114 lines
6.0 KiB
Go
114 lines
6.0 KiB
Go
package catalogue
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// 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)
|
|
}
|
|
}
|
|
}
|
|
|
|
// What a provider serves its consumers is the terminal's (novox/hq issue 339): any key under its `serves`, and any
|
|
// setting a served value asks for, is set at the terminal alone — a verb that could change a port could point every
|
|
// consumer at a listener of the caller's own. So is any setting a file marked `trusted` asks for.
|
|
func TestTerminalKeysAreDerived(t *testing.T) {
|
|
postgres := Manifest{Module: "postgres", Serves: map[string]map[string]any{"postgres-database": {"port": 5432.0}}}
|
|
keycloak := Manifest{Module: "keycloak", Serves: map[string]map[string]any{"oidc-client": {
|
|
"issuer": "${setting:issuer}", "token-path": "/protocol/openid-connect/token"}}}
|
|
power := Manifest{Module: "power", Resources: []map[string]any{
|
|
{"id": "logind", "type": "file", "path": "/etc/systemd/logind.conf.d/power.conf", "trusted": true,
|
|
"content": "HandleLidSwitch=${setting:handle-lid-switch}\n"},
|
|
{"id": "note", "type": "file", "path": "/var/lib/power/note", "trusted": false, "content": "${setting:greeting}\n"},
|
|
// Unmarked counts as trusted (fail closed): only `"trusted": false` lets a verb change what a file asks for.
|
|
{"id": "unmarked", "type": "file", "path": "/etc/power/unmarked", "content": "${setting:unmarked}\n"}}}
|
|
agent := Manifest{Module: "claude-code", Resources: []map[string]any{{"id": "settings", "type": "file",
|
|
"path": "/var/lib/agent/settings.json", "merge": MergeJSON,
|
|
"content": "{\n \"role\": \"\",\n \"mcp_servers\": {},\n \"managed_settings\": {}\n}\n"}}}
|
|
for _, c := range []struct {
|
|
m Manifest
|
|
want string
|
|
}{
|
|
{postgres, "places,accesses,port"},
|
|
{keycloak, "places,accesses,issuer,token-path"},
|
|
{power, "places,accesses,handle-lid-switch,unmarked"},
|
|
{Manifest{Module: "plain"}, "places,accesses"},
|
|
// A mergeable file asks for every key its own content names (novox/hq issue 340): the agent's module keeps
|
|
// what every Claude Code session on a node obeys in one.
|
|
{agent, "places,accesses,managed_settings,mcp_servers,role"},
|
|
{Manifest{Module: "notifier", Resources: []map[string]any{{"id": "look", "type": "file", "path": "/var/lib/n/look.json",
|
|
"merge": MergeJSON, "trusted": false, "content": `{"font": 13}`}}}, "places,accesses"},
|
|
{Manifest{Module: "empty", Resources: []map[string]any{{"id": "config", "type": "file", "path": "/var/lib/e/c.json",
|
|
"merge": MergeJSON, "content": `{}`}}}, "places,accesses"},
|
|
} {
|
|
if got := strings.Join(TerminalKeys(c.m), ","); got != c.want {
|
|
t.Errorf("%s: %s; want %s", c.m.Module, got, c.want)
|
|
}
|
|
}
|
|
}
|