mirror of
https://github.com/containers/podman.git
synced 2025-06-05 05:57:24 +08:00
rootless: detect user namespace configuration changes
detect if the current user namespace doesn't match the configuration in the /etc/subuid and /etc/subgid files. If there is a mismatch, raise a warning and suggest the user to recreate the user namespace with "system migrate", that also restarts the containers. Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
This commit is contained in:
@ -120,6 +120,14 @@ func profileOff(cmd *cobra.Command) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func setupRootless(cmd *cobra.Command, args []string) error {
|
func setupRootless(cmd *cobra.Command, args []string) error {
|
||||||
|
matches, err := rootless.ConfigurationMatches()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !matches {
|
||||||
|
logrus.Warningf("the current user namespace doesn't match the configuration in /etc/subuid or /etc/subgid")
|
||||||
|
logrus.Warningf("you can use `%s system migrate` to recreate the user namespace and restart the containers", os.Args[0])
|
||||||
|
}
|
||||||
if os.Geteuid() == 0 || cmd == _searchCommand || cmd == _versionCommand || cmd == _mountCommand || cmd == _migrateCommand || strings.HasPrefix(cmd.Use, "help") {
|
if os.Geteuid() == 0 || cmd == _searchCommand || cmd == _versionCommand || cmd == _mountCommand || cmd == _migrateCommand || strings.HasPrefix(cmd.Use, "help") {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -140,7 +148,7 @@ func setupRootless(cmd *cobra.Command, args []string) error {
|
|||||||
became, ret, err := rootless.TryJoinFromFilePaths("", false, []string{pausePidPath})
|
became, ret, err := rootless.TryJoinFromFilePaths("", false, []string{pausePidPath})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Errorf("cannot join pause process. You may need to remove %s and stop all containers", pausePidPath)
|
logrus.Errorf("cannot join pause process. You may need to remove %s and stop all containers", pausePidPath)
|
||||||
logrus.Errorf("you can use `%s system migrate` to recreate the pause process", os.Args[0])
|
logrus.Errorf("you can use `%s system migrate` to recreate the pause process and restart the containers", os.Args[0])
|
||||||
logrus.Errorf(err.Error())
|
logrus.Errorf(err.Error())
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,9 @@
|
|||||||
package rootless
|
package rootless
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
@ -106,7 +108,7 @@ func tryMappingTool(tool string, pid int, hostID int, mappings []idtools.IDMap)
|
|||||||
}
|
}
|
||||||
|
|
||||||
appendTriplet := func(l []string, a, b, c int) []string {
|
appendTriplet := func(l []string, a, b, c int) []string {
|
||||||
return append(l, fmt.Sprintf("%d", a), fmt.Sprintf("%d", b), fmt.Sprintf("%d", c))
|
return append(l, strconv.Itoa(a), strconv.Itoa(b), strconv.Itoa(c))
|
||||||
}
|
}
|
||||||
|
|
||||||
args := []string{path, fmt.Sprintf("%d", pid)}
|
args := []string{path, fmt.Sprintf("%d", pid)}
|
||||||
@ -345,6 +347,31 @@ func joinUserAndMountNS(pid uint, pausePid string) (bool, int, error) {
|
|||||||
return true, int(ret), nil
|
return true, int(ret), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getConfiguredMappings() ([]idtools.IDMap, []idtools.IDMap, error) {
|
||||||
|
var uids, gids []idtools.IDMap
|
||||||
|
username := os.Getenv("USER")
|
||||||
|
if username == "" {
|
||||||
|
var id string
|
||||||
|
if os.Geteuid() == 0 {
|
||||||
|
id = strconv.Itoa(GetRootlessUID())
|
||||||
|
} else {
|
||||||
|
id = strconv.Itoa(os.Geteuid())
|
||||||
|
}
|
||||||
|
userID, err := user.LookupId(id)
|
||||||
|
if err == nil {
|
||||||
|
username = userID.Username
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mappings, err := idtools.NewIDMappings(username, username)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Warnf("cannot find mappings for user %s: %v", username, err)
|
||||||
|
} else {
|
||||||
|
uids = mappings.UIDs()
|
||||||
|
gids = mappings.GIDs()
|
||||||
|
}
|
||||||
|
return uids, gids, nil
|
||||||
|
}
|
||||||
|
|
||||||
func becomeRootInUserNS(pausePid, fileToRead string, fileOutput *os.File) (bool, int, error) {
|
func becomeRootInUserNS(pausePid, fileToRead string, fileOutput *os.File) (bool, int, error) {
|
||||||
if os.Geteuid() == 0 || os.Getenv("_CONTAINERS_USERNS_CONFIGURED") != "" {
|
if os.Geteuid() == 0 || os.Getenv("_CONTAINERS_USERNS_CONFIGURED") != "" {
|
||||||
if os.Getenv("_CONTAINERS_USERNS_CONFIGURED") == "init" {
|
if os.Getenv("_CONTAINERS_USERNS_CONFIGURED") == "init" {
|
||||||
@ -386,25 +413,14 @@ func becomeRootInUserNS(pausePid, fileToRead string, fileOutput *os.File) (bool,
|
|||||||
return false, -1, errors.Errorf("cannot re-exec process")
|
return false, -1, errors.Errorf("cannot re-exec process")
|
||||||
}
|
}
|
||||||
|
|
||||||
var uids, gids []idtools.IDMap
|
uids, gids, err := getConfiguredMappings()
|
||||||
username := os.Getenv("USER")
|
|
||||||
if username == "" {
|
|
||||||
userID, err := user.LookupId(fmt.Sprintf("%d", os.Getuid()))
|
|
||||||
if err == nil {
|
|
||||||
username = userID.Username
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mappings, err := idtools.NewIDMappings(username, username)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Warnf("cannot find mappings for user %s: %v", username, err)
|
return false, -1, err
|
||||||
} else {
|
|
||||||
uids = mappings.UIDs()
|
|
||||||
gids = mappings.GIDs()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
uidsMapped := false
|
uidsMapped := false
|
||||||
if mappings != nil && uids != nil {
|
if uids != nil {
|
||||||
err := tryMappingTool("newuidmap", pid, os.Getuid(), uids)
|
err := tryMappingTool("newuidmap", pid, os.Geteuid(), uids)
|
||||||
uidsMapped = err == nil
|
uidsMapped = err == nil
|
||||||
}
|
}
|
||||||
if !uidsMapped {
|
if !uidsMapped {
|
||||||
@ -416,20 +432,20 @@ func becomeRootInUserNS(pausePid, fileToRead string, fileOutput *os.File) (bool,
|
|||||||
}
|
}
|
||||||
|
|
||||||
uidMap := fmt.Sprintf("/proc/%d/uid_map", pid)
|
uidMap := fmt.Sprintf("/proc/%d/uid_map", pid)
|
||||||
err = ioutil.WriteFile(uidMap, []byte(fmt.Sprintf("%d %d 1\n", 0, os.Getuid())), 0666)
|
err = ioutil.WriteFile(uidMap, []byte(fmt.Sprintf("%d %d 1\n", 0, os.Geteuid())), 0666)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, -1, errors.Wrapf(err, "cannot write uid_map")
|
return false, -1, errors.Wrapf(err, "cannot write uid_map")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
gidsMapped := false
|
gidsMapped := false
|
||||||
if mappings != nil && gids != nil {
|
if gids != nil {
|
||||||
err := tryMappingTool("newgidmap", pid, os.Getgid(), gids)
|
err := tryMappingTool("newgidmap", pid, os.Getegid(), gids)
|
||||||
gidsMapped = err == nil
|
gidsMapped = err == nil
|
||||||
}
|
}
|
||||||
if !gidsMapped {
|
if !gidsMapped {
|
||||||
gidMap := fmt.Sprintf("/proc/%d/gid_map", pid)
|
gidMap := fmt.Sprintf("/proc/%d/gid_map", pid)
|
||||||
err = ioutil.WriteFile(gidMap, []byte(fmt.Sprintf("%d %d 1\n", 0, os.Getgid())), 0666)
|
err = ioutil.WriteFile(gidMap, []byte(fmt.Sprintf("%d %d 1\n", 0, os.Getegid())), 0666)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, -1, errors.Wrapf(err, "cannot write gid_map")
|
return false, -1, errors.Wrapf(err, "cannot write gid_map")
|
||||||
}
|
}
|
||||||
@ -586,3 +602,85 @@ func TryJoinFromFilePaths(pausePidPath string, needNewNamespace bool, paths []st
|
|||||||
|
|
||||||
return joinUserAndMountNS(uint(pausePid), pausePidPath)
|
return joinUserAndMountNS(uint(pausePid), pausePidPath)
|
||||||
}
|
}
|
||||||
|
func readMappingsProc(path string) ([]idtools.IDMap, error) {
|
||||||
|
file, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(err, "cannot open %s", path)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
mappings := []idtools.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, idtools.IDMap{ContainerID: containerID, HostID: hostID, Size: size})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func matches(id int, configuredIDs []idtools.IDMap, currentIDs []idtools.IDMap) bool {
|
||||||
|
// The first mapping is the host user, handle it separately.
|
||||||
|
if currentIDs[0].HostID != id || currentIDs[0].Size != 1 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
currentIDs = currentIDs[1:]
|
||||||
|
if len(currentIDs) != len(configuredIDs) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// It is fine to iterate sequentially as both slices are sorted.
|
||||||
|
for i := range currentIDs {
|
||||||
|
if currentIDs[i].HostID != configuredIDs[i].HostID {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if currentIDs[i].Size != configuredIDs[i].Size {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConfigurationMatches checks whether the additional uids/gids configured for the user
|
||||||
|
// match the current user namespace.
|
||||||
|
func ConfigurationMatches() (bool, error) {
|
||||||
|
if !IsRootless() || os.Geteuid() != 0 {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
uids, gids, err := getConfiguredMappings()
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
currentUIDs, err := readMappingsProc("/proc/self/uid_map")
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !matches(GetRootlessUID(), uids, currentUIDs) {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
currentGIDs, err := readMappingsProc("/proc/self/gid_map")
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return matches(GetRootlessGID(), gids, currentGIDs), nil
|
||||||
|
}
|
||||||
|
@ -53,3 +53,9 @@ func EnableLinger() (string, error) {
|
|||||||
func TryJoinFromFilePaths(pausePidPath string, needNewNamespace bool, paths []string) (bool, int, error) {
|
func TryJoinFromFilePaths(pausePidPath string, needNewNamespace bool, paths []string) (bool, int, error) {
|
||||||
return false, -1, errors.New("this function is not supported on this os")
|
return false, -1, errors.New("this function is not supported on this os")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ConfigurationMatches checks whether the additional uids/gids configured for the user
|
||||||
|
// match the current user namespace.
|
||||||
|
func ConfigurationMatches() (bool, error) {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user