mirror of https://github.com/containers/podman.git
				
				
				
			Merge pull request #3926 from giuseppe/add-warning-mismatch-configuration
rootless: detect user namespace configuration changes
This commit is contained in:
		
						commit
						a16f63e96e
					
				|  | @ -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,32 @@ func joinUserAndMountNS(pid uint, pausePid string) (bool, int, error) { | ||||||
| 	return true, int(ret), nil | 	return true, int(ret), nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // GetConfiguredMappings returns the additional IDs configured for the current user.
 | ||||||
|  | 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 +414,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 +433,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 +603,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 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -5,6 +5,7 @@ package rootless | ||||||
| import ( | import ( | ||||||
| 	"os" | 	"os" | ||||||
| 
 | 
 | ||||||
|  | 	"github.com/containers/storage/pkg/idtools" | ||||||
| 	"github.com/pkg/errors" | 	"github.com/pkg/errors" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | @ -53,3 +54,14 @@ 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 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // GetConfiguredMappings returns the additional IDs configured for the current user.
 | ||||||
|  | func GetConfiguredMappings() ([]idtools.IDMap, []idtools.IDMap, error) { | ||||||
|  | 	return nil, nil, errors.New("this function is not supported on this os") | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -3,7 +3,6 @@ package util | ||||||
| import ( | import ( | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"os" | 	"os" | ||||||
| 	ouser "os/user" |  | ||||||
| 	"path/filepath" | 	"path/filepath" | ||||||
| 	"strings" | 	"strings" | ||||||
| 	"sync" | 	"sync" | ||||||
|  | @ -156,22 +155,15 @@ func ParseIDMapping(mode namespaces.UsernsMode, UIDMapSlice, GIDMapSlice []strin | ||||||
| 			uid := rootless.GetRootlessUID() | 			uid := rootless.GetRootlessUID() | ||||||
| 			gid := rootless.GetRootlessGID() | 			gid := rootless.GetRootlessGID() | ||||||
| 
 | 
 | ||||||
| 			username := os.Getenv("USER") | 			uids, gids, err := rootless.GetConfiguredMappings() | ||||||
| 			if username == "" { |  | ||||||
| 				user, err := ouser.LookupId(fmt.Sprintf("%d", uid)) |  | ||||||
| 				if err == nil { |  | ||||||
| 					username = user.Username |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 			mappings, err := idtools.NewIDMappings(username, username) |  | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				return nil, errors.Wrapf(err, "cannot find mappings for user %s", username) | 				return nil, errors.Wrapf(err, "cannot read mappings") | ||||||
| 			} | 			} | ||||||
| 			maxUID, maxGID := 0, 0 | 			maxUID, maxGID := 0, 0 | ||||||
| 			for _, u := range mappings.UIDs() { | 			for _, u := range uids { | ||||||
| 				maxUID += u.Size | 				maxUID += u.Size | ||||||
| 			} | 			} | ||||||
| 			for _, g := range mappings.GIDs() { | 			for _, g := range gids { | ||||||
| 				maxGID += g.Size | 				maxGID += g.Size | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue