[lxc-devel] [lxd/master] Multi idmap
tych0 on Github
lxc-bot at linuxcontainers.org
Thu Nov 17 01:17:20 UTC 2016
A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 443 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20161117/878d1882/attachment.bin>
-------------- next part --------------
From 43b2164c38fbae4f736e21b364926610ecbe4bcc Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Thu, 10 Nov 2016 09:38:40 +0200
Subject: [PATCH 1/3] add the id_map API extension
This first bit adds support for security.idmap.{size,isolated}, which allow
for configuring the idmaps of each individual container.
Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
doc/api-extensions.md | 4 ++
lxd/api_1.0.go | 1 +
lxd/container.go | 23 ++++++
lxd/container_lxc.go | 177 ++++++++++++++++++++++++++++++++++++++++++++++-
lxd/container_test.go | 82 ++++++++++++++++++++++
lxd/main_test.go | 7 ++
shared/container.go | 18 +++++
shared/idmapset_linux.go | 14 ++++
8 files changed, 323 insertions(+), 3 deletions(-)
diff --git a/doc/api-extensions.md b/doc/api-extensions.md
index fc592c4..3542f7a 100644
--- a/doc/api-extensions.md
+++ b/doc/api-extensions.md
@@ -156,3 +156,7 @@ Enables adding GPUs to a container.
## container\_image\_properties
Introduces a new "image" config key space. Read-only, includes the properties of the parent image.
+
+## id\_map
+Enables setting the `security.idmap.isolated` and `security.idmap.isolated`,
+`security.idmap.size`, and `raw.id_map` fields.
diff --git a/lxd/api_1.0.go b/lxd/api_1.0.go
index c4654e2..8f44225 100644
--- a/lxd/api_1.0.go
+++ b/lxd/api_1.0.go
@@ -77,6 +77,7 @@ func api10Get(d *Daemon, r *http.Request) Response {
"container_exec_signal_handling",
"gpu_devices",
"container_image_properties",
+ "id_map",
},
"api_status": "stable",
diff --git a/lxd/container.go b/lxd/container.go
index 95e9c12..ad462b9 100644
--- a/lxd/container.go
+++ b/lxd/container.go
@@ -1,6 +1,7 @@
package main
import (
+ "encoding/json"
"fmt"
"io"
"os"
@@ -645,6 +646,28 @@ func containerCreateInternal(d *Daemon, args containerArgs) (container, error) {
// Wipe any existing log for this container name
os.RemoveAll(shared.LogPath(args.Name))
+ idmap, base, err := findIdmap(
+ d,
+ args.Name,
+ args.Config["security.idmap.isolated"],
+ args.Config["security.idmap.size"],
+ )
+ if err != nil {
+ return nil, err
+ }
+ var jsonIdmap string
+ if idmap != nil {
+ idmapBytes, err := json.Marshal(idmap.Idmap)
+ if err != nil {
+ return nil, err
+ }
+ jsonIdmap = string(idmapBytes)
+ } else {
+ jsonIdmap = "[]"
+ }
+ args.Config["volatile.idmap.next"] = jsonIdmap
+ args.Config["volatile.idmap.base"] = fmt.Sprintf("%v", base)
+
// Create the container entry
id, err := dbContainerCreate(d.db, args)
if err != nil {
diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index d178fdb..f9b03ee 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -13,6 +13,7 @@ import (
"path"
"path/filepath"
"reflect"
+ "sort"
"strconv"
"strings"
"sync"
@@ -235,6 +236,12 @@ func containerLXCCreate(d *Daemon, args containerArgs) (container, error) {
return nil, err
}
+ err = c.ConfigKeySet("volatile.idmap.next", jsonIdmap)
+ if err != nil {
+ c.Delete()
+ return nil, err
+ }
+
// Update lease files
networkUpdateStatic(d)
@@ -345,6 +352,134 @@ func (c *containerLXC) waitOperation() error {
return nil
}
+func idmapSize(daemon *Daemon, isolatedStr string, size string) (int, error) {
+ isolated := false
+ if isolatedStr == "true" {
+ isolated = true
+ }
+
+ var idMapSize int
+ if size == "" || size == "auto" {
+ if isolated {
+ idMapSize = 65536
+ } else {
+ if len(daemon.IdmapSet.Idmap) != 2 {
+ return 0, fmt.Errorf("bad initial idmap: %v", daemon.IdmapSet)
+ }
+
+ idMapSize = daemon.IdmapSet.Idmap[0].Maprange
+ }
+ } else {
+ size, err := strconv.ParseInt(size, 10, 32)
+ if err != nil {
+ return 0, err
+ }
+
+ idMapSize = int(size)
+ }
+
+ return idMapSize, nil
+}
+
+var idmapLock sync.Mutex
+
+func findIdmap(daemon *Daemon, cName string, isolatedStr string, configSize string) (*shared.IdmapSet, int, error) {
+ isolated := false
+ if isolatedStr == "true" {
+ isolated = true
+ }
+
+ if !isolated {
+ return daemon.IdmapSet, 0, nil
+ }
+
+ idmapLock.Lock()
+ defer idmapLock.Unlock()
+
+ cs, err := dbContainersList(daemon.db, cTypeRegular)
+ if err != nil {
+ return nil, 0, err
+ }
+
+ offset := daemon.IdmapSet.Idmap[0].Hostid + 65536
+ size, err := idmapSize(daemon, isolatedStr, configSize)
+ if err != nil {
+ return nil, 0, err
+ }
+
+ mapentries := shared.ByHostid{}
+ for _, name := range cs {
+ /* Don't change our map Just Because. */
+ if name == cName {
+ continue
+ }
+
+ container, err := containerLoadByName(daemon, name)
+ if err != nil {
+ return nil, 0, err
+ }
+
+ if container.ExpandedConfig()["security.idmap.isolated"] != "true" {
+ continue
+ }
+
+ cBase, err := strconv.ParseInt(container.ExpandedConfig()["volatile.idmap.base"], 10, 32)
+ if err != nil {
+ return nil, 0, err
+ }
+
+ cSize, err := idmapSize(daemon, container.ExpandedConfig()["security.idmap.isolated"], container.ExpandedConfig()["security.idmap.size"])
+ if err != nil {
+ return nil, 0, err
+ }
+
+ mapentries = append(mapentries, &shared.IdmapEntry{Hostid: int(cBase), Maprange: cSize})
+ }
+
+ sort.Sort(mapentries)
+
+ for i := range mapentries {
+ if i == 0 {
+ if mapentries[0].Hostid < offset+size {
+ offset = mapentries[0].Hostid + mapentries[0].Maprange
+ continue
+ }
+
+ set := &shared.IdmapSet{Idmap: []shared.IdmapEntry{
+ shared.IdmapEntry{Isuid: true, Nsid: 0, Hostid: offset, Maprange: size},
+ shared.IdmapEntry{Isgid: true, Nsid: 0, Hostid: offset, Maprange: size},
+ }}
+
+ return set, offset, nil
+ }
+
+ if mapentries[i-1].Hostid+mapentries[i-1].Maprange > offset {
+ continue
+ }
+
+ offset = mapentries[i-1].Hostid + mapentries[i-1].Maprange
+ if offset+size < mapentries[i].Nsid {
+ set := &shared.IdmapSet{Idmap: []shared.IdmapEntry{
+ shared.IdmapEntry{Isuid: true, Nsid: 0, Hostid: offset, Maprange: size},
+ shared.IdmapEntry{Isgid: true, Nsid: 0, Hostid: offset, Maprange: size},
+ }}
+
+ return set, offset, nil
+ }
+ }
+
+ if offset+size < daemon.IdmapSet.Idmap[0].Hostid+daemon.IdmapSet.Idmap[0].Maprange {
+ set := &shared.IdmapSet{Idmap: []shared.IdmapEntry{
+ shared.IdmapEntry{Isuid: true, Nsid: 0, Hostid: offset, Maprange: size},
+ shared.IdmapEntry{Isgid: true, Nsid: 0, Hostid: offset, Maprange: size},
+ }}
+
+ return set, offset, nil
+ }
+
+ return nil, 0, fmt.Errorf("no map range available")
+}
+
func (c *containerLXC) init() error {
// Compute the expanded config and device list
err := c.expandConfig()
@@ -362,7 +497,11 @@ func (c *containerLXC) init() error {
if c.daemon.IdmapSet == nil {
return fmt.Errorf("LXD doesn't have a uid/gid allocation. In this mode, only privileged containers are supported.")
}
- c.idmapset = c.daemon.IdmapSet
+
+ c.idmapset, err = c.NextIdmapSet()
+ if err != nil {
+ return err
+ }
}
return nil
@@ -2521,6 +2660,29 @@ func (c *containerLXC) Update(args containerArgs, userRequested bool) error {
}
}()
+ // update the idmap
+ idmap, base, err := findIdmap(
+ c.daemon,
+ c.Name(),
+ args.Config["security.idmap.isolated"],
+ args.Config["security.idmap.size"],
+ )
+ if err != nil {
+ return err
+ }
+ var jsonIdmap string
+ if idmap != nil {
+ idmapBytes, err := json.Marshal(idmap.Idmap)
+ if err != nil {
+ return err
+ }
+ jsonIdmap = string(idmapBytes)
+ } else {
+ jsonIdmap = "[]"
+ }
+ args.Config["volatile.idmap.next"] = jsonIdmap
+ args.Config["volatile.idmap.base"] = fmt.Sprintf("%v", base)
+
// Apply the various changes
c.architecture = args.Architecture
c.ephemeral = args.Ephemeral
@@ -5648,8 +5810,8 @@ func (c *containerLXC) LocalDevices() shared.Devices {
return c.localDevices
}
-func (c *containerLXC) LastIdmapSet() (*shared.IdmapSet, error) {
- lastJsonIdmap := c.LocalConfig()["volatile.last_state.idmap"]
+func (c *containerLXC) idmapsetFromConfig(k string) (*shared.IdmapSet, error) {
+ lastJsonIdmap := c.LocalConfig()[k]
if lastJsonIdmap == "" {
return c.IdmapSet(), nil
@@ -5668,6 +5830,15 @@ func (c *containerLXC) LastIdmapSet() (*shared.IdmapSet, error) {
return lastIdmap, nil
}
+func (c *containerLXC) NextIdmapSet() (*shared.IdmapSet, error) {
+ return c.idmapsetFromConfig("volatile.idmap.next")
+
+}
+
+func (c *containerLXC) LastIdmapSet() (*shared.IdmapSet, error) {
+ return c.idmapsetFromConfig("volatile.last_state.idmap")
+}
+
func (c *containerLXC) Daemon() *Daemon {
// FIXME: This function should go away
return c.daemon
diff --git a/lxd/container_test.go b/lxd/container_test.go
index 13b608a..ea237b3 100644
--- a/lxd/container_test.go
+++ b/lxd/container_test.go
@@ -220,3 +220,85 @@ func (suite *lxdTestSuite) TestContainer_Rename() {
suite.Req.Nil(c.Rename("testFoo2"), "Failed to rename the container.")
suite.Req.Equal(shared.VarPath("containers", "testFoo2"), c.Path())
}
+
+func (suite *lxdTestSuite) TestContainer_findIdmap_isolated() {
+ c1, err := containerCreateInternal(suite.d, containerArgs{
+ Ctype: cTypeRegular,
+ Name: "isol-1",
+ Config: map[string]string{
+ "security.idmap.isolated": "true",
+ },
+ })
+ suite.Req.Nil(err)
+ defer c1.Delete()
+
+ c2, err := containerCreateInternal(suite.d, containerArgs{
+ Ctype: cTypeRegular,
+ Name: "isol-2",
+ Config: map[string]string{
+ "security.idmap.isolated": "true",
+ },
+ })
+ suite.Req.Nil(err)
+ defer c2.Delete()
+
+ map1, err := c1.(*containerLXC).NextIdmapSet()
+ suite.Req.Nil(err)
+ map2, err := c2.(*containerLXC).NextIdmapSet()
+ suite.Req.Nil(err)
+
+ host := suite.d.IdmapSet.Idmap[0]
+
+ for i := 0; i < 2; i++ {
+ suite.Req.Equal(host.Hostid+65536, map1.Idmap[i].Hostid, "hostids don't match %d", i)
+ suite.Req.Equal(0, map1.Idmap[i].Nsid, "nsid nonzero")
+ suite.Req.Equal(65536, map1.Idmap[i].Maprange, "incorrect maprange")
+ }
+
+ for i := 0; i < 2; i++ {
+ suite.Req.Equal(host.Hostid+65536*2, map2.Idmap[i].Hostid, "hostids don't match")
+ suite.Req.Equal(0, map2.Idmap[i].Nsid, "nsid nonzero")
+ suite.Req.Equal(65536, map2.Idmap[i].Maprange, "incorrect maprange")
+ }
+}
+
+func (suite *lxdTestSuite) TestContainer_findIdmap_mixed() {
+ c1, err := containerCreateInternal(suite.d, containerArgs{
+ Ctype: cTypeRegular,
+ Name: "isol-1",
+ Config: map[string]string{
+ "security.idmap.isolated": "false",
+ },
+ })
+ suite.Req.Nil(err)
+ defer c1.Delete()
+
+ c2, err := containerCreateInternal(suite.d, containerArgs{
+ Ctype: cTypeRegular,
+ Name: "isol-2",
+ Config: map[string]string{
+ "security.idmap.isolated": "true",
+ },
+ })
+ suite.Req.Nil(err)
+ defer c2.Delete()
+
+ map1, err := c1.(*containerLXC).NextIdmapSet()
+ suite.Req.Nil(err)
+ map2, err := c2.(*containerLXC).NextIdmapSet()
+ suite.Req.Nil(err)
+
+ host := suite.d.IdmapSet.Idmap[0]
+
+ for i := 0; i < 2; i++ {
+ suite.Req.Equal(host.Hostid, map1.Idmap[i].Hostid, "hostids don't match %d", i)
+ suite.Req.Equal(0, map1.Idmap[i].Nsid, "nsid nonzero")
+ suite.Req.Equal(host.Maprange, map1.Idmap[i].Maprange, "incorrect maprange")
+ }
+
+ for i := 0; i < 2; i++ {
+ suite.Req.Equal(host.Hostid+65536+1, map2.Idmap[i].Hostid, "hostids don't match")
+ suite.Req.Equal(0, map2.Idmap[i].Nsid, "nsid nonzero")
+ suite.Req.Equal(65536, map2.Idmap[i].Maprange, "incorrect maprange")
+ }
+}
diff --git a/lxd/main_test.go b/lxd/main_test.go
index 7a61429..262c541 100644
--- a/lxd/main_test.go
+++ b/lxd/main_test.go
@@ -8,6 +8,8 @@ import (
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
+
+ "github.com/lxc/lxd/shared"
)
func mockStartDaemon() (*Daemon, error) {
@@ -21,6 +23,11 @@ func mockStartDaemon() (*Daemon, error) {
return nil, err
}
+ d.IdmapSet = &shared.IdmapSet{Idmap: []shared.IdmapEntry{
+ shared.IdmapEntry{Isuid: true, Hostid: 100000, Nsid: 0, Maprange: 500000},
+ shared.IdmapEntry{Isgid: true, Hostid: 100000, Nsid: 0, Maprange: 500000},
+ }}
+
// Call this after Init so we have a log object.
storageConfig := make(map[string]interface{})
d.Storage = &storageLogWrapper{w: &storageMock{d: d}}
diff --git a/shared/container.go b/shared/container.go
index 36964c7..aac0322 100644
--- a/shared/container.go
+++ b/shared/container.go
@@ -174,6 +174,19 @@ func IsInt64(value string) error {
return nil
}
+func IsUint32(value string) error {
+ if value == "" {
+ return nil
+ }
+
+ _, err := strconv.ParseInt(value, 10, 32)
+ if err != nil {
+ return fmt.Errorf("Invalid value for uint32: %s: %v", value, err)
+ }
+
+ return nil
+}
+
func IsPriority(value string) error {
if value == "" {
return nil
@@ -302,6 +315,9 @@ var KnownContainerConfigKeys = map[string]func(value string) error{
"security.nesting": IsBool,
"security.privileged": IsBool,
+ "security.idmap.size": IsUint32,
+ "security.idmap.isolated": IsBool,
+
"security.syscalls.blacklist_default": IsBool,
"security.syscalls.blacklist_compat": IsBool,
"security.syscalls.blacklist": IsAny,
@@ -316,6 +332,8 @@ var KnownContainerConfigKeys = map[string]func(value string) error{
"volatile.base_image": IsAny,
"volatile.last_state.idmap": IsAny,
"volatile.last_state.power": IsAny,
+ "volatile.idmap.next": IsAny,
+ "volatile.idmap.base": IsAny,
}
// ConfigKeyChecker returns a function that will check whether or not
diff --git a/shared/idmapset_linux.go b/shared/idmapset_linux.go
index 4e59d69..b08601a 100644
--- a/shared/idmapset_linux.go
+++ b/shared/idmapset_linux.go
@@ -123,6 +123,20 @@ func (e *IdmapEntry) shift_from_ns(id int) (int, error) {
return id - e.Hostid + e.Nsid, nil
}
+type ByHostid []*IdmapEntry
+
+func (s ByHostid) Len() int {
+ return len(s)
+}
+
+func (s ByHostid) Swap(i, j int) {
+ s[i], s[j] = s[j], s[i]
+}
+
+func (s ByHostid) Less(i, j int) bool {
+ return s[i].Hostid < s[j].Hostid
+}
+
/* taken from http://blog.golang.org/slices (which is under BSD licence) */
func Extend(slice []IdmapEntry, element IdmapEntry) []IdmapEntry {
n := len(slice)
From 0bbcd15667ba7442f2f5fd39fc9719b17d548606 Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Wed, 16 Nov 2016 17:19:50 -0700
Subject: [PATCH 2/3] add support for raw.idmap
Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
lxd/container.go | 1 +
lxd/container_lxc.go | 132 ++++++++++++++++++++++++++++++++++++++++-------
lxd/container_test.go | 38 +++++++++++++-
shared/container.go | 1 +
shared/idmapset_linux.go | 68 ++++++++++++++++++++++++
shared/idmapset_test.go | 83 +++++++++++++++++++++++++++++
test/suites/filemanip.sh | 5 ++
7 files changed, 307 insertions(+), 21 deletions(-)
create mode 100644 shared/idmapset_test.go
diff --git a/lxd/container.go b/lxd/container.go
index ad462b9..b40961a 100644
--- a/lxd/container.go
+++ b/lxd/container.go
@@ -651,6 +651,7 @@ func containerCreateInternal(d *Daemon, args containerArgs) (container, error) {
args.Name,
args.Config["security.idmap.isolated"],
args.Config["security.idmap.size"],
+ args.Config["raw.idmap"],
)
if err != nil {
return nil, err
diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index f9b03ee..7f2a08b 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -383,14 +383,107 @@ func idmapSize(daemon *Daemon, isolatedStr string, size string) (int, error) {
var idmapLock sync.Mutex
-func findIdmap(daemon *Daemon, cName string, isolatedStr string, configSize string) (*shared.IdmapSet, int, error) {
+func parseRawIdmap(value string) ([]shared.IdmapEntry, error) {
+ getRange := func(r string) (int, int, error) {
+ entries := strings.Split(r, "-")
+ if len(entries) > 2 {
+ return -1, -1, fmt.Errorf("invalid raw.idmap range %s", r)
+ }
+
+ base, err := strconv.Atoi(entries[0])
+ if err != nil {
+ return -1, -1, err
+ }
+
+ size := 1
+ if len(entries) > 1 {
+ size, err = strconv.Atoi(entries[1])
+ if err != nil {
+ return -1, -1, err
+ }
+
+ size -= base
+ }
+
+ return base, size, nil
+ }
+
+ ret := shared.IdmapSet{}
+
+ for _, line := range strings.Split(value, "\n") {
+ if line == "" {
+ continue
+ }
+
+ entries := strings.Split(line, " ")
+ if len(entries) != 3 {
+ return nil, fmt.Errorf("invalid raw.idmap 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("idmap ranges of different sizes %s", line)
+ }
+
+ entry := shared.IdmapEntry{
+ Hostid: outsideBase,
+ Nsid: insideBase,
+ Maprange: insideSize,
+ }
+
+ switch entries[0] {
+ case "both":
+ entry.Isuid = true
+ ret.AddSafe(entry)
+ ret.AddSafe(shared.IdmapEntry{
+ Isgid: true,
+ Hostid: entry.Hostid,
+ Nsid: entry.Nsid,
+ Maprange: entry.Maprange,
+ })
+ case "uid":
+ entry.Isuid = true
+ ret.AddSafe(entry)
+ case "gid":
+ entry.Isgid = true
+ ret.AddSafe(entry)
+ default:
+ return nil, fmt.Errorf("invalid raw.idmap type %s", line)
+ }
+ }
+
+ return ret.Idmap, nil
+}
+
+func findIdmap(daemon *Daemon, cName string, isolatedStr string, configSize string, rawIdmap string) (*shared.IdmapSet, int, error) {
isolated := false
if isolatedStr == "true" {
isolated = true
}
+ rawMaps, err := parseRawIdmap(rawIdmap)
+ if err != nil {
+ return nil, 0, err
+ }
+
if !isolated {
- return daemon.IdmapSet, 0, nil
+ newIdmapset := shared.IdmapSet{Idmap: make([]shared.IdmapEntry, len(daemon.IdmapSet.Idmap))}
+ copy(newIdmapset.Idmap, daemon.IdmapSet.Idmap)
+
+ for _, ent := range rawMaps {
+ newIdmapset.AddSafe(ent)
+ }
+
+ return &newIdmapset, 0, nil
}
idmapLock.Lock()
@@ -438,6 +531,19 @@ func findIdmap(daemon *Daemon, cName string, isolatedStr string, configSize stri
sort.Sort(mapentries)
+ mkIdmap := func(offset int, size int) *shared.IdmapSet {
+ set := &shared.IdmapSet{Idmap: []shared.IdmapEntry{
+ shared.IdmapEntry{Isuid: true, Nsid: 0, Hostid: offset, Maprange: size},
+ shared.IdmapEntry{Isgid: true, Nsid: 0, Hostid: offset, Maprange: size},
+ }}
+
+ for _, ent := range rawMaps {
+ set.AddSafe(ent)
+ }
+
+ return set
+ }
+
for i := range mapentries {
if i == 0 {
if mapentries[0].Hostid < offset+size {
@@ -445,12 +551,7 @@ func findIdmap(daemon *Daemon, cName string, isolatedStr string, configSize stri
continue
}
- set := &shared.IdmapSet{Idmap: []shared.IdmapEntry{
- shared.IdmapEntry{Isuid: true, Nsid: 0, Hostid: offset, Maprange: size},
- shared.IdmapEntry{Isgid: true, Nsid: 0, Hostid: offset, Maprange: size},
- }}
-
- return set, offset, nil
+ return mkIdmap(offset, size), offset, nil
}
if mapentries[i-1].Hostid+mapentries[i-1].Maprange > offset {
@@ -459,22 +560,12 @@ func findIdmap(daemon *Daemon, cName string, isolatedStr string, configSize stri
offset = mapentries[i-1].Hostid + mapentries[i-1].Maprange
if offset+size < mapentries[i].Nsid {
- set := &shared.IdmapSet{Idmap: []shared.IdmapEntry{
- shared.IdmapEntry{Isuid: true, Nsid: 0, Hostid: offset, Maprange: size},
- shared.IdmapEntry{Isgid: true, Nsid: 0, Hostid: offset, Maprange: size},
- }}
-
- return set, offset, nil
+ return mkIdmap(offset, size), offset, nil
}
}
if offset+size < daemon.IdmapSet.Idmap[0].Hostid+daemon.IdmapSet.Idmap[0].Maprange {
- set := &shared.IdmapSet{Idmap: []shared.IdmapEntry{
- shared.IdmapEntry{Isuid: true, Nsid: 0, Hostid: offset, Maprange: size},
- shared.IdmapEntry{Isgid: true, Nsid: 0, Hostid: offset, Maprange: size},
- }}
-
- return set, offset, nil
+ return mkIdmap(offset, size), offset, nil
}
return nil, 0, fmt.Errorf("no map range available")
@@ -2666,6 +2757,7 @@ func (c *containerLXC) Update(args containerArgs, userRequested bool) error {
c.Name(),
args.Config["security.idmap.isolated"],
args.Config["security.idmap.size"],
+ args.Config["raw.idmap"],
)
if err != nil {
return err
diff --git a/lxd/container_test.go b/lxd/container_test.go
index ea237b3..f74d3b6 100644
--- a/lxd/container_test.go
+++ b/lxd/container_test.go
@@ -297,8 +297,44 @@ func (suite *lxdTestSuite) TestContainer_findIdmap_mixed() {
}
for i := 0; i < 2; i++ {
- suite.Req.Equal(host.Hostid+65536+1, map2.Idmap[i].Hostid, "hostids don't match")
+ suite.Req.Equal(host.Hostid+65536, map2.Idmap[i].Hostid, "hostids don't match")
suite.Req.Equal(0, map2.Idmap[i].Nsid, "nsid nonzero")
suite.Req.Equal(65536, map2.Idmap[i].Maprange, "incorrect maprange")
}
}
+
+func (suite *lxdTestSuite) TestContainer_findIdmap_raw() {
+ c1, err := containerCreateInternal(suite.d, containerArgs{
+ Ctype: cTypeRegular,
+ Name: "isol-1",
+ Config: map[string]string{
+ "security.idmap.isolated": "false",
+ "raw.idmap": "both 1000 1000",
+ },
+ })
+ suite.Req.Nil(err)
+ defer c1.Delete()
+
+ map1, err := c1.(*containerLXC).NextIdmapSet()
+ suite.Req.Nil(err)
+
+ host := suite.d.IdmapSet.Idmap[0]
+
+ for _, i := range []int{0, 3} {
+ suite.Req.Equal(host.Hostid, map1.Idmap[i].Hostid, "hostids don't match")
+ suite.Req.Equal(0, map1.Idmap[i].Nsid, "nsid nonzero")
+ suite.Req.Equal(1000, map1.Idmap[i].Maprange, "incorrect maprange")
+ }
+
+ for _, i := range []int{1, 4} {
+ suite.Req.Equal(1000, map1.Idmap[i].Hostid, "hostids don't match")
+ suite.Req.Equal(1000, map1.Idmap[i].Nsid, "invalid nsid")
+ suite.Req.Equal(1, map1.Idmap[i].Maprange, "incorrect maprange")
+ }
+
+ for _, i := range []int{2, 5} {
+ suite.Req.Equal(host.Hostid+1001, map1.Idmap[i].Hostid, "hostids don't match")
+ suite.Req.Equal(1001, map1.Idmap[i].Nsid, "invalid nsid")
+ suite.Req.Equal(host.Maprange-1000-1, map1.Idmap[i].Maprange, "incorrect maprange")
+ }
+}
diff --git a/shared/container.go b/shared/container.go
index aac0322..fd15bc5 100644
--- a/shared/container.go
+++ b/shared/container.go
@@ -327,6 +327,7 @@ var KnownContainerConfigKeys = map[string]func(value string) error{
"raw.apparmor": IsAny,
"raw.lxc": IsAny,
"raw.seccomp": IsAny,
+ "raw.idmap": IsAny,
"volatile.apply_template": IsAny,
"volatile.base_image": IsAny,
diff --git a/shared/idmapset_linux.go b/shared/idmapset_linux.go
index b08601a..90ba6dc 100644
--- a/shared/idmapset_linux.go
+++ b/shared/idmapset_linux.go
@@ -35,6 +35,23 @@ func is_between(x, low, high int) bool {
return x >= low && x < high
}
+func (e *IdmapEntry) HostidsIntersect(i IdmapEntry) bool {
+ if (e.Isuid && i.Isuid) || (e.Isgid && i.Isgid) {
+ switch {
+ case is_between(e.Hostid, i.Hostid, i.Hostid+i.Maprange):
+ return true
+ case is_between(i.Hostid, e.Hostid, e.Hostid+e.Maprange):
+ return true
+ case is_between(e.Hostid+e.Maprange, i.Hostid, i.Hostid+i.Maprange):
+ return true
+ case is_between(i.Hostid+i.Maprange, e.Hostid, e.Hostid+e.Maprange):
+ return true
+ }
+ }
+
+ return false
+}
+
func (e *IdmapEntry) Intersects(i IdmapEntry) bool {
if (e.Isuid && i.Isuid) || (e.Isgid && i.Isgid) {
switch {
@@ -169,6 +186,57 @@ func (m IdmapSet) Intersects(i IdmapEntry) bool {
return false
}
+/* AddSafe adds an entry to the idmap set, breaking apart any ranges that the
+ * new idmap intersects with in the process.
+ */
+func (m *IdmapSet) AddSafe(i IdmapEntry) error {
+ result := []IdmapEntry{}
+ added := false
+ for _, e := range m.Idmap {
+ if !e.Intersects(i) {
+ result = append(result, e)
+ continue
+ }
+
+ if e.HostidsIntersect(i) {
+ return fmt.Errorf("can't map the same host UID twice")
+ }
+
+ added = true
+
+ lower := IdmapEntry{
+ Isuid: e.Isuid,
+ Isgid: e.Isgid,
+ Hostid: e.Hostid,
+ Nsid: e.Nsid,
+ Maprange: i.Nsid - e.Nsid,
+ }
+
+ upper := IdmapEntry{
+ Isuid: e.Isuid,
+ Isgid: e.Isgid,
+ Hostid: e.Hostid + lower.Maprange + i.Maprange,
+ Nsid: i.Nsid + i.Maprange,
+ Maprange: e.Maprange - i.Maprange - lower.Maprange,
+ }
+
+ if lower.Maprange > 0 {
+ result = append(result, lower)
+ }
+ result = append(result, i)
+ if upper.Maprange > 0 {
+ result = append(result, upper)
+ }
+ }
+
+ if !added {
+ result = append(result, i)
+ }
+
+ m.Idmap = result
+ return nil
+}
+
func (m IdmapSet) ToLxcString() []string {
var lines []string
for _, e := range m.Idmap {
diff --git a/shared/idmapset_test.go b/shared/idmapset_test.go
new file mode 100644
index 0000000..a0e6280
--- /dev/null
+++ b/shared/idmapset_test.go
@@ -0,0 +1,83 @@
+package shared
+
+import (
+ "fmt"
+ "testing"
+)
+
+func TestIdmapSetAddSafe_split(t *testing.T) {
+ orig := IdmapSet{Idmap: []IdmapEntry{IdmapEntry{Isuid: true, Hostid: 1000, Nsid: 0, Maprange: 1000}}}
+
+ if err := orig.AddSafe(IdmapEntry{Isuid: true, Hostid: 500, Nsid: 500, Maprange: 10}); err != nil {
+ t.Error(err)
+ return
+ }
+
+ if orig.Idmap[0].Hostid != 1000 || orig.Idmap[0].Nsid != 0 || orig.Idmap[0].Maprange != 500 {
+ t.Error(fmt.Errorf("bad range: %v", orig.Idmap[0]))
+ return
+ }
+
+ if orig.Idmap[1].Hostid != 500 || orig.Idmap[1].Nsid != 500 || orig.Idmap[1].Maprange != 10 {
+ t.Error(fmt.Errorf("bad range: %v", orig.Idmap[1]))
+ return
+ }
+
+ if orig.Idmap[2].Hostid != 1510 || orig.Idmap[2].Nsid != 510 || orig.Idmap[2].Maprange != 490 {
+ t.Error(fmt.Errorf("bad range: %v", orig.Idmap[2]))
+ return
+ }
+
+ if len(orig.Idmap) != 3 {
+ t.Error("too many idmap entries")
+ return
+ }
+}
+
+func TestIdmapSetAddSafe_lower(t *testing.T) {
+ orig := IdmapSet{Idmap: []IdmapEntry{IdmapEntry{Isuid: true, Hostid: 1000, Nsid: 0, Maprange: 1000}}}
+
+ if err := orig.AddSafe(IdmapEntry{Isuid: true, Hostid: 500, Nsid: 0, Maprange: 10}); err != nil {
+ t.Error(err)
+ return
+ }
+
+ if orig.Idmap[0].Hostid != 500 || orig.Idmap[0].Nsid != 0 || orig.Idmap[0].Maprange != 10 {
+ t.Error(fmt.Errorf("bad range: %v", orig.Idmap[0]))
+ return
+ }
+
+ if orig.Idmap[1].Hostid != 1010 || orig.Idmap[1].Nsid != 10 || orig.Idmap[1].Maprange != 990 {
+ t.Error(fmt.Errorf("bad range: %v", orig.Idmap[1]))
+ return
+ }
+
+ if len(orig.Idmap) != 2 {
+ t.Error("too many idmap entries")
+ return
+ }
+}
+
+func TestIdmapSetAddSafe_upper(t *testing.T) {
+ orig := IdmapSet{Idmap: []IdmapEntry{IdmapEntry{Isuid: true, Hostid: 1000, Nsid: 0, Maprange: 1000}}}
+
+ if err := orig.AddSafe(IdmapEntry{Isuid: true, Hostid: 500, Nsid: 995, Maprange: 10}); err != nil {
+ t.Error(err)
+ return
+ }
+
+ if orig.Idmap[0].Hostid != 1000 || orig.Idmap[0].Nsid != 0 || orig.Idmap[0].Maprange != 995 {
+ t.Error(fmt.Errorf("bad range: %v", orig.Idmap[0]))
+ return
+ }
+
+ if orig.Idmap[1].Hostid != 500 || orig.Idmap[1].Nsid != 995 || orig.Idmap[1].Maprange != 10 {
+ t.Error(fmt.Errorf("bad range: %v", orig.Idmap[1]))
+ return
+ }
+
+ if len(orig.Idmap) != 2 {
+ t.Error("too many idmap entries")
+ return
+ }
+}
diff --git a/test/suites/filemanip.sh b/test/suites/filemanip.sh
index 64503a6..171d3b4 100644
--- a/test/suites/filemanip.sh
+++ b/test/suites/filemanip.sh
@@ -44,4 +44,9 @@ test_filemanip() {
[ "$(lxc exec filemanip cat /foo)" = "foo" ]
lxc delete filemanip -f
+
+ if [ "${LXD_BACKEND}" != "lvm" ]; then
+ lxc launch testimage idmap -c "raw.idmap=\"both 0 0\""
+ [ "$(stat -c %u "${LXD_DIR}/containers/idmap/rootfs")" = "0" ]
+ fi
}
From 6f5e21b7c05f9df907b03aac19e5f5886e384a91 Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Wed, 16 Nov 2016 17:57:21 -0700
Subject: [PATCH 3/3] add configuration information about idmap
Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
doc/configuration.md | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/doc/configuration.md b/doc/configuration.md
index 16aba91..d33a833 100644
--- a/doc/configuration.md
+++ b/doc/configuration.md
@@ -89,6 +89,9 @@ linux.kernel\_modules | string | - | yes
raw.apparmor | blob | - | yes | - | Apparmor profile entries to be appended to the generated profile
raw.lxc | blob | - | no | - | Raw LXC configuration to be appended to the generated one
raw.seccomp | blob | - | no | container\_syscall\_filtering | Raw Seccomp configuration
+raw.idmap | blob | - | no | id\_map | Raw idmap configuration (e.g. "both 1000 1000")
+security.idmap.isolated | boolean | false | no | id\_map | Use an idmap for this container that is unique among containers with isolated set.
+security.idmap.size | integer | - | no | id\_map | The size of the idmap to use
security.nesting | boolean | false | yes | - | Support running lxd (nested) inside the container
security.privileged | boolean | false | no | - | Runs the container in privileged mode
security.syscalls.blacklist\_default | boolean | true | no | container\_syscall\_filtering | Enables the default syscall blacklist
@@ -105,6 +108,8 @@ volatile.\<name\>.hwaddr | string | - | Network device MAC add
volatile.\<name\>.name | string | - | Network device name (when no name propery is set on the device itself)
volatile.apply\_template | string | - | The name of a template hook which should be triggered upon next startup
volatile.base\_image | string | - | The hash of the image the container was created from, if any.
+volatile.idmap.base | integer | - | The first id in the container's primary idmap range
+volatile.idmap.next | string | - | The idmap to use next time the container starts
volatile.last\_state.idmap | string | - | Serialized container uid/gid map
volatile.last\_state.power | string | - | Container state as of last host shutdown
More information about the lxc-devel
mailing list