Merge pull request #3190 from giuseppe/fix-userns-psgo
rootless: fix top huser and hgroup
This commit is contained in:
commit
1dbb27365a
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/containers/libpod/pkg/rootless"
|
||||||
"github.com/containers/psgo"
|
"github.com/containers/psgo"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
@ -47,7 +48,9 @@ func (c *Container) GetContainerPidInformation(descriptors []string) ([]string,
|
||||||
// filters on the data. We need to change the API here and the
|
// filters on the data. We need to change the API here and the
|
||||||
// varlink API to return a [][]string if we want to make use of
|
// varlink API to return a [][]string if we want to make use of
|
||||||
// filtering.
|
// filtering.
|
||||||
psgoOutput, err := psgo.JoinNamespaceAndProcessInfo(pid, descriptors)
|
opts := psgo.JoinNamespaceOpts{FillMappings: rootless.IsRootless()}
|
||||||
|
|
||||||
|
psgoOutput, err := psgo.JoinNamespaceAndProcessInfoWithOptions(pid, descriptors, &opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/containers/libpod/pkg/rootless"
|
||||||
"github.com/containers/psgo"
|
"github.com/containers/psgo"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -43,7 +44,8 @@ func (p *Pod) GetPodPidInformation(descriptors []string) ([]string, error) {
|
||||||
// filters on the data. We need to change the API here and the
|
// filters on the data. We need to change the API here and the
|
||||||
// varlink API to return a [][]string if we want to make use of
|
// varlink API to return a [][]string if we want to make use of
|
||||||
// filtering.
|
// filtering.
|
||||||
output, err := psgo.JoinNamespaceAndProcessInfoByPids(pids, descriptors)
|
opts := psgo.JoinNamespaceOpts{FillMappings: rootless.IsRootless()}
|
||||||
|
output, err := psgo.JoinNamespaceAndProcessInfoByPidsWithOptions(pids, descriptors, &opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ github.com/vbauerster/mpb v3.3.4
|
||||||
github.com/mattn/go-isatty v0.0.4
|
github.com/mattn/go-isatty v0.0.4
|
||||||
github.com/VividCortex/ewma v1.1.1
|
github.com/VividCortex/ewma v1.1.1
|
||||||
github.com/containers/storage v1.12.7
|
github.com/containers/storage v1.12.7
|
||||||
github.com/containers/psgo v1.2.1
|
github.com/containers/psgo v1.3.0
|
||||||
github.com/coreos/go-systemd v14
|
github.com/coreos/go-systemd v14
|
||||||
github.com/coreos/pkg v4
|
github.com/coreos/pkg v4
|
||||||
github.com/cri-o/ocicni 0c180f981b27ef6036fa5be29bcb4dd666e406eb
|
github.com/cri-o/ocicni 0c180f981b27ef6036fa5be29bcb4dd666e406eb
|
||||||
|
|
|
||||||
|
|
@ -15,10 +15,20 @@
|
||||||
package proc
|
package proc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type IDMap struct {
|
||||||
|
ContainerID int
|
||||||
|
HostID int
|
||||||
|
Size int
|
||||||
|
}
|
||||||
|
|
||||||
// ParsePIDNamespace returns the content of /proc/$pid/ns/pid.
|
// ParsePIDNamespace returns the content of /proc/$pid/ns/pid.
|
||||||
func ParsePIDNamespace(pid string) (string, error) {
|
func ParsePIDNamespace(pid string) (string, error) {
|
||||||
pidNS, err := os.Readlink(fmt.Sprintf("/proc/%s/ns/pid", pid))
|
pidNS, err := os.Readlink(fmt.Sprintf("/proc/%s/ns/pid", pid))
|
||||||
|
|
@ -36,3 +46,34 @@ func ParseUserNamespace(pid string) (string, error) {
|
||||||
}
|
}
|
||||||
return userNS, nil
|
return userNS, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ReadMappings reads the user namespace mappings at the specified path
|
||||||
|
func ReadMappings(path string) ([]IDMap, error) {
|
||||||
|
file, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(err, "cannot open %s", path)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
mappings := []IDMap{}
|
||||||
|
|
||||||
|
buf := bufio.NewReader(file)
|
||||||
|
for {
|
||||||
|
line, _, err := buf.ReadLine()
|
||||||
|
if err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
return mappings, nil
|
||||||
|
}
|
||||||
|
return nil, errors.Wrapf(err, "cannot read line from %s", path)
|
||||||
|
}
|
||||||
|
if line == nil {
|
||||||
|
return mappings, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
containerID, hostID, size := 0, 0, 0
|
||||||
|
if _, err := fmt.Sscanf(string(line), "%d %d %d", &containerID, &hostID, &size); err != nil {
|
||||||
|
return nil, errors.Wrapf(err, "cannot parse %s", string(line))
|
||||||
|
}
|
||||||
|
mappings = append(mappings, IDMap{ContainerID: containerID, HostID: hostID, Size: size})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@ type Process struct {
|
||||||
Hgroup string
|
Hgroup string
|
||||||
}
|
}
|
||||||
|
|
||||||
// LookupGID returns the textual group ID, if it can be optained, or the
|
// LookupGID returns the textual group ID, if it can be obtained, or the
|
||||||
// decimal representation otherwise.
|
// decimal representation otherwise.
|
||||||
func LookupGID(gid string) (string, error) {
|
func LookupGID(gid string) (string, error) {
|
||||||
gidNum, err := strconv.Atoi(gid)
|
gidNum, err := strconv.Atoi(gid)
|
||||||
|
|
@ -59,7 +59,7 @@ func LookupGID(gid string) (string, error) {
|
||||||
return g.Name, nil
|
return g.Name, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// LookupUID return the textual user ID, if it can be optained, or the decimal
|
// LookupUID return the textual user ID, if it can be obtained, or the decimal
|
||||||
// representation otherwise.
|
// representation otherwise.
|
||||||
func LookupUID(uid string) (string, error) {
|
func LookupUID(uid string) (string, error) {
|
||||||
uidNum, err := strconv.Atoi(uid)
|
uidNum, err := strconv.Atoi(uid)
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ package psgo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
@ -43,6 +44,31 @@ import (
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// IDMap specifies a mapping range from the host to the container IDs.
|
||||||
|
type IDMap struct {
|
||||||
|
// ContainerID is the first ID in the container.
|
||||||
|
ContainerID int
|
||||||
|
// HostID is the first ID in the host.
|
||||||
|
HostID int
|
||||||
|
// Size specifies how long is the range. e.g. 1 means a single user
|
||||||
|
// is mapped.
|
||||||
|
Size int
|
||||||
|
}
|
||||||
|
|
||||||
|
// JoinNamespaceOpts specifies different options for joining the specified namespaces.
|
||||||
|
type JoinNamespaceOpts struct {
|
||||||
|
// UIDMap specifies a mapping for UIDs in the container. If specified
|
||||||
|
// huser will perform the reverse mapping.
|
||||||
|
UIDMap []IDMap
|
||||||
|
// GIDMap specifies a mapping for GIDs in the container. If specified
|
||||||
|
// hgroup will perform the reverse mapping.
|
||||||
|
GIDMap []IDMap
|
||||||
|
|
||||||
|
// FillMappings specified whether UIDMap and GIDMap must be initialized
|
||||||
|
// with the current user namespace.
|
||||||
|
FillMappings bool
|
||||||
|
}
|
||||||
|
|
||||||
type psContext struct {
|
type psContext struct {
|
||||||
// Processes in the container.
|
// Processes in the container.
|
||||||
containersProcesses []*process.Process
|
containersProcesses []*process.Process
|
||||||
|
|
@ -50,6 +76,8 @@ type psContext struct {
|
||||||
hostProcesses []*process.Process
|
hostProcesses []*process.Process
|
||||||
// tty and pty devices.
|
// tty and pty devices.
|
||||||
ttys *[]dev.TTY
|
ttys *[]dev.TTY
|
||||||
|
// Various options
|
||||||
|
opts *JoinNamespaceOpts
|
||||||
}
|
}
|
||||||
|
|
||||||
// processFunc is used to map a given aixFormatDescriptor to a corresponding
|
// processFunc is used to map a given aixFormatDescriptor to a corresponding
|
||||||
|
|
@ -69,10 +97,36 @@ type aixFormatDescriptor struct {
|
||||||
// onHost controls if data of the corresponding host processes will be
|
// onHost controls if data of the corresponding host processes will be
|
||||||
// extracted as well.
|
// extracted as well.
|
||||||
onHost bool
|
onHost bool
|
||||||
// procFN points to the corresponding method to etract the desired data.
|
// procFN points to the corresponding method to extract the desired data.
|
||||||
procFn processFunc
|
procFn processFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// findID converts the specified id to the host mapping
|
||||||
|
func findID(idStr string, mapping []IDMap, lookupFunc func(uid string) (string, error), overflowFile string) (string, error) {
|
||||||
|
if len(mapping) == 0 {
|
||||||
|
return idStr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
id, err := strconv.ParseInt(idStr, 10, 0)
|
||||||
|
if err != nil {
|
||||||
|
return "", errors.Wrapf(err, "cannot parse %s", idStr)
|
||||||
|
}
|
||||||
|
for _, m := range mapping {
|
||||||
|
if int(id) >= m.ContainerID && int(id) < m.ContainerID+m.Size {
|
||||||
|
user := fmt.Sprintf("%d", m.HostID+(int(id)-m.ContainerID))
|
||||||
|
|
||||||
|
return lookupFunc(user)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// User not found, read the overflow
|
||||||
|
overflow, err := ioutil.ReadFile(overflowFile)
|
||||||
|
if err != nil {
|
||||||
|
return "", errors.Wrapf(err, "cannot read %s", overflowFile)
|
||||||
|
}
|
||||||
|
return string(overflow), nil
|
||||||
|
}
|
||||||
|
|
||||||
// translateDescriptors parses the descriptors and returns a correspodning slice of
|
// translateDescriptors parses the descriptors and returns a correspodning slice of
|
||||||
// aixFormatDescriptors. Descriptors can be specified in the normal and in the
|
// aixFormatDescriptors. Descriptors can be specified in the normal and in the
|
||||||
// code form (if supported). If the descriptors slice is empty, the
|
// code form (if supported). If the descriptors slice is empty, the
|
||||||
|
|
@ -272,6 +326,46 @@ func ListDescriptors() (list []string) {
|
||||||
// JoinNamespaceAndProcessInfo has the same semantics as ProcessInfo but joins
|
// JoinNamespaceAndProcessInfo has the same semantics as ProcessInfo but joins
|
||||||
// the mount namespace of the specified pid before extracting data from `/proc`.
|
// the mount namespace of the specified pid before extracting data from `/proc`.
|
||||||
func JoinNamespaceAndProcessInfo(pid string, descriptors []string) ([][]string, error) {
|
func JoinNamespaceAndProcessInfo(pid string, descriptors []string) ([][]string, error) {
|
||||||
|
return JoinNamespaceAndProcessInfoWithOptions(pid, descriptors, &JoinNamespaceOpts{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func readMappings(path string) ([]IDMap, error) {
|
||||||
|
mappings, err := proc.ReadMappings(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var res []IDMap
|
||||||
|
for _, i := range mappings {
|
||||||
|
m := IDMap{ContainerID: i.ContainerID, HostID: i.HostID, Size: i.Size}
|
||||||
|
res = append(res, m)
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func contextFromOptions(options *JoinNamespaceOpts) (*psContext, error) {
|
||||||
|
ctx := new(psContext)
|
||||||
|
ctx.opts = options
|
||||||
|
if ctx.opts != nil && ctx.opts.FillMappings {
|
||||||
|
uidMappings, err := readMappings("/proc/self/uid_map")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
gidMappings, err := readMappings("/proc/self/gid_map")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ctx.opts.UIDMap = uidMappings
|
||||||
|
ctx.opts.GIDMap = gidMappings
|
||||||
|
|
||||||
|
ctx.opts.FillMappings = false
|
||||||
|
}
|
||||||
|
return ctx, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// JoinNamespaceAndProcessInfoWithOptions has the same semantics as ProcessInfo but joins
|
||||||
|
// the mount namespace of the specified pid before extracting data from `/proc`.
|
||||||
|
func JoinNamespaceAndProcessInfoWithOptions(pid string, descriptors []string, options *JoinNamespaceOpts) ([][]string, error) {
|
||||||
var (
|
var (
|
||||||
data [][]string
|
data [][]string
|
||||||
dataErr error
|
dataErr error
|
||||||
|
|
@ -283,7 +377,10 @@ func JoinNamespaceAndProcessInfo(pid string, descriptors []string) ([][]string,
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := new(psContext)
|
ctx, err := contextFromOptions(options)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
// extract data from host processes only on-demand / when at least one
|
// extract data from host processes only on-demand / when at least one
|
||||||
// of the specified descriptors requires host data
|
// of the specified descriptors requires host data
|
||||||
|
|
@ -356,10 +453,10 @@ func JoinNamespaceAndProcessInfo(pid string, descriptors []string) ([][]string,
|
||||||
return data, dataErr
|
return data, dataErr
|
||||||
}
|
}
|
||||||
|
|
||||||
// JoinNamespaceAndProcessInfoByPids has similar semantics to
|
// JoinNamespaceAndProcessInfoByPidsWithOptions has similar semantics to
|
||||||
// JoinNamespaceAndProcessInfo and avoids duplicate entries by joining a giving
|
// JoinNamespaceAndProcessInfo and avoids duplicate entries by joining a giving
|
||||||
// PID namepsace only once.
|
// PID namespace only once.
|
||||||
func JoinNamespaceAndProcessInfoByPids(pids []string, descriptors []string) ([][]string, error) {
|
func JoinNamespaceAndProcessInfoByPidsWithOptions(pids []string, descriptors []string, options *JoinNamespaceOpts) ([][]string, error) {
|
||||||
// Extracting data from processes that share the same PID namespace
|
// Extracting data from processes that share the same PID namespace
|
||||||
// would yield duplicate results. Avoid that by extracting data only
|
// would yield duplicate results. Avoid that by extracting data only
|
||||||
// from the first process in `pids` from a given PID namespace.
|
// from the first process in `pids` from a given PID namespace.
|
||||||
|
|
@ -385,7 +482,7 @@ func JoinNamespaceAndProcessInfoByPids(pids []string, descriptors []string) ([][
|
||||||
|
|
||||||
data := [][]string{}
|
data := [][]string{}
|
||||||
for i, pid := range pidList {
|
for i, pid := range pidList {
|
||||||
pidData, err := JoinNamespaceAndProcessInfo(pid, descriptors)
|
pidData, err := JoinNamespaceAndProcessInfoWithOptions(pid, descriptors, options)
|
||||||
if os.IsNotExist(errors.Cause(err)) {
|
if os.IsNotExist(errors.Cause(err)) {
|
||||||
// catch race conditions
|
// catch race conditions
|
||||||
continue
|
continue
|
||||||
|
|
@ -402,6 +499,13 @@ func JoinNamespaceAndProcessInfoByPids(pids []string, descriptors []string) ([][
|
||||||
return data, nil
|
return data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// JoinNamespaceAndProcessInfoByPids has similar semantics to
|
||||||
|
// JoinNamespaceAndProcessInfo and avoids duplicate entries by joining a giving
|
||||||
|
// PID namespace only once.
|
||||||
|
func JoinNamespaceAndProcessInfoByPids(pids []string, descriptors []string) ([][]string, error) {
|
||||||
|
return JoinNamespaceAndProcessInfoByPidsWithOptions(pids, descriptors, &JoinNamespaceOpts{})
|
||||||
|
}
|
||||||
|
|
||||||
// ProcessInfo returns the process information of all processes in the current
|
// ProcessInfo returns the process information of all processes in the current
|
||||||
// mount namespace. The input format must be a comma-separated list of
|
// mount namespace. The input format must be a comma-separated list of
|
||||||
// supported AIX format descriptors. If the input string is empty, the
|
// supported AIX format descriptors. If the input string is empty, the
|
||||||
|
|
@ -425,7 +529,10 @@ func ProcessInfoByPids(pids []string, descriptors []string) ([][]string, error)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := new(psContext)
|
ctx, err := contextFromOptions(nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
ctx.containersProcesses, err = process.FromPIDs(pids, false)
|
ctx.containersProcesses, err = process.FromPIDs(pids, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -725,6 +832,9 @@ func processHPID(p *process.Process, ctx *psContext) (string, error) {
|
||||||
// of the (container) or "?" if no corresponding process could be found.
|
// of the (container) or "?" if no corresponding process could be found.
|
||||||
func processHUSER(p *process.Process, ctx *psContext) (string, error) {
|
func processHUSER(p *process.Process, ctx *psContext) (string, error) {
|
||||||
if hp := findHostProcess(p, ctx); hp != nil {
|
if hp := findHostProcess(p, ctx); hp != nil {
|
||||||
|
if ctx.opts != nil && len(ctx.opts.UIDMap) > 0 {
|
||||||
|
return findID(p.Status.Uids[1], ctx.opts.UIDMap, process.LookupUID, "/proc/sys/fs/overflowuid")
|
||||||
|
}
|
||||||
return hp.Huser, nil
|
return hp.Huser, nil
|
||||||
}
|
}
|
||||||
return "?", nil
|
return "?", nil
|
||||||
|
|
@ -735,6 +845,9 @@ func processHUSER(p *process.Process, ctx *psContext) (string, error) {
|
||||||
// found.
|
// found.
|
||||||
func processHGROUP(p *process.Process, ctx *psContext) (string, error) {
|
func processHGROUP(p *process.Process, ctx *psContext) (string, error) {
|
||||||
if hp := findHostProcess(p, ctx); hp != nil {
|
if hp := findHostProcess(p, ctx); hp != nil {
|
||||||
|
if ctx.opts != nil && len(ctx.opts.GIDMap) > 0 {
|
||||||
|
return findID(p.Status.Gids[1], ctx.opts.GIDMap, process.LookupGID, "/proc/sys/fs/overflowgid")
|
||||||
|
}
|
||||||
return hp.Hgroup, nil
|
return hp.Hgroup, nil
|
||||||
}
|
}
|
||||||
return "?", nil
|
return "?", nil
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue