[lxc-devel] [lxd/master] [RFC] Project restrictions

freeekanayaka on Github lxc-bot at linuxcontainers.org
Fri Feb 28 10:27:15 UTC 2020


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 960 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20200228/75a619a3/attachment-0001.bin>
-------------- next part --------------
From 1e537fae51eaaaad60e388573a396f4b74f81fca Mon Sep 17 00:00:00 2001
From: Free Ekanayaka <free.ekanayaka at canonical.com>
Date: Fri, 28 Feb 2020 09:21:11 +0000
Subject: [PATCH 01/10] lxd/project: Rename limits.go to permissions.go

Signed-off-by: Free Ekanayaka <free.ekanayaka at canonical.com>
---
 lxd/project/{limits.go => permissions.go}           | 0
 lxd/project/{limits_test.go => permissions_test.go} | 0
 2 files changed, 0 insertions(+), 0 deletions(-)
 rename lxd/project/{limits.go => permissions.go} (100%)
 rename lxd/project/{limits_test.go => permissions_test.go} (100%)

diff --git a/lxd/project/limits.go b/lxd/project/permissions.go
similarity index 100%
rename from lxd/project/limits.go
rename to lxd/project/permissions.go
diff --git a/lxd/project/limits_test.go b/lxd/project/permissions_test.go
similarity index 100%
rename from lxd/project/limits_test.go
rename to lxd/project/permissions_test.go

From 4083752241b40f864ee91ae5c12f9b66c56e1b14 Mon Sep 17 00:00:00 2001
From: Free Ekanayaka <free.ekanayaka at canonical.com>
Date: Fri, 28 Feb 2020 09:22:48 +0000
Subject: [PATCH 02/10] lxd/project: Rename CheckLimitsUponInstanceCreation to
 AllowInstanceCreation

Signed-off-by: Free Ekanayaka <free.ekanayaka at canonical.com>
---
 lxd/containers_post.go          |  4 ++--
 lxd/project/permissions.go      |  6 +++---
 lxd/project/permissions_test.go | 16 ++++++++--------
 3 files changed, 13 insertions(+), 13 deletions(-)

diff --git a/lxd/containers_post.go b/lxd/containers_post.go
index 4836d1965a..be1ef20666 100644
--- a/lxd/containers_post.go
+++ b/lxd/containers_post.go
@@ -13,7 +13,7 @@ import (
 	"os"
 	"strings"
 
-	"github.com/dustinkirkland/golang-petname"
+	petname "github.com/dustinkirkland/golang-petname"
 	"github.com/gorilla/websocket"
 	"github.com/pkg/errors"
 
@@ -825,7 +825,7 @@ func containersPost(d *Daemon, r *http.Request) response.Response {
 			}
 		}
 
-		err := projecthelpers.CheckLimitsUponInstanceCreation(tx, project, req)
+		err := projecthelpers.AllowInstanceCreation(tx, project, req)
 		if err != nil {
 			return err
 		}
diff --git a/lxd/project/permissions.go b/lxd/project/permissions.go
index f2df2467c6..b74b918feb 100644
--- a/lxd/project/permissions.go
+++ b/lxd/project/permissions.go
@@ -13,9 +13,9 @@ import (
 	"github.com/pkg/errors"
 )
 
