Add utility/support package for user namespace support
The `pkg/idtools` package supports the creation of user(s) for retrieving /etc/sub{u,g}id ranges and creation of the UID/GID mappings provided to clone() to add support for user namespaces in Docker. Docker-DCO-1.1-Signed-off-by: Phil Estes <estesp@linux.vnet.ibm.com> (github: estesp)
This commit is contained in:
parent
dade93d00f
commit
5e8012caca
3 changed files with 374 additions and 0 deletions
155
idtools/usergroupadd_linux.go
Normal file
155
idtools/usergroupadd_linux.go
Normal file
|
@ -0,0 +1,155 @@
|
|||
package idtools
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// add a user and/or group to Linux /etc/passwd, /etc/group using standard
|
||||
// Linux distribution commands:
|
||||
// adduser --uid <id> --shell /bin/login --no-create-home --disabled-login --ingroup <groupname> <username>
|
||||
// useradd -M -u <id> -s /bin/nologin -N -g <groupname> <username>
|
||||
// addgroup --gid <id> <groupname>
|
||||
// groupadd -g <id> <groupname>
|
||||
|
||||
const baseUID int = 10000
|
||||
const baseGID int = 10000
|
||||
const idMAX int = 65534
|
||||
|
||||
var (
|
||||
userCommand string
|
||||
groupCommand string
|
||||
|
||||
cmdTemplates = map[string]string{
|
||||
"adduser": "--uid %d --shell /bin/false --no-create-home --disabled-login --ingroup %s %s",
|
||||
"useradd": "-M -u %d -s /bin/false -N -g %s %s",
|
||||
"addgroup": "--gid %d %s",
|
||||
"groupadd": "-g %d %s",
|
||||
}
|
||||
)
|
||||
|
||||
func init() {
|
||||
// set up which commands are used for adding users/groups dependent on distro
|
||||
if _, err := resolveBinary("adduser"); err == nil {
|
||||
userCommand = "adduser"
|
||||
} else if _, err := resolveBinary("useradd"); err == nil {
|
||||
userCommand = "useradd"
|
||||
}
|
||||
if _, err := resolveBinary("addgroup"); err == nil {
|
||||
groupCommand = "addgroup"
|
||||
} else if _, err := resolveBinary("groupadd"); err == nil {
|
||||
groupCommand = "groupadd"
|
||||
}
|
||||
}
|
||||
|
||||
func resolveBinary(binname string) (string, error) {
|
||||
binaryPath, err := exec.LookPath(binname)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
resolvedPath, err := filepath.EvalSymlinks(binaryPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
//only return no error if the final resolved binary basename
|
||||
//matches what was searched for
|
||||
if filepath.Base(resolvedPath) == binname {
|
||||
return resolvedPath, nil
|
||||
}
|
||||
return "", fmt.Errorf("Binary %q does not resolve to a binary of that name in $PATH (%q)", binname, resolvedPath)
|
||||
}
|
||||
|
||||
// AddNamespaceRangesUser takes a name and finds an unused uid, gid pair
|
||||
// and calls the appropriate helper function to add the group and then
|
||||
// the user to the group in /etc/group and /etc/passwd respectively.
|
||||
// This new user's /etc/sub{uid,gid} ranges will be used for user namespace
|
||||
// mapping ranges in containers.
|
||||
func AddNamespaceRangesUser(name string) (int, int, error) {
|
||||
// Find unused uid, gid pair
|
||||
uid, err := findUnusedUID(baseUID)
|
||||
if err != nil {
|
||||
return -1, -1, fmt.Errorf("Unable to find unused UID: %v", err)
|
||||
}
|
||||
gid, err := findUnusedGID(baseGID)
|
||||
if err != nil {
|
||||
return -1, -1, fmt.Errorf("Unable to find unused GID: %v", err)
|
||||
}
|
||||
|
||||
// First add the group that we will use
|
||||
if err := addGroup(name, gid); err != nil {
|
||||
return -1, -1, fmt.Errorf("Error adding group %q: %v", name, err)
|
||||
}
|
||||
// Add the user as a member of the group
|
||||
if err := addUser(name, uid, name); err != nil {
|
||||
return -1, -1, fmt.Errorf("Error adding user %q: %v", name, err)
|
||||
}
|
||||
return uid, gid, nil
|
||||
}
|
||||
|
||||
func addUser(userName string, uid int, groupName string) error {
|
||||
|
||||
if userCommand == "" {
|
||||
return fmt.Errorf("Cannot add user; no useradd/adduser binary found")
|
||||
}
|
||||
args := fmt.Sprintf(cmdTemplates[userCommand], uid, groupName, userName)
|
||||
return execAddCmd(userCommand, args)
|
||||
}
|
||||
|
||||
func addGroup(groupName string, gid int) error {
|
||||
|
||||
if groupCommand == "" {
|
||||
return fmt.Errorf("Cannot add group; no groupadd/addgroup binary found")
|
||||
}
|
||||
args := fmt.Sprintf(cmdTemplates[groupCommand], gid, groupName)
|
||||
// only error out if the error isn't that the group already exists
|
||||
// if the group exists then our needs are already met
|
||||
if err := execAddCmd(groupCommand, args); err != nil && !strings.Contains(err.Error(), "already exists") {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func execAddCmd(cmd, args string) error {
|
||||
execCmd := exec.Command(cmd, strings.Split(args, " ")...)
|
||||
out, err := execCmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to add user/group with error: %v; output: %q", err, string(out))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func findUnusedUID(startUID int) (int, error) {
|
||||
return findUnused("passwd", startUID)
|
||||
}
|
||||
|
||||
func findUnusedGID(startGID int) (int, error) {
|
||||
return findUnused("group", startGID)
|
||||
}
|
||||
|
||||
func findUnused(file string, id int) (int, error) {
|
||||
for {
|
||||
cmdStr := fmt.Sprintf("cat /etc/%s | cut -d: -f3 | grep '^%d$'", file, id)
|
||||
cmd := exec.Command("sh", "-c", cmdStr)
|
||||
if err := cmd.Run(); err != nil {
|
||||
// if a non-zero return code occurs, then we know the ID was not found
|
||||
// and is usable
|
||||
if exiterr, ok := err.(*exec.ExitError); ok {
|
||||
// The program has exited with an exit code != 0
|
||||
if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
|
||||
if status.ExitStatus() == 1 {
|
||||
//no match, we can use this ID
|
||||
return id, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1, fmt.Errorf("Error looking in /etc/%s for unused ID: %v", file, err)
|
||||
}
|
||||
id++
|
||||
if id > idMAX {
|
||||
return -1, fmt.Errorf("Maximum id in %q reached with finding unused numeric ID", file)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue