package system import ( "context" "errors" "strings" "testing" ) // busybox has no usermod (novox/hq ADR 0328 ยง4): an account the mesh made is given its added groups with addgroup // and delgroup, and a change of its primary group is refused, saying why. func TestBusyboxGivesAnAccountItsAddedGroupsAndRefusesAnotherPrimary(t *testing.T) { var ran []string run := func(_ context.Context, name string, args ...string) (string, error) { ran = append(ran, name+" "+strings.Join(args, " ")) switch { case name == "getent" && args[0] == "passwd": return "mesh-radarr:x:61001:62000::/:/sbin/nologin\n", nil case name == "getent" && args[0] == "group": return "mesh-media:x:62000:\n", nil case name == "id": return "mesh-media mesh-old\n", nil } return "", nil } if err := (alpine{}).ModifyAccount(context.Background(), run, "mesh-radarr", 62000, []string{"mesh-books"}); err != nil { t.Fatal(err) } got := strings.Join(ran, "\n") if !strings.Contains(got, "addgroup mesh-radarr mesh-books") || !strings.Contains(got, "delgroup mesh-radarr mesh-old") || strings.Contains(got, "delgroup mesh-radarr mesh-media") { t.Fatalf("not given exactly its added groups:\n%s", got) } err := (alpine{}).ModifyAccount(context.Background(), run, "mesh-radarr", 62005, nil) if !errors.Is(err, ErrUnsupported) || !strings.Contains(err.Error(), "primary group") { t.Fatalf("a change of primary group was not refused saying why: %v", err) } } // An account is made with no home, and a change of its groups moves an old home of `/` away first: `usermod --gid` // regroups every file under the home the account owns, and under `/` that is the whole machine. func TestAnAccountsGroupsChangeOnlyWithNoHome(t *testing.T) { var ran []string home := "/" run := func(_ context.Context, name string, args ...string) (string, error) { line := name + " " + strings.Join(args, " ") ran = append(ran, line) if name == "getent" { return "mesh-radarr:x:61001:61001::" + home + ":/usr/bin/nologin\n", nil } if name == "usermod" && args[0] == "--home" { home = args[1] } if name == "usermod" && args[0] == "--gid" && home != NoHome { t.Fatalf("usermod --gid ran with the home %s: every file there would be regrouped", home) } return "", nil } if err := (arch{}).CreateSystemAccount(context.Background(), run, "mesh-radarr", 61001, 61001, nil); err != nil { t.Fatal(err) } if !strings.Contains(ran[0], "--no-create-home --home-dir "+NoHome+" ") { t.Fatalf("an account is made with a home: %s", ran[0]) } if err := (arch{}).ModifyAccount(context.Background(), run, "mesh-radarr", 62000, nil); err != nil { t.Fatal(err) } got := strings.Join(ran, "\n") if !strings.Contains(got, "usermod --home "+NoHome+" mesh-radarr") || strings.Contains(got, " -m") || strings.Contains(got, "--move-home") { t.Fatalf("the home was not moved away first, without moving files:\n%s", got) } } // busybox removes what the mesh made of an identity with deluser and delgroup. func TestBusyboxRemovesAnAccountAndAGroup(t *testing.T) { var ran []string run := func(_ context.Context, name string, args ...string) (string, error) { ran = append(ran, name+" "+strings.Join(args, " ")) return "", nil } if err := (alpine{}).RemoveAccount(context.Background(), run, "mesh-radarr"); err != nil { t.Fatal(err) } if err := (alpine{}).RemoveGroup(context.Background(), run, "mesh-media"); err != nil { t.Fatal(err) } if strings.Join(ran, "\n") != "deluser mesh-radarr\ndelgroup mesh-media" { t.Fatalf("removed with: %v", ran) } }