mirror of
https://github.com/lxc/incus.git
synced 2026-02-05 09:46:19 +01:00
341 lines
7.1 KiB
Go
341 lines
7.1 KiB
Go
package idmap
|
|
|
|
import (
|
|
"bufio"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"os/user"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/lxc/incus/v6/shared/util"
|
|
)
|
|
|
|
// ErrNoUserMap indicates that no entry could be found for the user.
|
|
var ErrNoUserMap = errors.New("No map found for user")
|
|
|
|
// ErrSubidUnsupported indicates that the system is lacking support for subuid/subgid.
|
|
var ErrSubidUnsupported = errors.New("System doesn't support subuid/subgid")
|
|
|
|
// DefaultFullKernelSet is the default Set of uid/gid with no mapping at all.
|
|
var DefaultFullKernelSet = &Set{Entries: []Entry{
|
|
{true, false, int64(0), int64(0), int64(4294967294)},
|
|
{false, true, int64(0), int64(0), int64(4294967294)},
|
|
}}
|
|
|
|
// NewSetFromJSON unpacks an idmap Set from its JSON representation.
|
|
func NewSetFromJSON(data string) (*Set, error) {
|
|
ret := &Set{}
|
|
err := json.Unmarshal([]byte(data), &ret.Entries)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if len(ret.Entries) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
return ret, nil
|
|
}
|
|
|
|
// NewSetFromIncusIDMap parses an Incus raw.idmap into a new idmap Set.
|
|
func NewSetFromIncusIDMap(value string) (*Set, error) {
|
|
getRange := func(r string) (int64, int64, error) {
|
|
entries := strings.Split(r, "-")
|
|
if len(entries) > 2 {
|
|
return -1, -1, fmt.Errorf("Invalid ID map range: %s", r)
|
|
}
|
|
|
|
base, err := strconv.ParseInt(entries[0], 10, 64)
|
|
if err != nil {
|
|
return -1, -1, err
|
|
}
|
|
|
|
size := int64(1)
|
|
if len(entries) > 1 {
|
|
size, err = strconv.ParseInt(entries[1], 10, 64)
|
|
if err != nil {
|
|
return -1, -1, err
|
|
}
|
|
|
|
size -= base
|
|
size++
|
|
}
|
|
|
|
return base, size, nil
|
|
}
|
|
|
|
ret := &Set{}
|
|
|
|
for _, line := range strings.Split(value, "\n") {
|
|
if line == "" {
|
|
continue
|
|
}
|
|
|
|
entries := strings.Split(line, " ")
|
|
if len(entries) != 3 {
|
|
return nil, fmt.Errorf("Invalid ID map line: %s", line)
|
|
}
|
|
|
|
outsideBase, outsideSize, err := getRange(entries[1])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
insideBase, insideSize, err := getRange(entries[2])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if insideSize != outsideSize {
|
|
return nil, fmt.Errorf("The ID map ranges are of different sizes: %s", line)
|
|
}
|
|
|
|
entry := Entry{
|
|
HostID: outsideBase,
|
|
NSID: insideBase,
|
|
MapRange: insideSize,
|
|
}
|
|
|
|
switch entries[0] {
|
|
case "both":
|
|
entry.IsUID = true
|
|
entry.IsGID = true
|
|
err := ret.AddSafe(entry)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
case "uid":
|
|
entry.IsUID = true
|
|
err := ret.AddSafe(entry)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
case "gid":
|
|
entry.IsGID = true
|
|
err := ret.AddSafe(entry)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
default:
|
|
return nil, fmt.Errorf("Invalid ID map type: %s", line)
|
|
}
|
|
}
|
|
|
|
return ret, nil
|
|
}
|
|
|
|
// NewSetFromCurrentProcess returns a Set from the process' current uid/gid map.
|
|
func NewSetFromCurrentProcess() (*Set, error) {
|
|
// Check if system doesn't have user namespaces.
|
|
if !util.PathExists("/proc/self/uid_map") || !util.PathExists("/proc/self/gid_map") {
|
|
// Without a user namespace, just return the full map.
|
|
return DefaultFullKernelSet, nil
|
|
}
|
|
|
|
ret := &Set{}
|
|
|
|
// Parse the uidmap.
|
|
entries, err := getFromProc("/proc/self/uid_map")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for _, entry := range entries {
|
|
e := Entry{IsUID: true, NSID: entry[0], HostID: entry[1], MapRange: entry[2]}
|
|
ret.Entries = append(ret.Entries, e)
|
|
}
|
|
|
|
// Parse the gidmap.
|
|
entries, err = getFromProc("/proc/self/gid_map")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for _, entry := range entries {
|
|
e := Entry{IsGID: true, NSID: entry[0], HostID: entry[1], MapRange: entry[2]}
|
|
ret.Entries = append(ret.Entries, e)
|
|
}
|
|
|
|
return ret, nil
|
|
}
|
|
|
|
// NewSetFromSystem returns a Set for the specified user from the system's subuid/subgid configuration.
|
|
func NewSetFromSystem(username string) (*Set, error) {
|
|
// Check if the system supports subuid/subgid.
|
|
pathNewUIDMap, _ := exec.LookPath("newuidmap")
|
|
pathNewGIDMap, _ := exec.LookPath("newgidmap")
|
|
|
|
if !util.PathExists("/etc/subuid") || !util.PathExists("/etc/subgid") || pathNewUIDMap == "" || pathNewGIDMap == "" {
|
|
return nil, ErrSubidUnsupported
|
|
}
|
|
|
|
// If no username specified, use the current user.
|
|
if username == "" {
|
|
currentUser, err := user.Current()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
username = currentUser.Username
|
|
}
|
|
|
|
ret := &Set{}
|
|
|
|
// Parse the shadow uidmap.
|
|
entries, err := getFromShadow("/etc/subuid", username)
|
|
if err != nil {
|
|
if err != ErrNoUserMap {
|
|
return nil, err
|
|
}
|
|
|
|
// Try using the uid instead.
|
|
u, err := user.Lookup(username)
|
|
if err != nil {
|
|
return nil, ErrNoUserMap
|
|
}
|
|
|
|
entries, err = getFromShadow("/etc/subuid", u.Uid)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
for _, entry := range entries {
|
|
e := Entry{IsUID: true, NSID: 0, HostID: entry[0], MapRange: entry[1]}
|
|
ret.Entries = append(ret.Entries, e)
|
|
}
|
|
|
|
// Parse the shadow gidmap.
|
|
entries, err = getFromShadow("/etc/subgid", username)
|
|
if err != nil {
|
|
if err != ErrNoUserMap {
|
|
return nil, err
|
|
}
|
|
|
|
// Try using the uid instead.
|
|
u, err := user.Lookup(username)
|
|
if err != nil {
|
|
return nil, ErrNoUserMap
|
|
}
|
|
|
|
entries, err = getFromShadow("/etc/subgid", u.Uid)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
for _, entry := range entries {
|
|
e := Entry{IsGID: true, NSID: 0, HostID: entry[0], MapRange: entry[1]}
|
|
ret.Entries = append(ret.Entries, e)
|
|
}
|
|
|
|
return ret, nil
|
|
}
|
|
|
|
func getFromShadow(fname string, username string) ([][]int64, error) {
|
|
entries := [][]int64{}
|
|
|
|
f, err := os.Open(fname)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
defer func() { _ = f.Close() }()
|
|
|
|
scanner := bufio.NewScanner(f)
|
|
for scanner.Scan() {
|
|
// Skip comments.
|
|
s := strings.Split(scanner.Text(), "#")
|
|
if len(s[0]) == 0 {
|
|
continue
|
|
}
|
|
|
|
// Validate format.
|
|
s = strings.Split(s[0], ":")
|
|
if len(s) < 3 {
|
|
return nil, fmt.Errorf("Unexpected values in %q: %q", fname, s)
|
|
}
|
|
|
|
if strings.EqualFold(s[0], username) {
|
|
// Get range start.
|
|
entryStart, err := strconv.ParseUint(s[1], 10, 32)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
// Get range size.
|
|
entrySize, err := strconv.ParseUint(s[2], 10, 32)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
entries = append(entries, []int64{int64(entryStart), int64(entrySize)})
|
|
}
|
|
}
|
|
|
|
if len(entries) == 0 {
|
|
return nil, ErrNoUserMap
|
|
}
|
|
|
|
return entries, nil
|
|
}
|
|
|
|
func getFromProc(fname string) ([][]int64, error) {
|
|
entries := [][]int64{}
|
|
|
|
f, err := os.Open(fname)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
defer func() { _ = f.Close() }()
|
|
|
|
scanner := bufio.NewScanner(f)
|
|
for scanner.Scan() {
|
|
// Skip comments.
|
|
s := strings.Split(scanner.Text(), "#")
|
|
if len(s[0]) == 0 {
|
|
continue
|
|
}
|
|
|
|
// Validate format.
|
|
s = strings.Fields(s[0])
|
|
if len(s) < 3 {
|
|
return nil, fmt.Errorf("Unexpected values in %q: %q", fname, s)
|
|
}
|
|
|
|
// Get range start.
|
|
entryStart, err := strconv.ParseUint(s[0], 10, 32)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
// Get range size.
|
|
entryHost, err := strconv.ParseUint(s[1], 10, 32)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
// Get range size.
|
|
entrySize, err := strconv.ParseUint(s[2], 10, 32)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
entries = append(entries, []int64{int64(entryStart), int64(entryHost), int64(entrySize)})
|
|
}
|
|
|
|
if len(entries) == 0 {
|
|
return nil, errors.New("Namespace doesn't have any map set")
|
|
}
|
|
|
|
return entries, nil
|
|
}
|