github.com/damirazo/docker@v1.9.0/pkg/idtools/usergroupadd_linux.go (about) 1 package idtools 2 3 import ( 4 "fmt" 5 "os" 6 "os/exec" 7 "path/filepath" 8 "strings" 9 "syscall" 10 11 "github.com/docker/docker/pkg/system" 12 ) 13 14 // add a user and/or group to Linux /etc/passwd, /etc/group using standard 15 // Linux distribution commands: 16 // adduser --uid <id> --shell /bin/login --no-create-home --disabled-login --ingroup <groupname> <username> 17 // useradd -M -u <id> -s /bin/nologin -N -g <groupname> <username> 18 // addgroup --gid <id> <groupname> 19 // groupadd -g <id> <groupname> 20 21 const baseUID int = 10000 22 const baseGID int = 10000 23 const idMAX int = 65534 24 25 var ( 26 userCommand string 27 groupCommand string 28 29 cmdTemplates = map[string]string{ 30 "adduser": "--uid %d --shell /bin/false --no-create-home --disabled-login --ingroup %s %s", 31 "useradd": "-M -u %d -s /bin/false -N -g %s %s", 32 "addgroup": "--gid %d %s", 33 "groupadd": "-g %d %s", 34 } 35 ) 36 37 func init() { 38 // set up which commands are used for adding users/groups dependent on distro 39 if _, err := resolveBinary("adduser"); err == nil { 40 userCommand = "adduser" 41 } else if _, err := resolveBinary("useradd"); err == nil { 42 userCommand = "useradd" 43 } 44 if _, err := resolveBinary("addgroup"); err == nil { 45 groupCommand = "addgroup" 46 } else if _, err := resolveBinary("groupadd"); err == nil { 47 groupCommand = "groupadd" 48 } 49 } 50 51 func resolveBinary(binname string) (string, error) { 52 binaryPath, err := exec.LookPath(binname) 53 if err != nil { 54 return "", err 55 } 56 resolvedPath, err := filepath.EvalSymlinks(binaryPath) 57 if err != nil { 58 return "", err 59 } 60 //only return no error if the final resolved binary basename 61 //matches what was searched for 62 if filepath.Base(resolvedPath) == binname { 63 return resolvedPath, nil 64 } 65 return "", fmt.Errorf("Binary %q does not resolve to a binary of that name in $PATH (%q)", binname, resolvedPath) 66 } 67 68 // AddNamespaceRangesUser takes a name and finds an unused uid, gid pair 69 // and calls the appropriate helper function to add the group and then 70 // the user to the group in /etc/group and /etc/passwd respectively. 71 // This new user's /etc/sub{uid,gid} ranges will be used for user namespace 72 // mapping ranges in containers. 73 func AddNamespaceRangesUser(name string) (int, int, error) { 74 // Find unused uid, gid pair 75 uid, err := findUnusedUID(baseUID) 76 if err != nil { 77 return -1, -1, fmt.Errorf("Unable to find unused UID: %v", err) 78 } 79 gid, err := findUnusedGID(baseGID) 80 if err != nil { 81 return -1, -1, fmt.Errorf("Unable to find unused GID: %v", err) 82 } 83 84 // First add the group that we will use 85 if err := addGroup(name, gid); err != nil { 86 return -1, -1, fmt.Errorf("Error adding group %q: %v", name, err) 87 } 88 // Add the user as a member of the group 89 if err := addUser(name, uid, name); err != nil { 90 return -1, -1, fmt.Errorf("Error adding user %q: %v", name, err) 91 } 92 return uid, gid, nil 93 } 94 95 func addUser(userName string, uid int, groupName string) error { 96 97 if userCommand == "" { 98 return fmt.Errorf("Cannot add user; no useradd/adduser binary found") 99 } 100 args := fmt.Sprintf(cmdTemplates[userCommand], uid, groupName, userName) 101 return execAddCmd(userCommand, args) 102 } 103 104 func addGroup(groupName string, gid int) error { 105 106 if groupCommand == "" { 107 return fmt.Errorf("Cannot add group; no groupadd/addgroup binary found") 108 } 109 args := fmt.Sprintf(cmdTemplates[groupCommand], gid, groupName) 110 // only error out if the error isn't that the group already exists 111 // if the group exists then our needs are already met 112 if err := execAddCmd(groupCommand, args); err != nil && !strings.Contains(err.Error(), "already exists") { 113 return err 114 } 115 return nil 116 } 117 118 func execAddCmd(cmd, args string) error { 119 execCmd := exec.Command(cmd, strings.Split(args, " ")...) 120 out, err := execCmd.CombinedOutput() 121 if err != nil { 122 return fmt.Errorf("Failed to add user/group with error: %v; output: %q", err, string(out)) 123 } 124 return nil 125 } 126 127 func findUnusedUID(startUID int) (int, error) { 128 return findUnused("passwd", startUID) 129 } 130 131 func findUnusedGID(startGID int) (int, error) { 132 return findUnused("group", startGID) 133 } 134 135 func findUnused(file string, id int) (int, error) { 136 for { 137 cmdStr := fmt.Sprintf("cat /etc/%s | cut -d: -f3 | grep '^%d$'", file, id) 138 cmd := exec.Command("sh", "-c", cmdStr) 139 if err := cmd.Run(); err != nil { 140 // if a non-zero return code occurs, then we know the ID was not found 141 // and is usable 142 if exiterr, ok := err.(*exec.ExitError); ok { 143 // The program has exited with an exit code != 0 144 if status, ok := exiterr.Sys().(syscall.WaitStatus); ok { 145 if status.ExitStatus() == 1 { 146 //no match, we can use this ID 147 return id, nil 148 } 149 } 150 } 151 return -1, fmt.Errorf("Error looking in /etc/%s for unused ID: %v", file, err) 152 } 153 id++ 154 if id > idMAX { 155 return -1, fmt.Errorf("Maximum id in %q reached with finding unused numeric ID", file) 156 } 157 } 158 } 159 160 func mkdirAs(path string, mode os.FileMode, ownerUID, ownerGID int, mkAll bool) error { 161 if mkAll { 162 if err := system.MkdirAll(path, mode); err != nil && !os.IsExist(err) { 163 return err 164 } 165 } else { 166 if err := os.Mkdir(path, mode); err != nil && !os.IsExist(err) { 167 return err 168 } 169 } 170 // even if it existed, we will chown to change ownership as requested 171 if err := os.Chown(path, ownerUID, ownerGID); err != nil { 172 return err 173 } 174 return nil 175 }