From e0d57f585942c26260a09f65fe9078975154f576 Mon Sep 17 00:00:00 2001 From: Sohan Kunkerkar Date: Wed, 1 Jul 2020 18:14:05 -0400 Subject: [PATCH] passwd: allow removing the existing users/groups Fixes https://github.com/coreos/ignition/issues/738 Some users may want to add a custom user, and remove the `core` user injected via the default configuration. This PR will enable users to delete the existing users/groups in a given distro. --- internal/distro/distro.go | 4 ++ internal/exec/stages/files/passwd.go | 33 +++++++++------ internal/exec/util/passwd.go | 61 +++++++++++++++++++++++++--- 3 files changed, 81 insertions(+), 17 deletions(-) diff --git a/internal/distro/distro.go b/internal/distro/distro.go index 3364715757..a8b3ddbfab 100644 --- a/internal/distro/distro.go +++ b/internal/distro/distro.go @@ -34,6 +34,7 @@ var ( // Helper programs groupaddCmd = "groupadd" + groupdelCmd = "groupdel" mdadmCmd = "mdadm" mountCmd = "mount" sgdiskCmd = "sgdisk" @@ -41,6 +42,7 @@ var ( udevadmCmd = "udevadm" usermodCmd = "usermod" useraddCmd = "useradd" + userdelCmd = "userdel" setfilesCmd = "setfiles" wipefsCmd = "wipefs" @@ -81,6 +83,7 @@ func KernelCmdlinePath() string { return kernelCmdlinePath } func SystemConfigDir() string { return fromEnv("SYSTEM_CONFIG_DIR", systemConfigDir) } func GroupaddCmd() string { return groupaddCmd } +func GroupdelCmd() string { return groupdelCmd } func MdadmCmd() string { return mdadmCmd } func MountCmd() string { return mountCmd } func SgdiskCmd() string { return sgdiskCmd } @@ -88,6 +91,7 @@ func ModprobeCmd() string { return modprobeCmd } func UdevadmCmd() string { return udevadmCmd } func UsermodCmd() string { return usermodCmd } func UseraddCmd() string { return useraddCmd } +func UserdelCmd() string { return userdelCmd } func SetfilesCmd() string { return setfilesCmd } func WipefsCmd() string { return wipefsCmd } diff --git a/internal/exec/stages/files/passwd.go b/internal/exec/stages/files/passwd.go index 7e4263faa4..927ff0b36a 100644 --- a/internal/exec/stages/files/passwd.go +++ b/internal/exec/stages/files/passwd.go @@ -41,12 +41,12 @@ func (s *stage) expandGlobList(globs ...string) ([]string, error) { // createPasswd creates the users and groups as described in config.Passwd. func (s *stage) createPasswd(config types.Config) error { - if err := s.createGroups(config); err != nil { - return fmt.Errorf("failed to create groups: %v", err) + if err := s.ensureGroups(config); err != nil { + return fmt.Errorf("failed to configure groups: %v", err) } - if err := s.createUsers(config); err != nil { - return fmt.Errorf("failed to create users: %v", err) + if err := s.ensureUsers(config); err != nil { + return fmt.Errorf("failed to configure users: %v", err) } // to be safe, just blanket mark all passwd-related files rather than @@ -68,7 +68,10 @@ func (s *stage) createPasswd(config types.Config) error { s.relabel(deglobbed...) s.relabel("/etc/.pwd.lock") for _, user := range config.Passwd.Users { - if user.NoCreateHome != nil && *user.NoCreateHome == true { + if user.NoCreateHome != nil && *user.NoCreateHome { + continue + } + if user.ShouldExist != nil && !*user.ShouldExist { continue } homedir, err := s.GetUserHomeDir(user) @@ -94,18 +97,23 @@ func (s *stage) createPasswd(config types.Config) error { return nil } -// createUsers creates the users as described in config.Passwd.Users. -func (s stage) createUsers(config types.Config) error { +// ensureUsers ensures that users match the state described +// in config.Passwd.Users. +func (s stage) ensureUsers(config types.Config) error { if len(config.Passwd.Users) == 0 { return nil } - s.Logger.PushPrefix("createUsers") + s.Logger.PushPrefix("ensureUsers") defer s.Logger.PopPrefix() for _, u := range config.Passwd.Users { if err := s.EnsureUser(u); err != nil { return fmt.Errorf("failed to create user %q: %v", u.Name, err) + } else if err == nil { + if u.ShouldExist != nil && !*u.ShouldExist { + continue + } } if err := s.SetPasswordHash(u); err != nil { @@ -122,16 +130,17 @@ func (s stage) createUsers(config types.Config) error { return nil } -// createGroups creates the users as described in config.Passwd.Groups. -func (s stage) createGroups(config types.Config) error { +// ensureGroups ensures that groups match the state described +// in config.Passwd.Groups. +func (s stage) ensureGroups(config types.Config) error { if len(config.Passwd.Groups) == 0 { return nil } - s.Logger.PushPrefix("createGroups") + s.Logger.PushPrefix("ensureGroups") defer s.Logger.PopPrefix() for _, g := range config.Passwd.Groups { - if err := s.CreateGroup(g); err != nil { + if err := s.EnsureGroup(g); err != nil { return fmt.Errorf("failed to create group %q: %v", g.Name, err) } diff --git a/internal/exec/util/passwd.go b/internal/exec/util/passwd.go index 30b03ac4bd..6866cec7e7 100644 --- a/internal/exec/util/passwd.go +++ b/internal/exec/util/passwd.go @@ -50,13 +50,30 @@ func appendIfStringSet(args []string, arg string, str *string) []string { } // EnsureUser ensures that the user exists as described. If the user does not -// yet exist, they will be created, otherwise the existing user will be -// modified. +// yet exist, they will be created, otherwise the existing user will be modified. +// If the `shouldExist` field is set to false and the user already exists, then +// they will be deleted. func (u Util) EnsureUser(c types.PasswdUser) error { + shouldExist := c.ShouldExist == nil || *c.ShouldExist exists, err := u.CheckIfUserExists(c) if err != nil { return err } + if !shouldExist && exists { + args := []string{"--remove", "--root", u.DestDir, c.Name} + _, err := u.LogCmd(exec.Command(distro.UserdelCmd(), args...), + "deleting user %q", c.Name) + if err != nil { + return fmt.Errorf("failed to delete user %q: %v", + c.Name, err) + } + return nil + } + + if !shouldExist && !exists { + return nil + } + args := []string{"--root", u.DestDir} var cmd string @@ -244,8 +261,30 @@ func (u Util) SetPasswordHash(c types.PasswdUser) error { return err } -// CreateGroup creates the group as described. -func (u Util) CreateGroup(g types.PasswdGroup) error { +// EnsureGroup ensures that the group exists as described. If the +// `shouldExist` field is set to false and the group already exists, +// then it will be deleted. +func (u Util) EnsureGroup(g types.PasswdGroup) error { + shouldExist := g.ShouldExist == nil || *g.ShouldExist + exists, err := u.CheckIfGroupExists(g) + if err != nil { + return err + } + if !shouldExist && exists { + args := []string{"--root", u.DestDir, g.Name} + _, err := u.LogCmd(exec.Command(distro.GroupdelCmd(), args...), + "deleting group %q", g.Name) + if err != nil { + return fmt.Errorf("failed to delete group %q: %v", + g.Name, err) + } + return nil + } + + if !shouldExist && !exists { + return nil + } + args := []string{"--root", u.DestDir} if g.Gid != nil { @@ -263,7 +302,19 @@ func (u Util) CreateGroup(g types.PasswdGroup) error { args = append(args, g.Name) - _, err := u.LogCmd(exec.Command(distro.GroupaddCmd(), args...), + _, err = u.LogCmd(exec.Command(distro.GroupaddCmd(), args...), "adding group %q", g.Name) return err } + +// CheckIfGroupExists will return Info log when group is empty +func (u Util) CheckIfGroupExists(g types.PasswdGroup) (bool, error) { + _, err := u.groupLookup(g.Name) + if _, ok := err.(user.UnknownGroupError); ok { + return false, nil + } + if err != nil { + return false, err + } + return true, nil +}