-// CheckLimitsUponInstanceCreation returns an error if any project-specific
-// limit is violated when creating a new instance.
-func CheckLimitsUponInstanceCreation(tx *db.ClusterTx, projectName string, req api.InstancesPost) error {
+// AllowInstanceCreation returns an error if any project-specific limit or
+// restriction is violated when creating a new instance.
+func AllowInstanceCreation(tx *db.ClusterTx, projectName string, req api.InstancesPost) error {
 	project, profiles, instances, err := fetchProject(tx, projectName, true)
 	if err != nil {
 		return err
diff --git a/lxd/project/permissions_test.go b/lxd/project/permissions_test.go
index 89f96f4bed..d81f4fee26 100644
--- a/lxd/project/permissions_test.go
+++ b/lxd/project/permissions_test.go
@@ -12,7 +12,7 @@ import (
 )
 
 // If there's no limit configured on the project, the check passes.
-func TestCheckLimitsUponInstanceCreation_NotConfigured(t *testing.T) {
+func TestAllowInstanceCreation_NotConfigured(t *testing.T) {
 	tx, cleanup := db.NewTestClusterTx(t)
 	defer cleanup()
 
@@ -20,12 +20,12 @@ func TestCheckLimitsUponInstanceCreation_NotConfigured(t *testing.T) {
 		Name: "c1",
 		Type: api.InstanceTypeContainer,
 	}
-	err := project.CheckLimitsUponInstanceCreation(tx, "default", req)
+	err := project.AllowInstanceCreation(tx, "default", req)
 	assert.NoError(t, err)
 }
 
 // If a limit is configured and the current number of instances is below it, the check passes.
-func TestCheckLimitsUponInstanceCreation_Below(t *testing.T) {
+func TestAllowInstanceCreation_Below(t *testing.T) {
 	tx, cleanup := db.NewTestClusterTx(t)
 	defer cleanup()
 
@@ -53,13 +53,13 @@ func TestCheckLimitsUponInstanceCreation_Below(t *testing.T) {
 		Type: api.InstanceTypeContainer,
 	}
 
-	err = project.CheckLimitsUponInstanceCreation(tx, "p1", req)
+	err = project.AllowInstanceCreation(tx, "p1", req)
 	assert.NoError(t, err)
 }
 
 // If a limit is configured and it matches the current number of instances, the
 // check fails.
-func TestCheckLimitsUponInstanceCreation_Above(t *testing.T) {
+func TestAllowInstanceCreation_Above(t *testing.T) {
 	tx, cleanup := db.NewTestClusterTx(t)
 	defer cleanup()
 
@@ -87,13 +87,13 @@ func TestCheckLimitsUponInstanceCreation_Above(t *testing.T) {
 		Type: api.InstanceTypeContainer,
 	}
 
-	err = project.CheckLimitsUponInstanceCreation(tx, "p1", req)
+	err = project.AllowInstanceCreation(tx, "p1", req)
 	assert.EqualError(t, err, "Reached maximum number of instances of type container in project p1")
 }
 
 // If a limit is configured, but for a different instance type, the check
 // passes.
-func TestCheckLimitsUponInstanceCreation_DifferentType(t *testing.T) {
+func TestAllowInstanceCreation_DifferentType(t *testing.T) {
 	tx, cleanup := db.NewTestClusterTx(t)
 	defer cleanup()
 
@@ -121,6 +121,6 @@ func TestCheckLimitsUponInstanceCreation_DifferentType(t *testing.T) {
 		Type: api.InstanceTypeContainer,
 	}
 
-	err = project.CheckLimitsUponInstanceCreation(tx, "p1", req)
+	err = project.AllowInstanceCreation(tx, "p1", req)
 	assert.NoError(t, err)
 }

From 4f71e94f5873bbfd4552f82470c6743061390f0c Mon Sep 17 00:00:00 2001
From: Free Ekanayaka <free.ekanayaka at canonical.com>
Date: Fri, 28 Feb 2020 09:23:45 +0000
Subject: [PATCH 03/10] lxd/project: Rename CheckLimitsUponInstanceUpdate to
 AllowInstanceUpdate

Signed-off-by: Free Ekanayaka <free.ekanayaka at canonical.com>
---
 lxd/container_patch.go     | 2 +-
 lxd/container_put.go       | 2 +-
 lxd/project/permissions.go | 6 +++---
 3 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/lxd/container_patch.go b/lxd/container_patch.go
index cbc609b994..cbd36fad56 100644
--- a/lxd/container_patch.go
+++ b/lxd/container_patch.go
@@ -123,7 +123,7 @@ func containerPatch(d *Daemon, r *http.Request) response.Response {
 
 	// Check project limits.
 	err = d.cluster.Transaction(func(tx *db.ClusterTx) error {
-		return projecthelpers.CheckLimitsUponInstanceUpdate(tx, project, name, req)
+		return projecthelpers.AllowInstanceUpdate(tx, project, name, req)
 	})
 	if err != nil {
 		return response.SmartError(err)
diff --git a/lxd/container_put.go b/lxd/container_put.go
index 443d6ad503..8cd0130301 100644
--- a/lxd/container_put.go
+++ b/lxd/container_put.go
@@ -68,7 +68,7 @@ func containerPut(d *Daemon, r *http.Request) response.Response {
 
 	// Check project limits.
 	err = d.cluster.Transaction(func(tx *db.ClusterTx) error {
-		return projecthelpers.CheckLimitsUponInstanceUpdate(tx, project, name, configRaw)
+		return projecthelpers.AllowInstanceUpdate(tx, project, name, configRaw)
 	})
 	if err != nil {
 		return response.SmartError(err)
diff --git a/lxd/project/permissions.go b/lxd/project/permissions.go
index b74b918feb..c1ebda6590 100644
--- a/lxd/project/permissions.go
+++ b/lxd/project/permissions.go
@@ -109,9 +109,9 @@ func checkAggregateInstanceLimits(tx *db.ClusterTx, project *api.Project, instan
 	return nil
 }
 
-// CheckLimitsUponInstanceUpdate returns an error if any project-specific limit
-// is violated when updating an existing instance.
-func CheckLimitsUponInstanceUpdate(tx *db.ClusterTx, projectName, instanceName string, req api.InstancePut) error {
+// AllowInstanceUpdate returns an error if any project-specific limit or
+// restriction is violated when updating an existing instance.
+func AllowInstanceUpdate(tx *db.ClusterTx, projectName, instanceName string, req api.InstancePut) error {
 	project, profiles, instances, err := fetchProject(tx, projectName, true)
 	if err != nil {
 		return err

From 60de388f9af6fce5764e2078dee98a189bcec927 Mon Sep 17 00:00:00 2001
From: Free Ekanayaka <free.ekanayaka at canonical.com>
Date: Fri, 28 Feb 2020 09:24:53 +0000
Subject: [PATCH 04/10] lxd/project: Rename CheckLimitsUponProfileUpdate to
 AllowProfileUpdate

Signed-off-by: Free Ekanayaka <free.ekanayaka at canonical.com>
---
 lxd/profiles_utils.go      | 2 +-
 lxd/project/permissions.go | 6 +++---
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/lxd/profiles_utils.go b/lxd/profiles_utils.go
index e506d56e64..cdbfc2d623 100644
--- a/lxd/profiles_utils.go
+++ b/lxd/profiles_utils.go
@@ -18,7 +18,7 @@ import (
 func doProfileUpdate(d *Daemon, project, name string, id int64, profile *api.Profile, req api.ProfilePut) error {
 	// Check project limits.
 	err := d.cluster.Transaction(func(tx *db.ClusterTx) error {
-		return projecthelpers.CheckLimitsUponProfileUpdate(tx, project, name, req)
+		return projecthelpers.AllowProfileUpdate(tx, project, name, req)
 	})
 	if err != nil {
 		return err
diff --git a/lxd/project/permissions.go b/lxd/project/permissions.go
index c1ebda6590..11845c1be2 100644
--- a/lxd/project/permissions.go
+++ b/lxd/project/permissions.go
@@ -137,9 +137,9 @@ func AllowInstanceUpdate(tx *db.ClusterTx, projectName, instanceName string, req
 	return nil
 }
 
-// CheckLimitsUponProfileUpdate checks that project limits are not violated
-// when changing a profile.
-func CheckLimitsUponProfileUpdate(tx *db.ClusterTx, projectName, profileName string, req api.ProfilePut) error {
+// AllowProfileUpdate checks that project limits and restrictions are not
+// violated when changing a profile.
+func AllowProfileUpdate(tx *db.ClusterTx, projectName, profileName string, req api.ProfilePut) error {
 	project, profiles, instances, err := fetchProject(tx, projectName, true)
 	if err != nil {
 		return err

From 029593a83abc9b1192fdffbf285a2b29a094f3df Mon Sep 17 00:00:00 2001
From: Free Ekanayaka <free.ekanayaka at canonical.com>
Date: Fri, 28 Feb 2020 09:25:56 +0000
Subject: [PATCH 05/10] lxd/project: Rename ValidateLimitsUponProjectUpdate to
 AllowProjectUpdate

Signed-off-by: Free Ekanayaka <free.ekanayaka at canonical.com>
---
 lxd/api_project.go         | 2 +-
 lxd/project/permissions.go | 5 ++---
 2 files changed, 3 insertions(+), 4 deletions(-)

diff --git a/lxd/api_project.go b/lxd/api_project.go
index 32c0ce17b7..da215c2004 100644
--- a/lxd/api_project.go
+++ b/lxd/api_project.go
@@ -358,7 +358,7 @@ func projectChange(d *Daemon, project *api.Project, req api.ProjectPut) response
 
 	// Update the database entry
 	err = d.cluster.Transaction(func(tx *db.ClusterTx) error {
-		err := projecthelpers.ValidateLimitsUponProjectUpdate(tx, project.Name, req.Config, configChanged)
+		err := projecthelpers.AllowProjectUpdate(tx, project.Name, req.Config, configChanged)
 		if err != nil {
 			return err
 		}
diff --git a/lxd/project/permissions.go b/lxd/project/permissions.go
index 11845c1be2..c1a6a722fd 100644
--- a/lxd/project/permissions.go
+++ b/lxd/project/permissions.go
@@ -164,9 +164,8 @@ func AllowProfileUpdate(tx *db.ClusterTx, projectName, profileName string, req a
 	return nil
 }
 
-// ValidateLimitsUponProjectUpdate checks the new limits to be set on a project
-// are valid.
-func ValidateLimitsUponProjectUpdate(tx *db.ClusterTx, projectName string, config map[string]string, changed []string) error {
+// AllowProjectUpdate checks the new config to be set on a project is valid.
+func AllowProjectUpdate(tx *db.ClusterTx, projectName string, config map[string]string, changed []string) error {
 	_, profiles, instances, err := fetchProject(tx, projectName, false)
 	if err != nil {
 		return err

From dc6bd336f0665388cf983933cdf205a682561296 Mon Sep 17 00:00:00 2001
From: Free Ekanayaka <free.ekanayaka at canonical.com>
Date: Fri, 28 Feb 2020 09:28:24 +0000
Subject: [PATCH 06/10] lxd/project: Rename checkAggregateInstanceLimits to
 checkRestrictionsAndAggregateLimits

Signed-off-by: Free Ekanayaka <free.ekanayaka at canonical.com>
---
 lxd/project/permissions.go | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/lxd/project/permissions.go b/lxd/project/permissions.go
index c1a6a722fd..7c9b4eded7 100644
--- a/lxd/project/permissions.go
+++ b/lxd/project/permissions.go
@@ -35,7 +35,7 @@ func AllowInstanceCreation(tx *db.ClusterTx, projectName string, req api.Instanc
 		Config:   req.Config,
 	})
 
-	err = checkAggregateInstanceLimits(tx, project, instances, profiles)
+	err = checkRestrictionsAndAggregateLimits(tx, project, instances, profiles)
 	if err != nil {
 		return err
 	}
@@ -71,9 +71,9 @@ func checkInstanceCountLimit(project *api.Project, instanceCount int, instanceTy
 	return nil
 }
 
-// Check that we would not violate the project limits if we were to commit the
-// given instances and profiles.
-func checkAggregateInstanceLimits(tx *db.ClusterTx, project *api.Project, instances []db.Instance, profiles []db.Profile) error {
+// Check that we would not violate the project limits or restrictions if we
+// were to commit the given instances and profiles.
+func checkRestrictionsAndAggregateLimits(tx *db.ClusterTx, project *api.Project, instances []db.Instance, profiles []db.Profile) error {
 	// List of config keys for which we need to check aggregate values
 	// across all project instances.
 	aggregateKeys := []string{}
@@ -129,7 +129,7 @@ func AllowInstanceUpdate(tx *db.ClusterTx, projectName, instanceName string, req
 		instances[i].Config = req.Config
 	}
 
-	err = checkAggregateInstanceLimits(tx, project, instances, profiles)
+	err = checkRestrictionsAndAggregateLimits(tx, project, instances, profiles)
 	if err != nil {
 		return err
 	}
@@ -156,7 +156,7 @@ func AllowProfileUpdate(tx *db.ClusterTx, projectName, profileName string, req a
 		profiles[i].Config = req.Config
 	}
 
-	err = checkAggregateInstanceLimits(tx, project, instances, profiles)
+	err = checkRestrictionsAndAggregateLimits(tx, project, instances, profiles)
 	if err != nil {
 		return err
 	}

From 89ef6ab892ab66e333f1e11a5ad514179826ca0c Mon Sep 17 00:00:00 2001
From: Free Ekanayaka <free.ekanayaka at canonical.com>
Date: Fri, 28 Feb 2020 09:35:06 +0000
Subject: [PATCH 07/10] lxd/project: Extract checkAggregateLimits from
 checkRestrictionsAndAggregateLimits

Signed-off-by: Free Ekanayaka <free.ekanayaka at canonical.com>
---
 lxd/project/permissions.go | 19 +++++++++++++++++--
 1 file changed, 17 insertions(+), 2 deletions(-)

diff --git a/lxd/project/permissions.go b/lxd/project/permissions.go
index 7c9b4eded7..d5af664078 100644
--- a/lxd/project/permissions.go
+++ b/lxd/project/permissions.go
@@ -78,16 +78,26 @@ func checkRestrictionsAndAggregateLimits(tx *db.ClusterTx, project *api.Project,
 	// across all project instances.
 	aggregateKeys := []string{}
 	for key := range project.Config {
-		if shared.StringInSlice(key, []string{"limits.memory", "limits.processes", "limits.cpu"}) {
+		if shared.StringInSlice(key, allAggregateLimits) {
 			aggregateKeys = append(aggregateKeys, key)
 		}
 	}
+
 	if len(aggregateKeys) == 0 {
 		return nil
 	}
 
 	instances = expandInstancesConfig(instances, profiles)
 
+	err := checkAggregateLimits(project, instances, aggregateKeys)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func checkAggregateLimits(project *api.Project, instances []db.Instance, aggregateKeys []string) error {
 	totals, err := getTotalsAcrossInstances(instances, aggregateKeys)
 	if err != nil {
 		return err
@@ -105,10 +115,15 @@ func checkRestrictionsAndAggregateLimits(tx *db.ClusterTx, project *api.Project,
 				project.Config[key], key, project.Name)
 		}
 	}
-
 	return nil
 }
 
+var allAggregateLimits = []string{
+	"limits.cpu",
+	"limits.memory",
+	"limits.processes",
+}
+
 // AllowInstanceUpdate returns an error if any project-specific limit or
 // restriction is violated when updating an existing instance.
 func AllowInstanceUpdate(tx *db.ClusterTx, projectName, instanceName string, req api.InstancePut) error {

From 1d6d91251bcf9b3b9912d34e39b609ebf9ed9064 Mon Sep 17 00:00:00 2001
From: Free Ekanayaka <free.ekanayaka at canonical.com>
Date: Fri, 28 Feb 2020 09:56:46 +0000
Subject: [PATCH 08/10] lxd/project: Honor the "restrict.containers.nesting"
 config

Signed-off-by: Free Ekanayaka <free.ekanayaka at canonical.com>
---
 lxd/project/permissions.go | 72 +++++++++++++++++++++++++++++++++++---
 1 file changed, 67 insertions(+), 5 deletions(-)

diff --git a/lxd/project/permissions.go b/lxd/project/permissions.go
index d5af664078..bfcdad0059 100644
--- a/lxd/project/permissions.go
+++ b/lxd/project/permissions.go
@@ -31,6 +31,7 @@ func AllowInstanceCreation(tx *db.ClusterTx, projectName string, req api.Instanc
 
 	// Add the instance being created.
 	instances = append(instances, db.Instance{
+		Name:     req.Name,
 		Profiles: req.Profiles,
 		Config:   req.Config,
 	})
@@ -83,7 +84,15 @@ func checkRestrictionsAndAggregateLimits(tx *db.ClusterTx, project *api.Project,
 		}
 	}
 
-	if len(aggregateKeys) == 0 {
+	// List of restriction-related keys.
+	restrictionKeys := []string{}
+	for key := range project.Config {
+		if shared.StringInSlice(key, allRestrictions) {
+			restrictionKeys = append(restrictionKeys, key)
+		}
+	}
+
+	if len(aggregateKeys) == 0 && len(restrictionKeys) == 0 {
 		return nil
 	}
 
@@ -94,10 +103,19 @@ func checkRestrictionsAndAggregateLimits(tx *db.ClusterTx, project *api.Project,
 		return err
 	}
 
+	err = checkRestrictions(project, instances, restrictionKeys)
+	if err != nil {
+		return err
+	}
+
 	return nil
 }
 
 func checkAggregateLimits(project *api.Project, instances []db.Instance, aggregateKeys []string) error {
+	if len(aggregateKeys) == 0 {
+		return nil
+	}
+
 	totals, err := getTotalsAcrossInstances(instances, aggregateKeys)
 	if err != nil {
 		return err
@@ -118,12 +136,56 @@ func checkAggregateLimits(project *api.Project, instances []db.Instance, aggrega
 	return nil
 }
 
+func checkRestrictions(project *api.Project, instances []db.Instance, restrictionKeys []string) error {
+	if len(restrictionKeys) == 0 {
+		return nil
+	}
+
+	containerKeyChecks := map[string]func(value string) error{}
+
+	for _, key := range restrictionKeys {
+		switch key {
+		case "restrict.containers.nesting":
+			containerKeyChecks["security.nesting"] = func(value string) error {
+				if !shared.IsTrue(value) {
+					return nil
+				}
+				return fmt.Errorf("Container nesting is forbidden")
+			}
+		}
+	}
+
+	for _, instance := range instances {
+		if instance.Type == instancetype.Container {
+			for key, value := range instance.Config {
+				checker, ok := containerKeyChecks[key]
+				if !ok {
+					continue
+				}
+				err := checker(value)
+				if err != nil {
+					return errors.Wrapf(
+						err,
+						"Invalid value %q for config %q on instance %q of project %q",
+						value, key, instance.Name, project.Name)
+				}
+			}
+		}
+	}
+
+	return nil
+}
+
 var allAggregateLimits = []string{
 	"limits.cpu",
 	"limits.memory",
 	"limits.processes",
 }
 
+var allRestrictions = []string{
+	"restrict.containers.nesting",
+}
+
 // AllowInstanceUpdate returns an error if any project-specific limit or
 // restriction is violated when updating an existing instance.
 func AllowInstanceUpdate(tx *db.ClusterTx, projectName, instanceName string, req api.InstancePut) error {
@@ -277,10 +339,10 @@ func validateAggregateLimit(totals map[string]int64, key, value string) error {
 	return nil
 }
 
-// Return true if the project has some limits.
-func projectHasLimits(project *api.Project) bool {
+// Return true if the project has some limits or restrictions set.
+func projectHasLimitsOrRestrictions(project *api.Project) bool {
 	for k := range project.Config {
-		if strings.HasPrefix(k, "limits.") {
+		if strings.HasPrefix(k, "limits.") || strings.HasPrefix(k, "restrict.") {
 			return true
 		}
 	}
@@ -297,7 +359,7 @@ func fetchProject(tx *db.ClusterTx, projectName string, skipIfNoLimits bool) (*a
 		return nil, nil, nil, errors.Wrap(err, "Fetch project database object")
 	}
 
-	if skipIfNoLimits && !projectHasLimits(project) {
+	if skipIfNoLimits && !projectHasLimitsOrRestrictions(project) {
 		return nil, nil, nil, nil
 	}
 

From e6d2f4b8c7c4e9374a428e38491c0ceb117e0c92 Mon Sep 17 00:00:00 2001
From: Free Ekanayaka <free.ekanayaka at canonical.com>
Date: Fri, 28 Feb 2020 09:10:59 +0000
Subject: [PATCH 09/10] api: Add new restrict.* config keys to projects

Signed-off-by: Free Ekanayaka <free.ekanayaka at canonical.com>
---
 lxd/api_project.go | 15 ++++++++-------
 1 file changed, 8 insertions(+), 7 deletions(-)

diff --git a/lxd/api_project.go b/lxd/api_project.go
index da215c2004..e736671a45 100644
--- a/lxd/api_project.go
+++ b/lxd/api_project.go
@@ -515,13 +515,14 @@ func projectIsEmpty(project *api.Project) bool {
 
 // Validate the project configuration
 var projectConfigKeys = map[string]func(value string) error{
-	"features.profiles":       shared.IsBool,
-	"features.images":         shared.IsBool,
-	"limits.containers":       shared.IsUint32,
-	"limits.virtual-machines": shared.IsUint32,
-	"limits.memory":           shared.IsSize,
-	"limits.processes":        shared.IsUint32,
-	"limits.cpu":              shared.IsUint32,
+	"features.profiles":           shared.IsBool,
+	"features.images":             shared.IsBool,
+	"limits.containers":           shared.IsUint32,
+	"limits.virtual-machines":     shared.IsUint32,
+	"limits.memory":               shared.IsSize,
+	"limits.processes":            shared.IsUint32,
+	"limits.cpu":                  shared.IsUint32,
+	"restrict.containers.nesting": shared.IsBool,
 }
 
 func projectValidateConfig(config map[string]string) error {

From 522f9dda3f8adf0f678db866bcec5b1b1d92390b Mon Sep 17 00:00:00 2001
From: Free Ekanayaka <free.ekanayaka at canonical.com>
Date: Fri, 28 Feb 2020 10:18:04 +0000
Subject: [PATCH 10/10] test: Add projects restrictions tests

Signed-off-by: Free Ekanayaka <free.ekanayaka at canonical.com>
---
 test/main.sh            |  1 +
 test/suites/projects.sh | 25 +++++++++++++++++++++++++
 2 files changed, 26 insertions(+)

diff --git a/test/main.sh b/test/main.sh
index 8c142cdb39..7a56b501b8 100755
--- a/test/main.sh
+++ b/test/main.sh
@@ -193,6 +193,7 @@ run_test test_projects_images_default "images from the global default project"
 run_test test_projects_storage "projects and storage pools"
 run_test test_projects_network "projects and networks"
 run_test test_projects_limits "projects limits"
+run_test test_projects_restrictions "projects restrictions"
 run_test test_container_devices_disk "container devices - disk"
 run_test test_container_devices_nic_p2p "container devices - nic - p2p"
 run_test test_container_devices_nic_bridged "container devices - nic - bridged"
diff --git a/test/suites/projects.sh b/test/suites/projects.sh
index c8060f2d9a..9980f7bee8 100644
--- a/test/suites/projects.sh
+++ b/test/suites/projects.sh
@@ -625,3 +625,28 @@ test_projects_limits() {
   lxc project switch default
   lxc project delete p1
 }
+
+# Set restrictions on projects.
+test_projects_restrictions() {
+  # Create a project
+  lxc project create p1
+
+  lxc project switch p1
+
+  # Add a root device to the default profile of the project and import an image.
+  pool="lxdtest-$(basename "${LXD_DIR}")"
+  lxc profile device add default root disk path="/" pool="${pool}"
+
+  deps/import-busybox --project p1 --alias testimage
+  fingerprint="$(lxc image list -c f --format json | jq -r .[0].fingerprint)"
+
+  # Create a couple of containers in the project.
+  lxc project set p1 restrict.containers.nesting=true
+
+  ! lxc init testimage c1 -c security.nesting=true || false
+
+  lxc image delete testimage
+
+  lxc project switch default
+  lxc project delete p1
+}


More information about the lxc-devel mailing list