[lxc-devel] [lxd/master] Initial apparmor profile for qemu
stgraber on Github
lxc-bot at linuxcontainers.org
Tue Sep 15 22:54:46 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/20200915/bd2f042d/attachment.bin>
-------------- next part --------------
From ec0e232d3fef873b7441df2c75249e73bec397f2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 15 Sep 2020 15:41:25 -0400
Subject: [PATCH 1/7] shared/subprocess: Set err on non-zero
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>
---
shared/subprocess/proc.go | 3 +++
1 file changed, 3 insertions(+)
diff --git a/shared/subprocess/proc.go b/shared/subprocess/proc.go
index f78b1a1f70..27ee69d7e8 100644
--- a/shared/subprocess/proc.go
+++ b/shared/subprocess/proc.go
@@ -177,6 +177,9 @@ func (p *Process) start(fds []*os.File) error {
exitcode := int64(procstate.ExitCode())
p.exitCode = exitcode
+ if p.exitCode != 0 {
+ p.exitErr = fmt.Errorf("Process exited with a non-zero value")
+ }
close(p.chExit)
}()
From 2bc7219b6078710382c8a8526bc7828737729a49 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 15 Sep 2020 15:25:13 -0400
Subject: [PATCH 2/7] lxd/instances/qemu: Use subprocess
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/instance/drivers/driver_qemu.go | 29 +++++++++++++++++++++--------
1 file changed, 21 insertions(+), 8 deletions(-)
diff --git a/lxd/instance/drivers/driver_qemu.go b/lxd/instance/drivers/driver_qemu.go
index c3b3d80d35..bab4b84d61 100644
--- a/lxd/instance/drivers/driver_qemu.go
+++ b/lxd/instance/drivers/driver_qemu.go
@@ -57,6 +57,7 @@ import (
"github.com/lxc/lxd/shared/logger"
"github.com/lxc/lxd/shared/logging"
"github.com/lxc/lxd/shared/osarch"
+ "github.com/lxc/lxd/shared/subprocess"
"github.com/lxc/lxd/shared/termios"
"github.com/lxc/lxd/shared/units"
)
@@ -827,13 +828,14 @@ func (vm *qemu) Start(stateful bool) error {
forkLimitsCmd = append(forkLimitsCmd, fmt.Sprintf("fd=%d", 3+i))
}
- cmd := exec.Command(vm.state.OS.ExecPath, append(forkLimitsCmd, qemuCmd...)...)
- var stdout bytes.Buffer
- var stderr bytes.Buffer
- cmd.Stdout = &stdout
- cmd.Stderr = &stderr
+ // Setup background process.
+ p, err := subprocess.NewProcess(vm.state.OS.ExecPath, append(forkLimitsCmd, qemuCmd...), vm.EarlyLogFilePath(), vm.EarlyLogFilePath())
+ if err != nil {
+ return err
+ }
// Open any extra files and pass their file handles to qemu command.
+ files := []*os.File{}
for _, file := range fdFiles {
info, err := os.Stat(file)
if err != nil {
@@ -870,12 +872,18 @@ func (vm *qemu) Start(stateful bool) error {
defer f.Close() // Close file after qemu has started.
}
- cmd.ExtraFiles = append(cmd.ExtraFiles, f)
+ files = append(files, f)
}
- err = cmd.Run()
+ err = p.StartWithFiles(files)
if err != nil {
- err = errors.Wrapf(err, "Failed to run: %s: %s", strings.Join(cmd.Args, " "), strings.TrimSpace(string(stderr.Bytes())))
+ return err
+ }
+
+ _, err = p.Wait()
+ if err != nil {
+ stderr, _ := ioutil.ReadFile(vm.EarlyLogFilePath())
+ err = errors.Wrapf(err, "Failed to run: %s: %s", strings.Join(p.Args, " "), string(stderr))
op.Done(err)
return err
}
@@ -4389,6 +4397,11 @@ func (vm *qemu) LogPath() string {
return shared.LogPath(name)
}
+// EarlyLogFilePath returns the instance's early log path.
+func (vm *qemu) EarlyLogFilePath() string {
+ return filepath.Join(vm.LogPath(), "qemu.early.log")
+}
+
// LogFilePath returns the instance's log path.
func (vm *qemu) LogFilePath() string {
return filepath.Join(vm.LogPath(), "qemu.log")
From 85ddc644cf42b184aeac11d1b47cce11c503cb1c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 15 Sep 2020 17:44:48 -0400
Subject: [PATCH 3/7] lxd/instance: Add DevPaths
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/instance/drivers/driver_common.go | 9 +++++++++
lxd/instance/drivers/driver_qemu.go | 7 +++++++
lxd/instance/instance_interface.go | 3 +++
3 files changed, 19 insertions(+)
diff --git a/lxd/instance/drivers/driver_common.go b/lxd/instance/drivers/driver_common.go
index 27d0fd1c81..7f3e9da6dd 100644
--- a/lxd/instance/drivers/driver_common.go
+++ b/lxd/instance/drivers/driver_common.go
@@ -11,6 +11,7 @@ import (
// common provides structure common to all instance types.
type common struct {
dbType instancetype.Type
+ devPaths []string
expandedConfig map[string]string
expandedDevices deviceConfig.Devices
localConfig map[string]string
@@ -77,3 +78,11 @@ func (c *common) expandDevices(profiles []api.Profile) error {
return nil
}
+
+// DevPaths() returns a list of /dev devices which the instance requires access to.
+// This is function is only safe to call from within the security
+// packages as called during instance startup, the rest of the time this
+// will likely return nil.
+func (c *common) DevPaths() []string {
+ return c.devPaths
+}
diff --git a/lxd/instance/drivers/driver_qemu.go b/lxd/instance/drivers/driver_qemu.go
index bab4b84d61..6a5f7cd2f7 100644
--- a/lxd/instance/drivers/driver_qemu.go
+++ b/lxd/instance/drivers/driver_qemu.go
@@ -640,6 +640,9 @@ func (vm *qemu) Start(stateful bool) error {
revert := revert.New()
defer revert.Fail()
+ // Start accumulating device paths.
+ vm.devPaths = []string{}
+
// Mount the instance's config volume.
_, err = vm.mount()
if err != nil {
@@ -2059,6 +2062,10 @@ func (vm *qemu) addDriveConfig(sb *strings.Builder, bootIndexes map[string]int,
}
}
+ if !strings.HasPrefix(driveConf.DevPath, "rbd:") {
+ vm.devPaths = append(vm.devPaths, driveConf.DevPath)
+ }
+
return qemuDrive.Execute(sb, map[string]interface{}{
"devName": driveConf.DevName,
"devPath": driveConf.DevPath,
diff --git a/lxd/instance/instance_interface.go b/lxd/instance/instance_interface.go
index f0c117f6e7..bfe9054a70 100644
--- a/lxd/instance/instance_interface.go
+++ b/lxd/instance/instance_interface.go
@@ -69,6 +69,9 @@ type Instance interface {
Delete() error
Export(w io.Writer, properties map[string]string) (api.ImageMetadata, error)
+ // Used for security.
+ DevPaths() []string
+
// Live configuration.
CGroupSet(key string, value string) error
VolatileSet(changes map[string]string) error
From 20581ad6585878588f3d9827d90a21be6610e0cd Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 15 Sep 2020 17:42:02 -0400
Subject: [PATCH 4/7] lxd/apparmor: Fix unload/delete
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 | 14 +++++++-------
lxd/apparmor/instance.go | 4 ++--
lxd/apparmor/instance_forkproxy.go | 4 ++--
lxd/apparmor/network.go | 8 ++++----
4 files changed, 15 insertions(+), 15 deletions(-)
diff --git a/lxd/apparmor/apparmor.go b/lxd/apparmor/apparmor.go
index ffb9491f56..2231964633 100644
--- a/lxd/apparmor/apparmor.go
+++ b/lxd/apparmor/apparmor.go
@@ -83,9 +83,9 @@ func deleteNamespace(state *state.State, name string) error {
// hasProfile checks if the profile is already loaded.
func hasProfile(state *state.State, name string) (bool, error) {
- mangled := strings.Replace(name, "/", ".", -1)
+ mangled := strings.Replace(strings.Replace(strings.Replace(name, "/", ".", -1), "<", "", -1), ">", "", -1)
- profilesPath := "/sys/kernel/security/apaprmor/policy/profiles"
+ profilesPath := "/sys/kernel/security/apparmor/policy/profiles"
if shared.PathExists(profilesPath) {
entries, err := ioutil.ReadDir(profilesPath)
if err != nil {
@@ -94,7 +94,7 @@ func hasProfile(state *state.State, name string) (bool, error) {
for _, entry := range entries {
fields := strings.Split(entry.Name(), ".")
- if mangled == strings.Join(fields[0:len(fields)-2], ".") {
+ if mangled == strings.Join(fields[0:len(fields)-1], ".") {
return true, nil
}
}
@@ -122,12 +122,12 @@ func loadProfile(state *state.State, name string) error {
}
// unloadProfile removes the profile from the kernel.
-func unloadProfile(state *state.State, name string) error {
+func unloadProfile(state *state.State, fullName string, name string) error {
if !state.OS.AppArmorAvailable {
return nil
}
- ok, err := hasProfile(state, name)
+ ok, err := hasProfile(state, fullName)
if err != nil {
return err
}
@@ -140,7 +140,7 @@ func unloadProfile(state *state.State, name string) error {
}
// deleteProfile unloads and delete profile and cache for a profile.
-func deleteProfile(state *state.State, name string) error {
+func deleteProfile(state *state.State, fullName string, name string) error {
if !state.OS.AppArmorAdmin {
return nil
}
@@ -150,7 +150,7 @@ func deleteProfile(state *state.State, name string) error {
return err
}
- err = unloadProfile(state, name)
+ err = unloadProfile(state, fullName, name)
if err != nil {
return err
}
diff --git a/lxd/apparmor/instance.go b/lxd/apparmor/instance.go
index 1ae6f64b80..bccd46bf57 100644
--- a/lxd/apparmor/instance.go
+++ b/lxd/apparmor/instance.go
@@ -93,7 +93,7 @@ func InstanceUnload(state *state.State, inst instance) error {
return err
}
- err = unloadProfile(state, instanceProfileFilename(inst))
+ err := unloadProfile(state, InstanceProfileName(inst), instanceProfileFilename(inst))
if err != nil {
return err
}
@@ -108,7 +108,7 @@ func InstanceParse(state *state.State, inst instance) error {
// InstanceDelete removes the policy from cache/disk.
func InstanceDelete(state *state.State, inst instance) error {
- return deleteProfile(state, instanceProfileFilename(inst))
+ return deleteProfile(state, InstanceProfileName(inst), instanceProfileFilename(inst))
}
// instanceProfile generates the AppArmor profile template from the given instance.
diff --git a/lxd/apparmor/instance_forkproxy.go b/lxd/apparmor/instance_forkproxy.go
index 3bdb79ed11..96215cd77e 100644
--- a/lxd/apparmor/instance_forkproxy.go
+++ b/lxd/apparmor/instance_forkproxy.go
@@ -185,10 +185,10 @@ func ForkproxyLoad(state *state.State, inst instance, dev device) error {
// ForkproxyUnload ensures that the instances's policy namespace is unloaded to free kernel memory.
// This does not delete the policy from disk or cache.
func ForkproxyUnload(state *state.State, inst instance, dev device) error {
- return unloadProfile(state, forkproxyProfileFilename(inst, dev))
+ return unloadProfile(state, ForkproxyProfileName(inst, dev), forkproxyProfileFilename(inst, dev))
}
// ForkproxyDelete removes the policy from cache/disk.
func ForkproxyDelete(state *state.State, inst instance, dev device) error {
- return deleteProfile(state, forkproxyProfileFilename(inst, dev))
+ return deleteProfile(state, ForkproxyProfileName(inst, dev), forkproxyProfileFilename(inst, dev))
}
diff --git a/lxd/apparmor/network.go b/lxd/apparmor/network.go
index f3efe516e5..e5b2698c79 100644
--- a/lxd/apparmor/network.go
+++ b/lxd/apparmor/network.go
@@ -85,14 +85,14 @@ func NetworkLoad(state *state.State, n network) error {
// This does not delete the policy from disk or cache.
func NetworkUnload(state *state.State, n network) error {
// dnsmasq
- err := unloadProfile(state, dnsmasqProfileFilename(n))
+ err := unloadProfile(state, DnsmasqProfileName(n), dnsmasqProfileFilename(n))
if err != nil {
return err
}
// forkdns
if n.Config()["bridge.mode"] == "fan" {
- err := unloadProfile(state, forkdnsProfileFilename(n))
+ err := unloadProfile(state, ForkdnsProfileName(n), forkdnsProfileFilename(n))
if err != nil {
return err
}
@@ -103,13 +103,13 @@ func NetworkUnload(state *state.State, n network) error {
// NetworkDelete removes the profiles from cache/disk.
func NetworkDelete(state *state.State, n network) error {
- err := deleteProfile(state, dnsmasqProfileFilename(n))
+ err := deleteProfile(state, DnsmasqProfileName(n), dnsmasqProfileFilename(n))
if err != nil {
return err
}
if n.Config()["bridge.mode"] == "fan" {
- err := deleteProfile(state, forkdnsProfileFilename(n))
+ err := deleteProfile(state, ForkdnsProfileName(n), forkdnsProfileFilename(n))
if err != nil {
return err
}
From 2ece5ff996cf549401dd2871fb2fcb0a5e5f3671 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 15 Sep 2020 17:45:34 -0400
Subject: [PATCH 5/7] lxd/apparmor/instance: Sort context
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/instance.go | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/lxd/apparmor/instance.go b/lxd/apparmor/instance.go
index bccd46bf57..d6459e7262 100644
--- a/lxd/apparmor/instance.go
+++ b/lxd/apparmor/instance.go
@@ -131,15 +131,15 @@ func instanceProfile(state *state.State, inst instance) (string, error) {
// Render the profile.
var sb *strings.Builder = &strings.Builder{}
err = lxcProfileTpl.Execute(sb, map[string]interface{}{
- "feature_unix": unixSupported,
"feature_cgns": state.OS.CGInfo.Namespacing,
"feature_cgroup2": state.OS.CGInfo.Layout == cgroup.CgroupsUnified || state.OS.CGInfo.Layout == cgroup.CgroupsHybrid,
"feature_stacking": state.OS.AppArmorStacking && !state.OS.AppArmorStacked,
+ "feature_unix": unixSupported,
+ "name": InstanceProfileName(inst),
"namespace": InstanceNamespaceName(inst),
"nesting": shared.IsTrue(inst.ExpandedConfig()["security.nesting"]),
- "name": InstanceProfileName(inst),
- "unprivileged": !shared.IsTrue(inst.ExpandedConfig()["security.privileged"]) || state.OS.RunningInUserNS,
"raw": rawContent,
+ "unprivileged": !shared.IsTrue(inst.ExpandedConfig()["security.privileged"]) || state.OS.RunningInUserNS,
})
if err != nil {
return "", err
From fd082cf26bf1c220a1a146576fc421b8d54a2783 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 15 Sep 2020 14:04:15 -0400
Subject: [PATCH 6/7] lxd/apparmor: Prepare for qemu
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/instance.go | 18 ++++++++++++------
1 file changed, 12 insertions(+), 6 deletions(-)
diff --git a/lxd/apparmor/instance.go b/lxd/apparmor/instance.go
index d6459e7262..ab9880a26f 100644
--- a/lxd/apparmor/instance.go
+++ b/lxd/apparmor/instance.go
@@ -8,6 +8,7 @@ import (
"strings"
"github.com/lxc/lxd/lxd/cgroup"
+ "github.com/lxc/lxd/lxd/instance/instancetype"
"github.com/lxc/lxd/lxd/project"
"github.com/lxc/lxd/lxd/state"
"github.com/lxc/lxd/shared"
@@ -18,6 +19,7 @@ type instance interface {
Project() string
Name() string
ExpandedConfig() map[string]string
+ Type() instancetype.Type
}
// InstanceProfileName returns the instance's AppArmor profile name.
@@ -43,9 +45,11 @@ func instanceProfileFilename(inst instance) string {
// 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
+ if inst.Type() == instancetype.Container {
+ err := createNamespace(state, InstanceNamespaceName(inst))
+ if err != nil {
+ return err
+ }
}
/* In order to avoid forcing a profile parse (potentially slow) on
@@ -88,9 +92,11 @@ func InstanceLoad(state *state.State, inst instance) error {
// 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
+ if inst.Type() == instancetype.Container {
+ err := deleteNamespace(state, InstanceNamespaceName(inst))
+ if err != nil {
+ return err
+ }
}
err := unloadProfile(state, InstanceProfileName(inst), instanceProfileFilename(inst))
From de3e3680ff59dd1db0b016bf23616d5d13612586 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 15 Sep 2020 14:04:25 -0400
Subject: [PATCH 7/7] lxd/apparmor: Add qemu profile
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/instance.go | 66 ++++++++++++++++++++-----
lxd/apparmor/instance_qemu.go | 74 +++++++++++++++++++++++++++++
lxd/instance/drivers/driver_qemu.go | 26 ++++++++++
3 files changed, 153 insertions(+), 13 deletions(-)
create mode 100644 lxd/apparmor/instance_qemu.go
diff --git a/lxd/apparmor/instance.go b/lxd/apparmor/instance.go
index ab9880a26f..77d266befd 100644
--- a/lxd/apparmor/instance.go
+++ b/lxd/apparmor/instance.go
@@ -11,6 +11,7 @@ import (
"github.com/lxc/lxd/lxd/instance/instancetype"
"github.com/lxc/lxd/lxd/project"
"github.com/lxc/lxd/lxd/state"
+ "github.com/lxc/lxd/lxd/util"
"github.com/lxc/lxd/shared"
)
@@ -20,6 +21,9 @@ type instance interface {
Name() string
ExpandedConfig() map[string]string
Type() instancetype.Type
+ LogPath() string
+ Path() string
+ DevPaths() []string
}
// InstanceProfileName returns the instance's AppArmor profile name.
@@ -136,19 +140,55 @@ func instanceProfile(state *state.State, inst instance) (string, error) {
// Render the profile.
var sb *strings.Builder = &strings.Builder{}
- err = lxcProfileTpl.Execute(sb, map[string]interface{}{
- "feature_cgns": state.OS.CGInfo.Namespacing,
- "feature_cgroup2": state.OS.CGInfo.Layout == cgroup.CgroupsUnified || state.OS.CGInfo.Layout == cgroup.CgroupsHybrid,
- "feature_stacking": state.OS.AppArmorStacking && !state.OS.AppArmorStacked,
- "feature_unix": unixSupported,
- "name": InstanceProfileName(inst),
- "namespace": InstanceNamespaceName(inst),
- "nesting": shared.IsTrue(inst.ExpandedConfig()["security.nesting"]),
- "raw": rawContent,
- "unprivileged": !shared.IsTrue(inst.ExpandedConfig()["security.privileged"]) || state.OS.RunningInUserNS,
- })
- if err != nil {
- return "", err
+ if inst.Type() == instancetype.Container {
+ err = lxcProfileTpl.Execute(sb, map[string]interface{}{
+ "feature_cgns": state.OS.CGInfo.Namespacing,
+ "feature_cgroup2": state.OS.CGInfo.Layout == cgroup.CgroupsUnified || state.OS.CGInfo.Layout == cgroup.CgroupsHybrid,
+ "feature_stacking": state.OS.AppArmorStacking && !state.OS.AppArmorStacked,
+ "feature_unix": unixSupported,
+ "name": InstanceProfileName(inst),
+ "namespace": InstanceNamespaceName(inst),
+ "nesting": shared.IsTrue(inst.ExpandedConfig()["security.nesting"]),
+ "raw": rawContent,
+ "unprivileged": !shared.IsTrue(inst.ExpandedConfig()["security.privileged"]) || state.OS.RunningInUserNS,
+ })
+ if err != nil {
+ return "", err
+ }
+ } else {
+ rootPath := ""
+ if shared.InSnap() {
+ rootPath = "/var/lib/snapd/hostfs"
+ }
+
+ // AppArmor requires deref of all paths.
+ path, err := filepath.EvalSymlinks(inst.Path())
+ if err != nil {
+ return "", err
+ }
+
+ devPaths := inst.DevPaths()
+ for i := range devPaths {
+ devPaths[i], err = filepath.EvalSymlinks(devPaths[i])
+ if err != nil {
+ return "", err
+ }
+ }
+
+ err = qemuProfileTpl.Execute(sb, map[string]interface{}{
+ "devPaths": inst.DevPaths(),
+ "exePath": util.GetExecPath(),
+ "libraryPath": strings.Split(os.Getenv("LD_LIBRARY_PATH"), ":"),
+ "logPath": inst.LogPath(),
+ "name": InstanceProfileName(inst),
+ "path": path,
+ "raw": rawContent,
+ "rootPath": rootPath,
+ "snap": shared.InSnap(),
+ })
+ if err != nil {
+ return "", err
+ }
}
return sb.String(), nil
diff --git a/lxd/apparmor/instance_qemu.go b/lxd/apparmor/instance_qemu.go
new file mode 100644
index 0000000000..7ceb02dc52
--- /dev/null
+++ b/lxd/apparmor/instance_qemu.go
@@ -0,0 +1,74 @@
+package apparmor
+
+import (
+ "text/template"
+)
+
+var qemuProfileTpl = template.Must(template.New("qemuProfile").Parse(`#include <tunables/global>
+profile "{{ .name }}" flags=(attach_disconnected,mediate_deleted) {
+ #include <abstractions/base>
+ #include <abstractions/consoles>
+ #include <abstractions/nameservice>
+
+ capability dac_override,
+ capability dac_read_search,
+ capability setgid,
+ capability setuid,
+ capability sys_chroot,
+ capability sys_resource,
+
+ # Needed by qemu
+ /{,usr/}bin/qemu* mrix,
+ /dev/hugepages/** w,
+ /dev/kvm w,
+ /dev/net/tun w,
+ /dev/ptmx w,
+ /dev/vfio/** w,
+ /dev/vhost-net w,
+ /dev/vhost-vsock w,
+ /etc/ceph/** r,
+ /usr/share/OVMF/OVMF_CODE.fd kr,
+ owner @{PROC}/@{pid}/task/@{tid}/comm rw,
+
+ # Instance specific paths
+ {{ .logPath }}/** rwk,
+ {{ .path }}/qemu.nvram rwk,
+{{range $index, $element := .devPaths}}
+ {{$element}} rwk,
+{{- end }}
+
+ # Needed for lxd fork commands
+ {{ .exePath }} mr,
+ @{PROC}/@{pid}/cmdline r,
+ {{ .rootPath }}/{etc,lib,usr/lib}/os-release r,
+
+ # Things that we definitely don't need
+ deny @{PROC}/@{pid}/cgroup r,
+ deny /sys/module/apparmor/parameters/enabled r,
+ deny /sys/kernel/mm/transparent_hugepage/hpage_pmd_size r,
+
+{{- if .snap }}
+ # The binary itself (for nesting)
+ /var/snap/lxd/common/lxd.debug mr,
+ /snap/lxd/*/bin/lxd mr,
+ /snap/lxd/*/bin/qemu* mrix,
+ /snap/lxd/*/share/qemu/OVMF_CODE.fd kr,
+
+ # Snap-specific libraries
+ /snap/lxd/*/lib/**.so* mr,
+{{- end }}
+
+{{if .libraryPath -}}
+ # Entries from LD_LIBRARY_PATH
+{{range $index, $element := .libraryPath}}
+ {{$element}}/** mr,
+{{- end }}
+{{- end }}
+
+{{- if .raw }}
+
+ ### Configuration: raw.apparmor
+{{ .raw }}
+{{- end }}
+}
+`))
diff --git a/lxd/instance/drivers/driver_qemu.go b/lxd/instance/drivers/driver_qemu.go
index 6a5f7cd2f7..fc1a8ac5cc 100644
--- a/lxd/instance/drivers/driver_qemu.go
+++ b/lxd/instance/drivers/driver_qemu.go
@@ -27,6 +27,7 @@ import (
"gopkg.in/yaml.v2"
lxdClient "github.com/lxc/lxd/client"
+ "github.com/lxc/lxd/lxd/apparmor"
"github.com/lxc/lxd/lxd/backup"
"github.com/lxc/lxd/lxd/cluster"
"github.com/lxc/lxd/lxd/db"
@@ -505,6 +506,12 @@ func (vm *qemu) Freeze() error {
// onStop is run when the instance stops.
func (vm *qemu) onStop(target string) error {
+ ctxMap := log.Ctx{
+ "project": vm.project,
+ "name": vm.name,
+ "ephemeral": vm.ephemeral,
+ }
+
// Pick up the existing stop operation lock created in Stop() function.
op := operationlock.Get(vm.id)
if op != nil && op.Action() != "stop" {
@@ -526,6 +533,13 @@ func (vm *qemu) onStop(target string) error {
return err
}
+ // Unload the apparmor profile
+ err = apparmor.InstanceUnload(vm.state, vm)
+ if err != nil {
+ ctxMap["err"] = err
+ logger.Error("Failed to unload AppArmor profile", ctxMap)
+ }
+
if target == "reboot" {
err = vm.Start(false)
} else if vm.ephemeral {
@@ -837,6 +851,15 @@ func (vm *qemu) Start(stateful bool) error {
return err
}
+ // Load the AppArmor profile
+ err = apparmor.InstanceLoad(vm.state, vm)
+ if err != nil {
+ op.Done(err)
+ return err
+ }
+
+ p.SetApparmor(apparmor.InstanceProfileName(vm))
+
// Open any extra files and pass their file handles to qemu command.
files := []*os.File{}
for _, file := range fdFiles {
@@ -3230,6 +3253,9 @@ func (vm *qemu) cleanup() {
vm.removeUnixDevices()
vm.removeDiskDevices()
+ // Remove the security profiles
+ apparmor.InstanceDelete(vm.state, vm)
+
// Remove the devices path
os.Remove(vm.DevicesPath())
More information about the lxc-devel
mailing list