The following pull request was submitted through Github.
It can be accessed and reviewed at: https://github.com/lxc/lxd/pull/2622

This e-mail was sent by the LXC bot, direct replies will not reach the author
unless they happen to be subscribed to this list.

=== Description (from pull-request) ===
This adds support for custom idmaps to LXD, and in particular adds a simple mechanism to isolate containers from each others' idmaps entirely.
From 43b2164c38fbae4f736e21b364926610ecbe4bcc Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.ander...@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.ander...@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.ander...@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.ander...@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.ander...@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.ander...@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
 
_______________________________________________
lxc-devel mailing list
lxc-devel@lists.linuxcontainers.org
http://lists.linuxcontainers.org/listinfo/lxc-devel

Reply via email to