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  }