[lxc-devel] [lxd/master] Separate all instance logic in AppArmor code
stgraber on Github
lxc-bot at linuxcontainers.org
Thu Jul 16 22:09:06 UTC 2020
A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 301 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20200716/a089f255/attachment-0001.bin>
-------------- next part --------------
From 2b2ee9dd33452f41db38b30fb05a6a2c636413be Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 16 Jul 2020 14:39:14 -0400
Subject: [PATCH 1/2] lxd/sys: Create apparmor/seccomp paths
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
lxd/sys/fs.go | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/lxd/sys/fs.go b/lxd/sys/fs.go
index 88297d5b60..8c000ddcd7 100644
--- a/lxd/sys/fs.go
+++ b/lxd/sys/fs.go
@@ -55,6 +55,10 @@ func (s *OS) initDirs() error {
{s.LogDir, 0700},
{filepath.Join(s.VarDir, "networks"), 0711},
{filepath.Join(s.VarDir, "security"), 0700},
+ {filepath.Join(s.VarDir, "security", "apparmor"), 0700},
+ {filepath.Join(s.VarDir, "security", "apparmor", "cache"), 0700},
+ {filepath.Join(s.VarDir, "security", "apparmor", "profiles"), 0700},
+ {filepath.Join(s.VarDir, "security", "seccomp"), 0700},
{filepath.Join(s.VarDir, "shmounts"), 0711},
// snapshots is 0700 as liblxc does not need to access this.
{filepath.Join(s.VarDir, "snapshots"), 0700},
From 1b9d3152532646756a1a80b74680705753cba4e2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 16 Jul 2020 14:39:41 -0400
Subject: [PATCH 2/2] lxd/apparmor: Split and rename instance functions
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
lxd/apparmor/apparmor.go | 281 ++++++------------
lxd/apparmor/instance.go | 175 +++++++++++
.../{container.go => instance_lxc.go} | 2 +-
lxd/instance/drivers/driver_lxc.go | 20 +-
4 files changed, 281 insertions(+), 197 deletions(-)
create mode 100644 lxd/apparmor/instance.go
rename lxd/apparmor/{container.go => instance_lxc.go} (99%)
diff --git a/lxd/apparmor/apparmor.go b/lxd/apparmor/apparmor.go
index 7598ba1211..e277358567 100644
--- a/lxd/apparmor/apparmor.go
+++ b/lxd/apparmor/apparmor.go
@@ -1,21 +1,16 @@
package apparmor
import (
- "crypto/sha256"
"fmt"
- "io"
- "io/ioutil"
"os"
- "path"
+ "path/filepath"
"strings"
- "github.com/lxc/lxd/lxd/project"
+ "github.com/pkg/errors"
+
"github.com/lxc/lxd/lxd/state"
"github.com/lxc/lxd/shared"
- "github.com/lxc/lxd/shared/logger"
"github.com/lxc/lxd/shared/version"
-
- log "github.com/lxc/lxd/shared/log15"
)
const (
@@ -26,252 +21,139 @@ const (
var aaPath = shared.VarPath("security", "apparmor")
-type instance interface {
- Project() string
- Name() string
- IsNesting() bool
- IsPrivileged() bool
- ExpandedConfig() map[string]string
-}
-
-func mkApparmorName(name string) string {
- if len(name)+7 >= 253 {
- hash := sha256.New()
- io.WriteString(hash, name)
- return fmt.Sprintf("%x", hash.Sum(nil))
- }
-
- return name
-}
-
-// Namespace returns the instance's apparmor namespace.
-func Namespace(c instance) string {
- /* / is not allowed in apparmor namespace names; let's also trim the
- * leading / so it doesn't look like "-var-lib-lxd"
- */
- lxddir := strings.Replace(strings.Trim(shared.VarPath(""), "/"), "/", "-", -1)
- lxddir = mkApparmorName(lxddir)
- name := project.Instance(c.Project(), c.Name())
- return fmt.Sprintf("lxd-%s_<%s>", name, lxddir)
-}
-
-// ProfileFull returns the instance's apparmor profile.
-func ProfileFull(c instance) string {
- lxddir := shared.VarPath("")
- lxddir = mkApparmorName(lxddir)
- name := project.Instance(c.Project(), c.Name())
- return fmt.Sprintf("lxd-%s_<%s>", name, lxddir)
-}
-
-func profileShort(c instance) string {
- name := project.Instance(c.Project(), c.Name())
- return fmt.Sprintf("lxd-%s", name)
-}
-
-// profileContent generates the apparmor profile template from the given container.
-// This includes the stock lxc includes as well as stuff from raw.apparmor.
-func profileContent(state *state.State, c instance) (string, error) {
- // Prepare raw.apparmor.
- rawContent := ""
- rawApparmor, ok := c.ExpandedConfig()["raw.apparmor"]
- if ok {
- for _, line := range strings.Split(strings.Trim(rawApparmor, "\n"), "\n") {
- rawContent += fmt.Sprintf(" %s\n", line)
- }
- }
-
- // Render the profile.
- var sb *strings.Builder = &strings.Builder{}
- err := containerProfile.Execute(sb, map[string]interface{}{
- "feature_unix": parserSupports("unix"),
- "feature_cgns": shared.PathExists("/proc/self/ns/cgroup"),
- "feature_stacking": state.OS.AppArmorStacking && !state.OS.AppArmorStacked,
- "namespace": Namespace(c),
- "nesting": c.IsNesting(),
- "name": ProfileFull(c),
- "unprivileged": !c.IsPrivileged() || state.OS.RunningInUserNS,
- "raw": rawContent,
- })
- if err != nil {
- return "", err
- }
-
- return sb.String(), nil
-}
-
-func runApparmor(state *state.State, command string, c instance) error {
+// runApparmor runs the relevant AppArmor command.
+func runApparmor(state *state.State, command string, name string) error {
if !state.OS.AppArmorAvailable {
return nil
}
- output, err := shared.RunCommand("apparmor_parser", []string{
+ _, err := shared.RunCommand("apparmor_parser", []string{
fmt.Sprintf("-%sWL", command),
- path.Join(aaPath, "cache"),
- path.Join(aaPath, "profiles", profileShort(c)),
+ filepath.Join(aaPath, "cache"),
+ filepath.Join(aaPath, "profiles", name),
}...)
if err != nil {
- logger.Error("Running apparmor",
- log.Ctx{"action": command, "output": output, "err": err})
+ return err
}
- return err
+ return nil
}
-func getCacheDir() string {
- basePath := path.Join(aaPath, "cache")
-
- ver, err := getVersion()
- if err != nil {
- logger.Errorf("Unable to get AppArmor version: %v", err)
- return basePath
- }
-
- // Multiple policy cache directories were only added in v2.13.
- minVer, err := version.NewDottedVersion("2.13")
- if err != nil {
- logger.Errorf("Unable to parse AppArmor version 2.13: %v", err)
- return basePath
- }
-
- if ver.Compare(minVer) < 0 {
- return basePath
- }
-
- output, err := shared.RunCommand("apparmor_parser", "-L", basePath, "--print-cache-dir")
- if err != nil {
- logger.Errorf("Unable to get AppArmor cache directory: %v", err)
- return basePath
+// createNamespace creates a new AppArmor namespace.
+func createNamespace(state *state.State, name string) error {
+ if !state.OS.AppArmorAvailable {
+ return nil
}
- return strings.TrimSpace(output)
-}
-
-func mkApparmorNamespace(state *state.State, c instance, namespace string) error {
if !state.OS.AppArmorStacking || state.OS.AppArmorStacked {
return nil
}
- p := path.Join("/sys/kernel/security/apparmor/policy/namespaces", namespace)
- if err := os.Mkdir(p, 0755); !os.IsExist(err) {
+ p := filepath.Join("/sys/kernel/security/apparmor/policy/namespaces", name)
+ err := os.Mkdir(p, 0755)
+ if err != nil && !os.IsExist(err) {
return err
}
return nil
}
-// LoadProfile ensures that the instances's policy is loaded into the kernel so the it can boot.
-func LoadProfile(state *state.State, c instance) error {
- if !state.OS.AppArmorAdmin {
+// deleteNamespace destroys an AppArmor namespace.
+func deleteNamespace(state *state.State, name string) error {
+ if !state.OS.AppArmorAvailable {
return nil
}
- err := mkApparmorNamespace(state, c, Namespace(c))
- if err != nil {
- return err
+ if !state.OS.AppArmorStacking || state.OS.AppArmorStacked {
+ return nil
}
- /* In order to avoid forcing a profile parse (potentially slow) on
- * every container start, let's use apparmor's binary policy cache,
- * which checks mtime of the files to figure out if the policy needs to
- * be regenerated.
- *
- * Since it uses mtimes, we shouldn't just always write out our local
- * apparmor template; instead we should check to see whether the
- * template is the same as ours. If it isn't we should write our
- * version out so that the new changes are reflected and we definitely
- * force a recompile.
- */
- profile := path.Join(aaPath, "profiles", profileShort(c))
- content, err := ioutil.ReadFile(profile)
+ p := filepath.Join("/sys/kernel/security/apparmor/policy/namespaces", name)
+ err := os.Remove(p)
if err != nil && !os.IsNotExist(err) {
return err
}
- updated, err := profileContent(state, c)
- if err != nil {
- return err
- }
-
- if string(content) != string(updated) {
- err = os.MkdirAll(path.Join(aaPath, "cache"), 0700)
- if err != nil {
- return err
- }
-
- err = os.MkdirAll(path.Join(aaPath, "profiles"), 0700)
- if err != nil {
- return err
- }
+ return nil
+}
- err = ioutil.WriteFile(profile, []byte(updated), 0600)
- if err != nil {
- return err
- }
+// parseProfile parses the profile without loading it into the kernel.
+func parseProfile(state *state.State, name string) error {
+ if !state.OS.AppArmorAvailable {
+ return nil
}
- return runApparmor(state, cmdLoad, c)
+ return runApparmor(state, cmdParse, name)
}
-// Destroy ensures that the instances's policy namespace is unloaded to free kernel memory.
-// This does not delete the policy from disk or cache.
-func Destroy(state *state.State, c instance) error {
+// loadProfile loads the AppArmor profile into the kernel.
+func loadProfile(state *state.State, name string) error {
if !state.OS.AppArmorAdmin {
return nil
}
- if state.OS.AppArmorStacking && !state.OS.AppArmorStacked {
- p := path.Join("/sys/kernel/security/apparmor/policy/namespaces", Namespace(c))
- if err := os.Remove(p); err != nil {
- logger.Error("Error removing apparmor namespace", log.Ctx{"err": err, "ns": p})
- }
- }
-
- return runApparmor(state, cmdUnload, c)
+ return runApparmor(state, cmdLoad, name)
}
-// ParseProfile parses the profile without loading it into the kernel.
-func ParseProfile(state *state.State, c instance) error {
+// unloadProfile removes the profile from the kernel.
+func unloadProfile(state *state.State, name string) error {
if !state.OS.AppArmorAvailable {
return nil
}
- return runApparmor(state, cmdParse, c)
+ return runApparmor(state, cmdUnload, name)
}
-// DeleteProfile removes the policy from cache/disk.
-func DeleteProfile(state *state.State, c instance) {
+// deleteProfile unloads and delete profile and cache for a profile.
+func deleteProfile(state *state.State, name string) error {
if !state.OS.AppArmorAdmin {
- return
+ return nil
+ }
+
+ cacheDir, err := getCacheDir()
+ if err != nil {
+ return err
+ }
+
+ err = unloadProfile(state, name)
+ if err != nil {
+ return err
+ }
+
+ err = os.Remove(filepath.Join(cacheDir, name))
+ if err != nil && !os.IsNotExist(err) {
+ return errors.Wrapf(err, "Failed to remove: %s", filepath.Join(cacheDir, name))
}
- /* It's ok if these deletes fail: if the container was never started,
- * we'll have never written a profile or cached it.
- */
- os.Remove(path.Join(getCacheDir(), profileShort(c)))
- os.Remove(path.Join(aaPath, "profiles", profileShort(c)))
+ err = os.Remove(filepath.Join(aaPath, "profiles", name))
+ if err != nil && !os.IsNotExist(err) {
+ return errors.Wrapf(err, "Failed to remove: %s", filepath.Join(aaPath, "profiles", name))
+ }
+
+ return nil
}
-func parserSupports(feature string) bool {
+// parserSupports checks if the parser supports a particular feature.
+func parserSupports(feature string) (bool, error) {
ver, err := getVersion()
if err != nil {
- logger.Errorf("Unable to get AppArmor version: %v", err)
- return false
+ return false, err
}
if feature == "unix" {
minVer, err := version.NewDottedVersion("2.10.95")
if err != nil {
- logger.Errorf("Unable to parse AppArmor version 2.10.95: %v", err)
- return false
+ return false, err
}
- return ver.Compare(minVer) >= 0
+ return ver.Compare(minVer) >= 0, nil
}
- return false
+ return false, nil
}
+// getVersion reads and parses the AppArmor version.
func getVersion() (*version.DottedVersion, error) {
out, err := shared.RunCommand("apparmor_parser", "--version")
if err != nil {
@@ -281,3 +163,30 @@ func getVersion() (*version.DottedVersion, error) {
fields := strings.Fields(strings.Split(out, "\n")[0])
return version.NewDottedVersion(fields[len(fields)-1])
}
+
+// getCacheDir returns the applicable AppArmor cache directory.
+func getCacheDir() (string, error) {
+ basePath := filepath.Join(aaPath, "cache")
+
+ ver, err := getVersion()
+ if err != nil {
+ return "", err
+ }
+
+ // Multiple policy cache directories were only added in v2.13.
+ minVer, err := version.NewDottedVersion("2.13")
+ if err != nil {
+ return "", err
+ }
+
+ if ver.Compare(minVer) < 0 {
+ return basePath, nil
+ }
+
+ output, err := shared.RunCommand("apparmor_parser", "-L", basePath, "--print-cache-dir")
+ if err != nil {
+ return "", err
+ }
+
+ return strings.TrimSpace(output), nil
+}
diff --git a/lxd/apparmor/instance.go b/lxd/apparmor/instance.go
new file mode 100644
index 0000000000..9bdd93fb62
--- /dev/null
+++ b/lxd/apparmor/instance.go
@@ -0,0 +1,175 @@
+package apparmor
+
+import (
+ "crypto/sha256"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "strings"
+
+ "github.com/lxc/lxd/lxd/project"
+ "github.com/lxc/lxd/lxd/state"
+ "github.com/lxc/lxd/shared"
+)
+
+// Internal copy of the instance interface.
+type instance interface {
+ Project() string
+ Name() string
+ IsNesting() bool
+ IsPrivileged() bool
+ ExpandedConfig() map[string]string
+}
+
+// InstanceProfileName returns the instance's AppArmor profile name.
+func InstanceProfileName(inst instance) string {
+ path := shared.VarPath("")
+ name := fmt.Sprintf("%s_<%s>", project.Instance(inst.Project(), inst.Name()), path)
+
+ // Max length in AppArmor is 253 chars.
+ if len(name)+4 >= 253 {
+ hash := sha256.New()
+ io.WriteString(hash, name)
+ name = fmt.Sprintf("%x", hash.Sum(nil))
+ }
+
+ return fmt.Sprintf("lxd-%s", name)
+}
+
+// InstanceNamespaceName returns the instance's AppArmor namespace.
+func InstanceNamespaceName(inst instance) string {
+ // Unlike in profile names, / isn't an allowed character so replace with a -.
+ path := strings.Replace(strings.Trim(shared.VarPath(""), "/"), "/", "-", -1)
+ name := fmt.Sprintf("%s_<%s>", project.Instance(inst.Project(), inst.Name()), path)
+
+ // Max length in AppArmor is 253 chars.
+ if len(name)+4 >= 253 {
+ hash := sha256.New()
+ io.WriteString(hash, name)
+ name = fmt.Sprintf("%x", hash.Sum(nil))
+ }
+
+ return fmt.Sprintf("lxd-%s", name)
+}
+
+// instanceProfileFilename returns the name of the on-disk profile name.
+func instanceProfileFilename(inst instance) string {
+ name := project.Instance(inst.Project(), inst.Name())
+
+ // Max length in AppArmor is 253 chars.
+ if len(name)+4 >= 253 {
+ hash := sha256.New()
+ io.WriteString(hash, name)
+ name = fmt.Sprintf("%x", hash.Sum(nil))
+ }
+
+ return fmt.Sprintf("lxd-%s", name)
+}
+
+// InstanceLoad ensures that the instances's policy is loaded into the kernel so the it can boot.
+func InstanceLoad(state *state.State, inst instance) error {
+ err := createNamespace(state, InstanceNamespaceName(inst))
+ if err != nil {
+ return err
+ }
+
+ /* In order to avoid forcing a profile parse (potentially slow) on
+ * every container start, let's use AppArmor's binary policy cache,
+ * which checks mtime of the files to figure out if the policy needs to
+ * be regenerated.
+ *
+ * Since it uses mtimes, we shouldn't just always write out our local
+ * AppArmor template; instead we should check to see whether the
+ * template is the same as ours. If it isn't we should write our
+ * version out so that the new changes are reflected and we definitely
+ * force a recompile.
+ */
+ profile := filepath.Join(aaPath, "profiles", instanceProfileFilename(inst))
+ content, err := ioutil.ReadFile(profile)
+ if err != nil && !os.IsNotExist(err) {
+ return err
+ }
+
+ updated, err := instanceProfile(state, inst)
+ if err != nil {
+ return err
+ }
+
+ if string(content) != string(updated) {
+ err = ioutil.WriteFile(profile, []byte(updated), 0600)
+ if err != nil {
+ return err
+ }
+ }
+
+ err = loadProfile(state, instanceProfileFilename(inst))
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// InstanceUnload ensures that the instances's policy namespace is unloaded to free kernel memory.
+// This does not delete the policy from disk or cache.
+func InstanceUnload(state *state.State, inst instance) error {
+ err := deleteNamespace(state, InstanceNamespaceName(inst))
+ if err != nil {
+ return err
+ }
+
+ err = unloadProfile(state, instanceProfileFilename(inst))
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// InstanceParse validates the instance profile.
+func InstanceParse(state *state.State, inst instance) error {
+ return parseProfile(state, instanceProfileFilename(inst))
+}
+
+// InstanceDelete removes the policy from cache/disk.
+func InstanceDelete(state *state.State, inst instance) error {
+ return deleteProfile(state, instanceProfileFilename(inst))
+}
+
+// instanceProfile generates the AppArmor profile template from the given instance.
+func instanceProfile(state *state.State, inst instance) (string, error) {
+ // Prepare raw.apparmor.
+ rawContent := ""
+ rawApparmor, ok := inst.ExpandedConfig()["raw.apparmor"]
+ if ok {
+ for _, line := range strings.Split(strings.Trim(rawApparmor, "\n"), "\n") {
+ rawContent += fmt.Sprintf(" %s\n", line)
+ }
+ }
+
+ // Check for features.
+ unixSupported, err := parserSupports("unix")
+ if err != nil {
+ return "", err
+ }
+
+ // Render the profile.
+ var sb *strings.Builder = &strings.Builder{}
+ err = lxcProfile.Execute(sb, map[string]interface{}{
+ "feature_unix": unixSupported,
+ "feature_cgns": shared.PathExists("/proc/self/ns/cgroup"),
+ "feature_stacking": state.OS.AppArmorStacking && !state.OS.AppArmorStacked,
+ "namespace": InstanceNamespaceName(inst),
+ "nesting": inst.IsNesting(),
+ "name": InstanceProfileName(inst),
+ "unprivileged": !inst.IsPrivileged() || state.OS.RunningInUserNS,
+ "raw": rawContent,
+ })
+ if err != nil {
+ return "", err
+ }
+
+ return sb.String(), nil
+}
diff --git a/lxd/apparmor/container.go b/lxd/apparmor/instance_lxc.go
similarity index 99%
rename from lxd/apparmor/container.go
rename to lxd/apparmor/instance_lxc.go
index 02a08da532..4d6c423b54 100644
--- a/lxd/apparmor/container.go
+++ b/lxd/apparmor/instance_lxc.go
@@ -4,7 +4,7 @@ import (
"text/template"
)
-var containerProfile = template.Must(template.New("containerProfile").Parse(`#include <tunables/global>
+var lxcProfile = template.Must(template.New("lxcProfile").Parse(`#include <tunables/global>
profile "{{ .name }}" flags=(attach_disconnected,mediate_deleted) {
### Base profile
capability,
diff --git a/lxd/instance/drivers/driver_lxc.go b/lxd/instance/drivers/driver_lxc.go
index b5eac46db7..d0a5d59ead 100644
--- a/lxd/instance/drivers/driver_lxc.go
+++ b/lxd/instance/drivers/driver_lxc.go
@@ -977,7 +977,7 @@ func (c *lxc) initLXC(config bool) error {
}
} else {
// If not currently confined, use the container's profile
- profile := apparmor.ProfileFull(c)
+ profile := apparmor.InstanceProfileName(c)
/* In the nesting case, we want to enable the inside
* LXD to load its profile. Unprivileged containers can
@@ -987,7 +987,7 @@ func (c *lxc) initLXC(config bool) error {
* profile.
*/
if c.state.OS.AppArmorStacking && !c.state.OS.AppArmorStacked {
- profile = fmt.Sprintf("%s//&:%s:", profile, apparmor.Namespace(c))
+ profile = fmt.Sprintf("%s//&:%s:", profile, apparmor.InstanceNamespaceName(c))
}
err := lxcSetConfigItem(cc, "lxc.apparmor.profile", profile)
@@ -2474,7 +2474,7 @@ func (c *lxc) onStart(_ map[string]string) error {
}
// Load the container AppArmor profile
- err = apparmor.LoadProfile(c.state, c)
+ err = apparmor.InstanceLoad(c.state, c)
if err != nil {
if ourStart {
c.unmount()
@@ -2488,7 +2488,7 @@ func (c *lxc) onStart(_ map[string]string) error {
// Run any template that needs running
err = c.templateApplyNow(c.localConfig[key])
if err != nil {
- apparmor.Destroy(c.state, c)
+ apparmor.InstanceUnload(c.state, c)
if ourStart {
c.unmount()
}
@@ -2498,7 +2498,7 @@ func (c *lxc) onStart(_ map[string]string) error {
// Remove the volatile key from the DB
err := c.state.Cluster.DeleteInstanceConfigKey(c.id, key)
if err != nil {
- apparmor.Destroy(c.state, c)
+ apparmor.InstanceUnload(c.state, c)
if ourStart {
c.unmount()
}
@@ -2508,7 +2508,7 @@ func (c *lxc) onStart(_ map[string]string) error {
err = c.templateApplyNow("start")
if err != nil {
- apparmor.Destroy(c.state, c)
+ apparmor.InstanceUnload(c.state, c)
if ourStart {
c.unmount()
}
@@ -2856,7 +2856,7 @@ func (c *lxc) onStop(args map[string]string) error {
c.IsRunning()
// Unload the apparmor profile
- err = apparmor.Destroy(c.state, c)
+ err = apparmor.InstanceUnload(c.state, c)
if err != nil {
logger.Error("Failed to destroy apparmor namespace", log.Ctx{"container": c.Name(), "err": err})
}
@@ -3441,7 +3441,7 @@ func (c *lxc) cleanup() {
c.removeDiskDevices()
// Remove the security profiles
- apparmor.DeleteProfile(c.state, c)
+ apparmor.InstanceDelete(c.state, c)
seccomp.DeleteProfile(c)
// Remove the devices path
@@ -4010,7 +4010,7 @@ func (c *lxc) Update(args db.InstanceArgs, userRequested bool) error {
// If apparmor changed, re-validate the apparmor profile
if shared.StringInSlice("raw.apparmor", changedConfig) || shared.StringInSlice("security.nesting", changedConfig) {
- err = apparmor.ParseProfile(c.state, c)
+ err = apparmor.InstanceParse(c.state, c)
if err != nil {
return errors.Wrap(err, "Parse AppArmor profile")
}
@@ -4082,7 +4082,7 @@ func (c *lxc) Update(args db.InstanceArgs, userRequested bool) error {
if key == "raw.apparmor" || key == "security.nesting" {
// Update the AppArmor profile
- err = apparmor.LoadProfile(c.state, c)
+ err = apparmor.InstanceLoad(c.state, c)
if err != nil {
return err
}
More information about the lxc-devel
mailing list