func (u *DefGroup) Exists() (bool, error) { _, err := user.LookupGroup(u.groupname) if err != nil { return false, nil } return true, nil }
func (u *DefGroup) GID() (int, error) { group, err := user.LookupGroup(u.groupname) if err != nil { return 0, err } return group.Gid, nil }
func (u *DefGroup) Gid() (interface{}, error) { group, err := user.LookupGroup(u.groupname) if err != nil { return "", nil } return strconv.Itoa(group.Gid), nil }
// LookupGroup uses traditional local system files lookup (from libcontainer/user) on a group name, // followed by a call to `getent` for supporting host configured non-files passwd and group dbs func LookupGroup(groupname string) (user.Group, error) { // first try a local system files lookup using existing capabilities group, err := user.LookupGroup(groupname) if err == nil { return group, nil } // local files lookup failed; attempt to call `getent` to query configured group dbs return getentGroup(fmt.Sprintf("%s %s", "group", groupname)) }
func parseMappings(config *specs.LinuxRuntimeSpec, hc *containertypes.HostConfig) error { for _, g := range hc.GroupAdd { var newGidMap = []specs.IDMapping{} group, err := user.LookupGroup(g) if err != nil { return fmt.Errorf("looking up group %s failed: %v", g, err) } gid := uint32(group.Gid) for _, gm := range config.Linux.GIDMappings { if (gm.ContainerID+gm.Size) >= gid && gm.ContainerID <= gid { size := gm.Size // split the config.Linux.GIDMappingsping up so we can map to the additional group gm.Size = gid - gm.ContainerID - 1 // add the gid maps for the additional groups newGidMap = append(newGidMap, specs.IDMapping{ ContainerID: gid, HostID: gid, Size: 1, }) // add the other side of the split newGidMap = append(newGidMap, specs.IDMapping{ ContainerID: gid + 1, HostID: gm.HostID + gid - 1, Size: size - gid - 1, }) } // add back original gm newGidMap = append(newGidMap, gm) } config.Linux.GIDMappings = newGidMap } return nil }
// Parse the remapped root (user namespace) option, which can be one of: // username - valid username from /etc/passwd // username:groupname - valid username; valid groupname from /etc/group // uid - 32-bit unsigned int valid Linux UID value // uid:gid - uid value; 32-bit unsigned int Linux GID value // // If no groupname is specified, and a username is specified, an attempt // will be made to lookup a gid for that username as a groupname // // If names are used, they are verified to exist in passwd/group func parseRemappedRoot(usergrp string) (string, string, error) { var ( userID, groupID int username, groupname string ) idparts := strings.Split(usergrp, ":") if len(idparts) > 2 { return "", "", fmt.Errorf("Invalid user/group specification in --userns-remap: %q", usergrp) } if uid, err := strconv.ParseInt(idparts[0], 10, 32); err == nil { // must be a uid; take it as valid userID = int(uid) luser, err := user.LookupUid(userID) if err != nil { return "", "", fmt.Errorf("Uid %d has no entry in /etc/passwd: %v", userID, err) } username = luser.Name if len(idparts) == 1 { // if the uid was numeric and no gid was specified, take the uid as the gid groupID = userID lgrp, err := user.LookupGid(groupID) if err != nil { return "", "", fmt.Errorf("Gid %d has no entry in /etc/group: %v", groupID, err) } groupname = lgrp.Name } } else { lookupName := idparts[0] // special case: if the user specified "default", they want Docker to create or // use (after creation) the "dockremap" user/group for root remapping if lookupName == defaultIDSpecifier { lookupName = defaultRemappedID } luser, err := user.LookupUser(lookupName) if err != nil && idparts[0] != defaultIDSpecifier { // error if the name requested isn't the special "dockremap" ID return "", "", fmt.Errorf("Error during uid lookup for %q: %v", lookupName, err) } else if err != nil { // special case-- if the username == "default", then we have been asked // to create a new entry pair in /etc/{passwd,group} for which the /etc/sub{uid,gid} // ranges will be used for the user and group mappings in user namespaced containers _, _, err := idtools.AddNamespaceRangesUser(defaultRemappedID) if err == nil { return defaultRemappedID, defaultRemappedID, nil } return "", "", fmt.Errorf("Error during %q user creation: %v", defaultRemappedID, err) } userID = luser.Uid username = luser.Name if len(idparts) == 1 { // we only have a string username, and no group specified; look up gid from username as group group, err := user.LookupGroup(lookupName) if err != nil { return "", "", fmt.Errorf("Error during gid lookup for %q: %v", lookupName, err) } groupID = group.Gid groupname = group.Name } } if len(idparts) == 2 { // groupname or gid is separately specified and must be resolved // to a unsigned 32-bit gid if gid, err := strconv.ParseInt(idparts[1], 10, 32); err == nil { // must be a gid, take it as valid groupID = int(gid) lgrp, err := user.LookupGid(groupID) if err != nil { return "", "", fmt.Errorf("Gid %d has no entry in /etc/passwd: %v", groupID, err) } groupname = lgrp.Name } else { // not a number; attempt a lookup group, err := user.LookupGroup(idparts[1]) if err != nil { return "", "", fmt.Errorf("Error during gid lookup for %q: %v", idparts[1], err) } groupID = group.Gid groupname = idparts[1] } } return username, groupname, nil }
// Config takes ContainerJSON and Daemon Info and converts it into the opencontainers spec. func Config(c types.ContainerJSON, info types.Info, capabilities []string) (config *specs.LinuxSpec, err error) { config = &specs.LinuxSpec{ Spec: specs.Spec{ Version: SpecVersion, Platform: specs.Platform{ OS: info.OSType, Arch: info.Architecture, }, Process: specs.Process{ Terminal: c.Config.Tty, User: specs.User{ // TODO: user stuffs }, Args: append([]string{c.Path}, c.Args...), Env: c.Config.Env, Cwd: c.Config.WorkingDir, }, Root: specs.Root{ Path: "rootfs", Readonly: c.HostConfig.ReadonlyRootfs, }, Mounts: []specs.MountPoint{}, }, } // make sure the current working directory is not blank if config.Process.Cwd == "" { config.Process.Cwd = DefaultCurrentWorkingDirectory } // get the user if c.Config.User != "" { u, err := user.LookupUser(c.Config.User) if err != nil { config.Spec.Process.User = specs.User{ UID: uint32(u.Uid), GID: uint32(u.Gid), } } else { //return nil, fmt.Errorf("Looking up user (%s) failed: %v", c.Config.User, err) logrus.Warnf("Looking up user (%s) failed: %v", c.Config.User, err) } } // add the additional groups for _, group := range c.HostConfig.GroupAdd { g, err := user.LookupGroup(group) if err != nil { return nil, fmt.Errorf("Looking up group (%s) failed: %v", group, err) } config.Spec.Process.User.AdditionalGids = append(config.Spec.Process.User.AdditionalGids, uint32(g.Gid)) } // get the hostname, if the hostname is the name as the first 12 characters of the id, // then set the hostname as the container name if c.ID[:12] == c.Config.Hostname { config.Hostname = strings.TrimPrefix(c.Name, "/") } // get mounts mounts := map[string]bool{} for _, mount := range c.Mounts { mounts[mount.Destination] = true config.Mounts = append(config.Mounts, specs.MountPoint{ Name: mount.Destination, Path: mount.Destination, }) } // add /etc/hosts and /etc/resolv.conf if we should have networking if c.HostConfig.NetworkMode != "none" && c.HostConfig.NetworkMode != "host" { DefaultMounts = append(DefaultMounts, NetworkMounts...) } // if we aren't doing something crazy like mounting a default mount ourselves, // the we can mount it the default way for _, mount := range DefaultMounts { if _, ok := mounts[mount.Path]; !ok { config.Mounts = append(config.Mounts, mount) } } // set privileged if c.HostConfig.Privileged { // allow all caps capabilities = execdriver.GetAllCapabilities() } // get the capabilities config.Linux.Capabilities, err = execdriver.TweakCapabilities(capabilities, c.HostConfig.CapAdd.Slice(), c.HostConfig.CapDrop.Slice()) if err != nil { return nil, fmt.Errorf("setting capabilities failed: %v", err) } // add CAP_ prefix // TODO: this is awful for i, cap := range config.Linux.Capabilities { if !strings.HasPrefix(cap, "CAP_") { config.Linux.Capabilities[i] = fmt.Sprintf("CAP_%s", cap) } } // if we have a container that needs a terminal but no env vars, then set // default env vars for the terminal to function if config.Spec.Process.Terminal && len(config.Spec.Process.Env) <= 0 { config.Spec.Process.Env = DefaultTerminalEnv } if config.Spec.Process.Terminal { // make sure we have TERM set var termSet bool for _, env := range config.Spec.Process.Env { if strings.HasPrefix(env, "TERM=") { termSet = true break } } if !termSet { // set the term variable config.Spec.Process.Env = append(config.Spec.Process.Env, fmt.Sprintf("TERM=%s", DefaultTerminal)) } } return config, nil }
func TestParseMappings(t *testing.T) { groupIDs := map[string]uint32{} groups := []string{"audio", "video"} for _, g := range groups { group, err := user.LookupGroup(g) if err != nil { t.Fatalf("looking up group %s failed: %v", g, err) } groupIDs[g] = uint32(group.Gid) } tests := []mappings{ { gidMap: []specs.IDMapping{ { ContainerID: 0, HostID: 87645, Size: 46578392, }, }, additionalGroups: []string{"audio"}, expected: []specs.IDMapping{ { ContainerID: groupIDs["audio"], HostID: groupIDs["audio"], Size: 1, }, { ContainerID: groupIDs["audio"] + 1, HostID: 87645 + groupIDs["audio"] - 1, Size: 46578392 - groupIDs["audio"] - 1, }, { ContainerID: 0, HostID: 87645, Size: groupIDs["audio"] - 1, }, }, }, { gidMap: []specs.IDMapping{ { ContainerID: 0, HostID: 87645, Size: 46578392, }, }, additionalGroups: []string{"audio", "video"}, expected: []specs.IDMapping{ { ContainerID: groupIDs["audio"], HostID: groupIDs["audio"], Size: 1, }, { ContainerID: groupIDs["video"], HostID: groupIDs["video"], Size: 1, }, { ContainerID: groupIDs["video"] + 1, HostID: (87645 + groupIDs["audio"] - 1) + groupIDs["video"] - 1, Size: 46578392 - groupIDs["video"] - groupIDs["audio"] - 2, }, { ContainerID: groupIDs["audio"] + 1, HostID: 87645 + groupIDs["audio"] - 1, Size: groupIDs["video"] - groupIDs["audio"] - 2, }, { ContainerID: 0, HostID: 87645, Size: groupIDs["audio"] - 1, }, }, }, } for _, test := range tests { // make config config := &specs.LinuxRuntimeSpec{ Linux: specs.LinuxRuntime{ GIDMappings: test.gidMap, }, } hostConfig := &containertypes.HostConfig{ GroupAdd: test.additionalGroups, } if err := parseMappings(config, hostConfig); err != nil { t.Fatal(err) } if !reflect.DeepEqual(test.expected, config.Linux.GIDMappings) { t.Fatalf("expected:\n%#v\ngot:\n%#v", test.expected, config.Linux.GIDMappings) } } }
// Config takes ContainerJSON and converts it into the opencontainers spec. func Config(c types.ContainerJSON, osType, architecture string, capabilities []string, idroot, idlen uint32) (config *specs.Spec, err error) { // for user namespaces use defaults unless another range specified if idroot == 0 { idroot = DefaultUserNSHostID } if idlen == 0 { idlen = DefaultUserNSMapSize } config = &specs.Spec{ Version: SpecVersion, Platform: specs.Platform{ OS: osType, Arch: architecture, }, Process: specs.Process{ Terminal: c.Config.Tty, User: specs.User{ // TODO: user stuffs }, Args: append([]string{c.Path}, c.Args...), Env: c.Config.Env, Cwd: c.Config.WorkingDir, // TODO: add parsing of Ulimits Rlimits: []specs.Rlimit{ { Type: "RLIMIT_NOFILE", Hard: uint64(1024), Soft: uint64(1024), }, }, NoNewPrivileges: true, ApparmorProfile: c.AppArmorProfile, }, Root: specs.Root{ Path: "rootfs", Readonly: c.HostConfig.ReadonlyRootfs, }, Mounts: []specs.Mount{}, Linux: specs.Linux{ Namespaces: []specs.Namespace{ { Type: "ipc", }, { Type: "uts", }, { Type: "mount", }, }, UIDMappings: []specs.IDMapping{ { ContainerID: 0, HostID: idroot, Size: idlen, }, }, GIDMappings: []specs.IDMapping{ { ContainerID: 0, HostID: idroot, Size: idlen, }, }, Resources: &specs.Resources{ Devices: []specs.DeviceCgroup{ { Allow: false, Access: sPtr("rwm"), }, }, DisableOOMKiller: c.HostConfig.Resources.OomKillDisable, OOMScoreAdj: &c.HostConfig.OomScoreAdj, Memory: &specs.Memory{ Limit: uint64ptr(c.HostConfig.Resources.Memory), Reservation: uint64ptr(c.HostConfig.Resources.MemoryReservation), Swap: uint64ptr(c.HostConfig.Resources.MemorySwap), Swappiness: uint64ptr(*c.HostConfig.Resources.MemorySwappiness), Kernel: uint64ptr(c.HostConfig.Resources.KernelMemory), }, CPU: &specs.CPU{ Shares: uint64ptr(c.HostConfig.Resources.CPUShares), Quota: uint64ptr(c.HostConfig.Resources.CPUQuota), Period: uint64ptr(c.HostConfig.Resources.CPUPeriod), Cpus: &c.HostConfig.Resources.CpusetCpus, Mems: &c.HostConfig.Resources.CpusetMems, }, Pids: &specs.Pids{ Limit: &c.HostConfig.Resources.PidsLimit, }, BlockIO: &specs.BlockIO{ Weight: &c.HostConfig.Resources.BlkioWeight, // TODO: add parsing for Throttle/Weight Devices }, }, RootfsPropagation: "", }, } // make sure the current working directory is not blank if config.Process.Cwd == "" { config.Process.Cwd = DefaultCurrentWorkingDirectory } // get the user if c.Config.User != "" { u, err := user.LookupUser(c.Config.User) if err != nil { config.Process.User = specs.User{ UID: uint32(u.Uid), GID: uint32(u.Gid), } } else { //return nil, fmt.Errorf("Looking up user (%s) failed: %v", c.Config.User, err) logrus.Warnf("Looking up user (%s) failed: %v", c.Config.User, err) } } // add the additional groups for _, group := range c.HostConfig.GroupAdd { g, err := user.LookupGroup(group) if err != nil { return nil, fmt.Errorf("Looking up group (%s) failed: %v", group, err) } config.Process.User.AdditionalGids = append(config.Process.User.AdditionalGids, uint32(g.Gid)) } // get the hostname, if the hostname is the name as the first 12 characters of the id, // then set the hostname as the container name if c.ID[:12] == c.Config.Hostname { config.Hostname = strings.TrimPrefix(c.Name, "/") } // set privileged if c.HostConfig.Privileged { // allow all caps capabilities = execdriver.GetAllCapabilities() } // get the capabilities config.Process.Capabilities, err = execdriver.TweakCapabilities(capabilities, c.HostConfig.CapAdd, c.HostConfig.CapDrop) if err != nil { return nil, fmt.Errorf("setting capabilities failed: %v", err) } // add CAP_ prefix // TODO: this is awful for i, cap := range config.Process.Capabilities { if !strings.HasPrefix(cap, "CAP_") { config.Process.Capabilities[i] = fmt.Sprintf("CAP_%s", cap) } } // if we have a container that needs a terminal but no env vars, then set // default env vars for the terminal to function if config.Process.Terminal && len(config.Process.Env) <= 0 { config.Process.Env = DefaultTerminalEnv } if config.Process.Terminal { // make sure we have TERM set var termSet bool for _, env := range config.Process.Env { if strings.HasPrefix(env, "TERM=") { termSet = true break } } if !termSet { // set the term variable config.Process.Env = append(config.Process.Env, fmt.Sprintf("TERM=%s", DefaultTerminal)) } } // check namespaces if !c.HostConfig.NetworkMode.IsHost() { config.Linux.Namespaces = append(config.Linux.Namespaces, specs.Namespace{ Type: "network", }) } if !c.HostConfig.PidMode.IsHost() { config.Linux.Namespaces = append(config.Linux.Namespaces, specs.Namespace{ Type: "pid", }) } if c.HostConfig.UsernsMode.Valid() && !c.HostConfig.NetworkMode.IsHost() && !c.HostConfig.PidMode.IsHost() && !c.HostConfig.Privileged { config.Linux.Namespaces = append(config.Linux.Namespaces, specs.Namespace{ Type: "user", }) } else { // reset uid and gid mappings config.Linux.UIDMappings = []specs.IDMapping{} config.Linux.GIDMappings = []specs.IDMapping{} } // get mounts mounts := map[string]bool{} for _, mount := range c.Mounts { mounts[mount.Destination] = true var opt []string if mount.RW { opt = append(opt, "rw") } if mount.Mode != "" { opt = append(opt, mount.Mode) } opt = append(opt, []string{"rbind", "rprivate"}...) config.Mounts = append(config.Mounts, specs.Mount{ Destination: mount.Destination, Type: "bind", Source: mount.Source, Options: opt, }) } // add /etc/hosts and /etc/resolv.conf if we should have networking if c.HostConfig.NetworkMode != "none" && c.HostConfig.NetworkMode != "host" { DefaultMounts = append(DefaultMounts, NetworkMounts...) } // if we aren't doing something crazy like mounting a default mount ourselves, // the we can mount it the default way for _, mount := range DefaultMounts { if _, ok := mounts[mount.Destination]; !ok { config.Mounts = append(config.Mounts, mount) } } // fix default mounts for cgroups and devpts without user namespaces // see: https://github.com/opencontainers/runc/issues/225#issuecomment-136519577 if len(config.Linux.UIDMappings) == 0 { for k, mount := range config.Mounts { switch mount.Destination { case "/sys/fs/cgroup": config.Mounts[k].Options = append(config.Mounts[k].Options, "ro") case "/dev/pts": config.Mounts[k].Options = append(config.Mounts[k].Options, "gid=5") } } } // parse additional groups and add them to gid mappings if err := parseMappings(config, c.HostConfig); err != nil { return nil, err } // parse devices if err := parseDevices(config, c.HostConfig); err != nil { return nil, err } // parse security opt if err := parseSecurityOpt(config, c.HostConfig); err != nil { return nil, err } // set privileged if c.HostConfig.Privileged { if !c.HostConfig.ReadonlyRootfs { // clear readonly for cgroup // config.Mounts["cgroup"] = DefaultMountpoints["cgroup"] } } return config, nil }