82 lines
3.0 KiB
Go
82 lines
3.0 KiB
Go
package main
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// Every script in scripts/ that starts a bastion in namespaces of its own
|
|
// hides ALL of /run and /tmp, never a chosen subdirectory of them, and refuses
|
|
// to go on while it can still see the machine's own bastion. A run that put a
|
|
// tmpfs over /run/privatae but not /run/privatae-bastion replaced a live
|
|
// bastion's ask.sock and context.sock on the machine running the tests.
|
|
//
|
|
// MUTATION: the e2e script's /run mount back to /run/privatae alone. RESULT: red.
|
|
// MUTATION: drop its guard for /run/privatae-bastion. RESULT: red.
|
|
// MUTATION: move the guard after the first start of the bastion. RESULT: red.
|
|
// MUTATION: drop the PID 1 check before the first mount. RESULT: red.
|
|
func TestNamespaceScriptsHideAllOfRunAndTmpAndRefuseToSeeTheLiveBastion(t *testing.T) {
|
|
paths, err := filepath.Glob(filepath.Join("..", "scripts", "*.sh"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
n := 0
|
|
for _, p := range paths {
|
|
raw, err := os.ReadFile(p)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
s := string(raw)
|
|
if !strings.Contains(s, "unshare") {
|
|
continue
|
|
}
|
|
n++
|
|
name := filepath.Base(p)
|
|
lines := strings.Split(s, "\n")
|
|
at := func(re string) int {
|
|
r := regexp.MustCompile(re)
|
|
for i, l := range lines {
|
|
if strings.HasPrefix(strings.TrimSpace(l), "#") {
|
|
continue
|
|
}
|
|
if r.MatchString(l) {
|
|
return i
|
|
}
|
|
}
|
|
return -1
|
|
}
|
|
runAt := at(`mount -t tmpfs tmpfs /run\s*$`)
|
|
tmpAt := at(`mount -t tmpfs tmpfs /tmp\s*$`)
|
|
if runAt < 0 || tmpAt < 0 {
|
|
t.Errorf("%s: no tmpfs over all of /run (%d) and /tmp (%d)", name, runAt, tmpAt)
|
|
}
|
|
if i := at(`mount -t tmpfs tmpfs /(run|tmp)/`); i >= 0 && (runAt < 0 || i < runAt) {
|
|
t.Errorf("%s:%d hides a subdirectory of /run or /tmp instead of all of it: %s", name, i+1, lines[i])
|
|
}
|
|
guard := at(`/run/privatae-bastion`)
|
|
if guard < 0 || guard < runAt || guard < tmpAt {
|
|
t.Errorf("%s: no guard, after hiding /run and /tmp, that refuses to run while /run/privatae-bastion is visible", name)
|
|
}
|
|
if i := at(`"\$B"|privatae-bastion" (run|pair)|\$BAST`); i >= 0 && guard > i {
|
|
t.Errorf("%s:%d starts a bastion before the guard", name, i+1)
|
|
}
|
|
if at(`unshare .*--pid`) < 0 || at(`unshare .*--mount`) < 0 || at(`unshare .*--net`) < 0 {
|
|
t.Errorf("%s: a bastion runs in private PID, mount and network namespaces", name)
|
|
}
|
|
// The re-exec into the namespaces is chosen by an environment variable
|
|
// (E2E_INNER): one set by the caller skips the unshare, and every mount
|
|
// below then lands on this machine's own /run. So before any mount the
|
|
// script proves it is PID 1 of a namespace of its own.
|
|
pid1 := at(`\[ "?\$\$"? (=|-eq) "?1"? \]`)
|
|
if m := at(`^\s*(mount|mkdir -p [^;&]*&& mount) `); pid1 < 0 || (m >= 0 && pid1 > m) {
|
|
t.Errorf("%s: no check, before its first mount (line %d), that it runs as PID 1 of its own namespace (line %d)", name, m+1, pid1+1)
|
|
}
|
|
}
|
|
if n == 0 {
|
|
t.Fatal("no namespace script found: the glob is wrong")
|
|
}
|
|
}
|