package apply import ( "os" "path/filepath" "strings" "testing" "git.novox.be/novox/mesh-sdk/go/declaration" ) // A module that declares its own system account and a .ssh in that account's home is placed (novox/hq issue 1542): // the account is not in the user database yet when the declaration is judged, and its home is under /var/lib. func declaredHomeFixture(t *testing.T) (home string, user *declaration.User) { t.Helper() dir := t.TempDir() passwd := filepath.Join(dir, "passwd") if err := os.WriteFile(passwd, []byte("root:x:0:0::/root:/bin/sh\n"), 0o644); err != nil { t.Fatal(err) } before := passwdFile passwdFile = passwd t.Cleanup(func() { passwdFile = before }) home = filepath.Join(dir, "lib", "mesh-build-go") return home, &declaration.User{ID: "mesh-build-go.account", Type: declaration.TypeUser, Name: "mesh-build-go", Shell: "/usr/bin/nologin", Home: home} } // 1542/AC1: the .ssh of a declared account, owned by it, is refused by nothing. func TestIssue1542ADeclaredAccountsSSHIsPlaced(t *testing.T) { home, user := declaredHomeFixture(t) ssh := &declaration.Directory{ID: "mesh-build-go.ssh-dir", Type: declaration.TypeDirectory, Path: home + "/.ssh", Mode: "0700", Owner: "mesh-build-go"} refused := refusedPlaces([]declaration.Resource{user, ssh}) if len(refused) != 0 { t.Fatalf("a declared account's own .ssh was refused: %v", refused) } if err := (inputs{refused: refused}).refused[filepath.Clean(ssh.Path)]; err != nil { t.Fatalf("the apply-time check refuses it: %v", err) } if line := JudgeDirectories(&declaration.Declaration{Resources: []declaration.Resource{user, ssh}}); strings.Contains(line, "refused already by the placement guard: ") { t.Fatalf("the read-only judgement refuses it: %s", line) } } // 1542/AC2: another account's directory below the declared home is refused as below any home, and a .ssh below // no home of the machine or the declaration is refused with today's words. func TestIssue1542OnlyTheDeclaredAccountsOwnIsPlaced(t *testing.T) { home, user := declaredHomeFixture(t) other := &declaration.Directory{ID: "notes.ssh-dir", Type: declaration.TypeDirectory, Path: home + "/.ssh", Mode: "0700", Owner: "intruder"} refused := refusedPlaces([]declaration.Resource{user, other}) err := refused[filepath.Clean(other.Path)] if err == nil || !strings.Contains(err.Error(), "below mesh-build-go's home and declared intruder's") { t.Fatalf("another account's directory below a declared home: %v", err) } stray := &declaration.Directory{ID: "mesh-build-go.ssh-dir", Type: declaration.TypeDirectory, Path: filepath.Join(filepath.Dir(home), "elsewhere", ".ssh"), Mode: "0700", Owner: "mesh-build-go"} refused = refusedPlaces([]declaration.Resource{user, stray}) err = refused[filepath.Clean(stray.Path)] if err == nil || !strings.Contains(err.Error(), "a .ssh directory is placed only below its account's home, as that account's") { t.Fatalf("a .ssh below no home: %v", err) } // Not a .ssh: a root-owned directory below the declared home is placed exactly as on main. data := &declaration.Directory{ID: "mesh-build-go.data", Type: declaration.TypeDirectory, Path: home + "/data", Mode: "0755", Owner: "root"} if refused := refusedPlaces([]declaration.Resource{user, data}); len(refused) != 0 { t.Fatalf("a root-owned directory below a declared home, not a .ssh, was refused: %v", refused) } // And without the account declared, the same .ssh is refused as it was. own := &declaration.Directory{ID: "mesh-build-go.ssh-dir", Type: declaration.TypeDirectory, Path: home + "/.ssh", Mode: "0700", Owner: "mesh-build-go"} if refusedPlaces([]declaration.Resource{own})[filepath.Clean(own.Path)] == nil { t.Fatal("a .ssh below an account nobody makes was placed") } } // 1542/AC3: a declared account is no home to anyone but the directory placement guard. func TestIssue1542DeclaredAccountsAreNotHomesToTheOtherGuards(t *testing.T) { home, user := declaredHomeFixture(t) _ = refusedPlaces([]declaration.Resource{user}) if _, ok := accountsOfHomes()[home]; ok { t.Fatal("a declared account became a home of the machine") } if homeAbove(home+"/.ssh") != "" { t.Fatal("a declared account became a home for the links and the move-out") } } // A declared account is added as a home and never takes one over (reviewer, issue 1542): the person's own .ssh // stays refused to every other account, whatever a declaration says about a user. func TestIssue1542ADeclaredAccountTakesNoExistingHome(t *testing.T) { dir := t.TempDir() passwd := filepath.Join(dir, "passwd") body := "root:x:0:0::/root:/bin/sh\njochen:x:1000:1000::/home/jochen:/bin/sh\n" + "svc:x:998:998::/var/lib/svc:/usr/bin/nologin\n" if err := os.WriteFile(passwd, []byte(body), 0o644); err != nil { t.Fatal(err) } before := passwdFile passwdFile = passwd t.Cleanup(func() { passwdFile = before }) newHome := filepath.Join(dir, "lib", "mesh-build-go") ssh := func(path, owner string) *declaration.Directory { return &declaration.Directory{ID: "x.ssh", Type: declaration.TypeDirectory, Path: path, Mode: "0700", Owner: owner} } user := func(name, home string) *declaration.User { return &declaration.User{ID: "x.user", Type: declaration.TypeUser, Name: name, Home: home} } // A declared user over a person's home, whoever it is named for. for _, name := range []string{"evil", "postgres", "root"} { d := ssh("/home/jochen/.ssh", name) if refusedPlaces([]declaration.Resource{user(name, "/home/jochen"), d})[d.Path] == nil { t.Errorf("%s, declared over jochen's home, was given jochen's .ssh", name) } } // A declared home below a person's home. d := ssh("/home/jochen/sub/.ssh", "evil") if refusedPlaces([]declaration.Resource{user("evil", "/home/jochen/sub"), d})[d.Path] == nil { t.Error("a home declared below jochen's home was counted") } // A name the machine has already, declared with another home: that is not its home. d = ssh(newHome+"/e/.ssh", "svc") if refusedPlaces([]declaration.Resource{user("svc", newHome+"/e"), d})[d.Path] == nil { t.Error("an existing account's declared home was counted") } // A number is no name: chown -1 leaves the owner as it was. for _, owner := range []string{"-1", "-1:0", "0", "1001"} { d := ssh(newHome+"/.ssh", owner) if refusedPlaces([]declaration.Resource{user("mesh-build-go", newHome), d})[d.Path] == nil { t.Errorf("owner %q was placed below a declared home", owner) } } // And the account that is new, by name, still is. d = ssh(newHome+"/.ssh", "mesh-build-go") if got := refusedPlaces([]declaration.Resource{user("mesh-build-go", newHome), d}); len(got) != 0 { t.Errorf("a new account's own .ssh was refused: %v", got) } } // The account the mesh made, as declared, is judged the same at the next apply (issue 1542): once it is in the // user database with exactly the declared home, its .ssh is still placed. func TestIssue1542TheAccountTheMeshMadeIsStillPlacedAtTheNextApply(t *testing.T) { home, user := declaredHomeFixture(t) ssh := &declaration.Directory{ID: "mesh-build-go.ssh-dir", Type: declaration.TypeDirectory, Path: home + "/.ssh", Mode: "0700", Owner: "mesh-build-go"} resources := []declaration.Resource{user, ssh} if got := refusedPlaces(resources); len(got) != 0 { t.Fatalf("first apply, the account not yet made: %v", got) } body := "root:x:0:0::/root:/bin/sh\nmesh-build-go:x:998:998::" + home + ":/usr/bin/nologin\n" if err := os.WriteFile(passwdFile, []byte(body), 0o644); err != nil { t.Fatal(err) } if got := refusedPlaces(resources); len(got) != 0 { t.Fatalf("second apply, the account made as declared: %v", got) } }