From 6a6d30af1f15b0aa1b79174ecb08b59bd77dabb0 Mon Sep 17 00:00:00 2001 From: Ophestra Umiker Date: Sun, 17 Nov 2024 02:03:18 +0900 Subject: [PATCH] cmd/fuserdb: systemd userdb drop-in entries generator This provides user records via nss-systemd. Static drop-in entries are generated to reduce complexity and attack surface. Signed-off-by: Ophestra Umiker --- cmd/fuserdb/main.go | 95 +++++++++++++++++++++++++++++++++++++++++++++ package.nix | 7 ++-- 2 files changed, 99 insertions(+), 3 deletions(-) create mode 100644 cmd/fuserdb/main.go diff --git a/cmd/fuserdb/main.go b/cmd/fuserdb/main.go new file mode 100644 index 0000000..9283d82 --- /dev/null +++ b/cmd/fuserdb/main.go @@ -0,0 +1,95 @@ +package main + +import ( + "bytes" + "encoding/json" + "errors" + "flag" + "fmt" + "os" + "path" + "strconv" + + "git.ophivana.moe/security/fortify/internal/fmsg" +) + +func main() { + fmsg.SetPrefix("fuserdb") + + const varEmpty = "/var/empty" + + out := flag.String("o", "userdb", "output directory") + homeDir := flag.String("d", varEmpty, "parent of home directories") + shell := flag.String("s", "/sbin/nologin", "absolute path to subordinate user shell") + flag.Parse() + + type user struct { + name string + fid int + } + + users := make([]user, len(flag.Args())) + for i, s := range flag.Args() { + f := bytes.SplitN([]byte(s), []byte{':'}, 2) + if len(f) != 2 { + fmsg.Fatalf("invalid entry at index %d", i) + } + users[i].name = string(f[0]) + if fid, err := strconv.Atoi(string(f[1])); err != nil { + fmsg.Fatal(err.Error()) + } else { + users[i].fid = fid + } + } + + if err := os.MkdirAll(*out, 0755); err != nil && !errors.Is(err, os.ErrExist) { + fmsg.Fatalf("cannot create output: %v", err) + } + + type payload struct { + UserName string `json:"userName"` + Uid int `json:"uid"` + Gid int `json:"gid"` + RealName string `json:"realName"` + HomeDirectory string `json:"homeDirectory"` + Shell string `json:"shell"` + } + + for _, u := range users { + fidString := strconv.Itoa(u.fid) + for aid := 0; aid < 9999; aid++ { + userName := fmt.Sprintf("u%d_a%d", u.fid, aid) + uid := 1000000 + u.fid*10000 + aid + us := strconv.Itoa(uid) + realName := fmt.Sprintf("Fortify subordinate user %d (%s)", aid, u.name) + var homeDirectory string + if *homeDir != varEmpty { + homeDirectory = path.Join(*homeDir, fidString, strconv.Itoa(aid)) + } else { + homeDirectory = varEmpty + } + + fileName := userName + ".user" + if f, err := os.OpenFile(path.Join(*out, fileName), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644); err != nil { + fmsg.Fatalf("cannot create %s: %v", userName, err) + } else if err = json.NewEncoder(f).Encode(&payload{ + UserName: userName, + Uid: uid, + Gid: uid, + RealName: realName, + HomeDirectory: homeDirectory, + Shell: *shell, + }); err != nil { + fmsg.Fatalf("cannot serialise %s: %v", userName, err) + } else if err = f.Close(); err != nil { + fmsg.Printf("cannot close %s: %v", userName, err) + } + if err := os.Symlink(fileName, path.Join(*out, us+".user")); err != nil { + fmsg.Fatalf("cannot link %s: %v", userName, err) + } + } + } + + fmsg.Printf("created %d entries", len(users)*10000) + fmsg.Exit(0) +} diff --git a/package.nix b/package.nix index ddc9d48..d510c64 100644 --- a/package.nix +++ b/package.nix @@ -31,12 +31,12 @@ buildGoModule rec { "-X" "main.Fmain=${placeholder "out"}/bin/.fortify-wrapped" "-X" - "main.Fshim=${placeholder "out"}/bin/fshim" + "main.Fshim=${placeholder "out"}/libexec/fshim" ] { Version = "v${version}"; Fsu = "/run/wrappers/bin/fsu"; - Finit = "${placeholder "out"}/bin/finit"; + Finit = "${placeholder "out"}/libexec/finit"; }; buildInputs = [ @@ -54,6 +54,7 @@ buildGoModule rec { ] } - mv $out/bin/fsu $out/bin/.fsu + mkdir $out/libexec + (cd $out/bin && mv fsu fshim finit fuserdb ../libexec/) ''; }