Files
mesh-sdk/go/outward/outward_test.go
T
jochen 7bf4a8053a
mesh/repo-check head 7bf4a8053a49 merged into trunk 36d6f7745c8d — pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (typescript)…
mesh/merge-gate head 7bf4a8053a49 merged into trunk 36d6f7745c8d — pass: group feat/1439-name-runs composed: every machine composes with the change as it d…
mesh/delivery delivered
mesh/delivery-group group feat/1439-name-runs delivered: every member is delivered
Read a lowercase name with digits as a name, not a secret (issue 1439)
mesh-config refused to propose mesh-issues' owners because CheckSecret took
modules/asus-zephyrus-g14, i3status-rust and powerlevel10k for random strings: a run with a
digit in it was judged whole. A run of lowercase letters and digits in short pieces between
-, _ and / is now judged piece by piece; a capital letter or a piece of 20 or more is judged
whole as before.
2026-10-21 09:55:04 +02:00

210 lines
8.3 KiB
Go

package outward
// The operator channel's cases, carried here with the rule (novox/hq ADR 0310 §4): the rule moved, its meaning did not.
import (
"encoding/base64"
"encoding/json"
"fmt"
"math/rand"
"os"
"path/filepath"
"testing"
)
func TestRolesAndWordsMayLeave(t *testing.T) {
for _, s := range []string{
"URGENT: the home server has not been heard for 15 min",
"key: provider.keycloak.ace.gitea.failing",
"more: conditions show machine.ace.silent",
"since: 2026-10-06 12:30 UTC",
"call call-1759752000123456789-12 has run past its bound of 10 min",
"ace/postgres.query answers no more",
"3/4 machines answered; plan 41 waits on build 7f3a9c1e",
"the bus advisory slow-consumer for ace_records",
"core.controller.novox.rolled-back",
"seat node-notifier has no live holder",
"HELD BACK: 5 message(s) over the cap of 20 an hour",
"STILL OPEN after 1.5 h: the controller's event loop takes no message",
"node-engine and node tools builds differ from the plan's",
} {
if r, ok := Check(s); !ok {
t.Errorf("refused %q: %s", s, r)
}
}
}
func TestAnAddressAPathOrASecretMayNot(t *testing.T) {
for s, class := range map[string]string{
"cannot reach 192.168.1.20": "address",
"listening on 10.0.0.7:5432": "address",
"route fd00:1234:5678::1 is gone": "address",
"fe80::1 answered": "address",
"2001:db8:0:0:0:0:2:1 answered": "address",
"see https://git.example.org/x": "address",
"git.novox.internal does not answer": "address",
"the mail for admin@example.org bounced": "address",
"zurag.be is down": "address",
"localhost refused": "address",
"the card aa:bb:cc:dd:ee:ff went away": "address",
"/var/lib/mesh-controller is full": "path",
"~/.config/hal/env changed": "path",
"read ./grants.json": "path",
"services/postgres/data/pg_hba.conf": "path",
"C:\\Users\\x": "path",
"token 123456789:AAHdqTcvCH1vGWJxfSeofSAs0K5PALDsaw": "secret",
"ghp_abcdefghijklmnopqrstuvwxyz0123456789": "secret",
"password=hunter2": "secret",
"api_key: x": "secret",
"-----BEGIN OPENSSH PRIVATE KEY-----": "secret",
"eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0": "secret",
"commit 3f2a9c1e5b7d4f6a8c0e2b4d6f8a0c2e4b6d8f0a": "secret",
"value Zx8Qp2Lm9Rt4Vb7Nc1Kd5Hs3Jf6": "secret",
} {
r, ok := Check(s)
if ok {
t.Errorf("let %q leave", s)
continue
}
if r.Class != class {
t.Errorf("%q refused as %s, want %s", s, r, class)
}
}
}
// The secret class alone (novox/hq ADR 0310 §4, issue 383): a path and an address pass, a path's pieces are
// judged between its slashes, and every named secret shape and a random run outside a path are refused.
func TestCheckSecretRefusesASecretsShapeAndNothingElse(t *testing.T) {
for text, may := range map[string]bool{
"+ shares: photos=/mnt/Photos_2024/Jochen": true,
"+ sources: games=smb://nas.lan/Recalbox_Games2024": true,
"+ library: /srv/media/Series_Archive/Season01": true,
"root:x:0:0::/root:/usr/bin/bash": true,
"nameserver 192.168.1.1": true,
"drwxr-x--- jochen users /home/jochen": true,
"+ key: xK9mQ2vL8pR4tW7yB3nF6hJ1dG5sA0zC": false,
"-----BEGIN OPENSSH PRIVATE KEY-----": false,
"password=hunter2": false,
"Authorization: token ghp_abcdefghijklmnopqrstuvwxyz0123456789": false,
"eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0": false,
"sha 3f2a9c1e5b7d4f6a8c0e2b4d6f8a0c2e4b6d8f0a": false,
} {
r, ok := CheckSecret(text)
if ok != may {
t.Errorf("CheckSecret(%q) = %v (%s), want %v", text, ok, r, may)
}
if !ok && r.Class != "secret" {
t.Errorf("CheckSecret(%q) refused as %s: only the secret class is refused there", text, r)
}
}
}
// The review of mesh-sdk #23: the shapes node-reads meets in env files, unit files, configurations and
// history files, each refused by its shape whatever the value looks like.
func TestCheckSecretRefusesTheShapesOfConfigurationFiles(t *testing.T) {
for _, text := range []string{
"url = nats://user:pa55word@host:4222",
"DATABASE_URL=postgres://app:hunter2@db/app",
"MESH_TOKEN=s3cr3t-v4lue",
"DB_PASS=hunter2",
"export GITEA_SECRET=abc",
"Environment=APP_API_KEY=xyz",
"SUDO_PASSWORD hunter2",
"aws_secret_access_key = wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
"password = hunter2",
"wifi_psk: correcthorse",
"value: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
} {
if r, ok := CheckSecret(text); ok || r.Class != "secret" {
t.Errorf("CheckSecret(%q) let it through (%v)", text, r)
}
}
// What a configuration file says that is not a secret.
for _, text := range []string{
"ExecStart=/usr/lib/systemd/systemd-resolved",
"UUID=3f2a9c1e-5b7d-4f6a-8c0e-2b4d6f8a0c2e /home btrfs defaults 0 0",
"password required pam_unix.so",
"bypass = true",
"PasswordAuthentication no",
"url = https://git.example.org/novox/mesh-sdk.git",
"HOME=/var/lib/mesh-reads",
"TOKEN_FILE=/var/lib/app/token",
} {
if r, ok := CheckSecret(text); !ok {
t.Errorf("CheckSecret(%q) withheld it: %s", text, r)
}
}
}
// Random values as base64 and base64url, wherever their slashes fall: every one is refused.
func TestCheckSecretRefusesRandomValuesAsBase64(t *testing.T) {
rng := rand.New(rand.NewSource(418))
passed := map[string]int{}
for i := 0; i < 1000; i++ {
for _, n := range []int{16, 32} {
b := make([]byte, n)
rng.Read(b)
for name, enc := range map[string]*base64.Encoding{"std": base64.StdEncoding, "url": base64.RawURLEncoding} {
v := enc.EncodeToString(b)
if _, ok := CheckSecret("key " + v); ok {
passed[fmt.Sprintf("%s/%d", name, n)]++
}
}
}
}
if len(passed) > 0 {
t.Errorf("random values passed as clean, of 1000 each: %v", passed)
}
}
// A CamelCase name is a word, not a value: the case changes once a word.
func TestCheckSecretLetsACamelCaseNameThrough(t *testing.T) {
for _, text := range []string{"PasswordAuthenticationIsOff", "ChallengeResponseAuthentication no", "KbdInteractiveAuthentication"} {
if r, ok := CheckSecret(text); !ok {
t.Errorf("CheckSecret(%q) withheld it: %s", text, r)
}
}
}
// 1439/AC1: a name written in lowercase words and digits between `-`, `_` or `/`, every piece short, is a
// name and not a secret: mesh-issues' owners list (the catalogue's 134 defaults plus okioki) was refused
// whole as "a long random-looking string" for modules/asus-zephyrus-g14, i3status-rust and powerlevel10k.
func TestCheckSecretLetsALowercaseNameWithDigitsThrough(t *testing.T) {
for _, text := range []string{
"mesh-catalog modules/asus-zephyrus-g14",
"mesh-catalog modules/i3status-rust",
"mesh-catalog modules/powerlevel10k",
} {
if r, ok := CheckSecret(text); !ok {
t.Errorf("CheckSecret(%q) withheld it: %s", text, r)
}
}
raw, err := os.ReadFile(filepath.Join("testdata", "owners-1439.json"))
if err != nil {
t.Fatal(err)
}
var owners []string
if err := json.Unmarshal(raw, &owners); err != nil || len(owners) != 135 {
t.Fatalf("testdata holds %d owners (%v), want the 134 defaults plus okioki", len(owners), err)
}
compact, _ := json.Marshal(owners)
if r, ok := CheckSecret("+ owners: " + string(compact)); !ok {
t.Errorf("CheckSecret withheld the owners list: %s", r)
}
}
// 1439/AC2: what looks random is still refused: a mixed-case run, a base64url token with `-` and `_`, a
// lowercase run of letters and digits with no separator, and a lowercase name with one long random piece.
func TestCheckSecretStillRefusesRandomRunsBesideNames(t *testing.T) {
for _, text := range []string{
"key Zq8vR2mXk4Lp9sTw3nBy7cHd",
"token dGhpc0lz-QVNlY3JldC1_Ub2tlbl9XaXRoLUh5cGhlbnM",
"key k8s7x2m9q4w1e5r3t6y0u2p9",
"modules/k8s7x2m9q4w1e5r3t6y0u2p9-tools",
} {
if r, ok := CheckSecret(text); ok || r.Class != "secret" {
t.Errorf("CheckSecret(%q) let it through (%v)", text, r)
}
}
}