[lxc-devel] [lxd/master] Add apparmor confinement for forkproxy
stgraber on Github
lxc-bot at linuxcontainers.org
Wed Aug 26 22:37:18 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/20200826/3845ff77/attachment-0001.bin>
-------------- next part --------------
From 8b6110431401fbfd81c8ab7f6aa403f397b3cc84 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 26 Aug 2020 18:30:48 -0400
Subject: [PATCH 1/4] lxd/apparmor/dnsmasq: Add /proc/self/fd
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/network_dnsmasq.go | 1 +
1 file changed, 1 insertion(+)
diff --git a/lxd/apparmor/network_dnsmasq.go b/lxd/apparmor/network_dnsmasq.go
index 7fec180d52..ef2c5ef691 100644
--- a/lxd/apparmor/network_dnsmasq.go
+++ b/lxd/apparmor/network_dnsmasq.go
@@ -36,6 +36,7 @@ profile "{{ .name }}" flags=(attach_disconnected,mediate_deleted) {
# Additional system files
@{PROC}/sys/net/ipv6/conf/*/mtu r,
+ @{PROC}/@{pid}/fd/ r,
# System configuration access
{{ .rootPath }}/etc/gai.conf r,
From 5d3a3571534105bee5b4537b9a4d635dae19d73e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 26 Aug 2020 18:34:14 -0400
Subject: [PATCH 2/4] lxd/apparmor/forkdns: Allow reading/mapping the binary
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/network_forkdns.go | 3 +++
1 file changed, 3 insertions(+)
diff --git a/lxd/apparmor/network_forkdns.go b/lxd/apparmor/network_forkdns.go
index b9e66f9791..bc5dfecfc9 100644
--- a/lxd/apparmor/network_forkdns.go
+++ b/lxd/apparmor/network_forkdns.go
@@ -7,6 +7,7 @@ import (
"text/template"
"github.com/lxc/lxd/lxd/state"
+ "github.com/lxc/lxd/lxd/util"
"github.com/lxc/lxd/shared"
)
@@ -26,6 +27,7 @@ profile "{{ .name }}" flags=(attach_disconnected,mediate_deleted) {
{{ .varPath }}/networks/{{ .networkName }}/forkdns.servers/servers.conf r,
# Needed for lxd fork commands
+ {{ .exePath }} mr,
@{PROC}/@{pid}/cmdline r,
{{ .rootPath }}/{etc,lib,usr/lib}/os-release r,
@@ -68,6 +70,7 @@ func forkdnsProfile(state *state.State, n network) (string, error) {
"rootPath": rootPath,
"snap": shared.InSnap(),
"libraryPath": strings.Split(os.Getenv("LD_LIBRARY_PATH"), ":"),
+ "exePath": util.GetExecPath(),
})
if err != nil {
return "", err
From 8dad7747ae6b711703c851ae5d6e9d8163668cec Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 25 Aug 2020 23:05:40 -0400
Subject: [PATCH 3/4] lxd/apparmor: Add forkproxy
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_forkproxy.go | 153 +++++++++++++++++++++++++++++
1 file changed, 153 insertions(+)
create mode 100644 lxd/apparmor/instance_forkproxy.go
diff --git a/lxd/apparmor/instance_forkproxy.go b/lxd/apparmor/instance_forkproxy.go
new file mode 100644
index 0000000000..6625c4b0a2
--- /dev/null
+++ b/lxd/apparmor/instance_forkproxy.go
@@ -0,0 +1,153 @@
+package apparmor
+
+import (
+ "fmt"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "strings"
+ "text/template"
+
+ "github.com/lxc/lxd/lxd/project"
+ "github.com/lxc/lxd/lxd/state"
+ "github.com/lxc/lxd/shared"
+)
+
+// Internal copy of the device interface.
+type device interface {
+ Name() string
+}
+
+var forkproxyProfileTpl = template.Must(template.New("forkproxyProfile").Parse(`#include <tunables/global>
+profile "{{ .name }}" flags=(attach_disconnected,mediate_deleted) {
+ #include <abstractions/base>
+
+ # Capabilities
+ capability dac_read_search,
+ capability dac_override,
+ capability kill,
+ capability net_bind_service,
+ capability sys_admin,
+ capability sys_ptrace,
+
+ # Network access
+ network inet dgram,
+ network inet6 dgram,
+
+ # Forkproxy operation
+ @{PROC}/** rw,
+ / rw,
+ ptrace (read),
+
+ # Needed for lxd fork commands
+ @{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-specific libraries
+ /snap/lxd/*/lib/**.so* mr,
+{{- end }}
+
+{{if .libraryPath -}}
+ # Entries from LD_LIBRARY_PATH
+{{range $index, $element := .libraryPath}}
+ {{$element}}/** mr,
+{{- end }}
+{{- end }}
+}
+`))
+
+// forkproxyProfile generates the AppArmor profile template from the given network.
+func forkproxyProfile(state *state.State, inst instance, dev device) (string, error) {
+ rootPath := ""
+ if shared.InSnap() {
+ rootPath = "/var/lib/snapd/hostfs"
+ }
+
+ // Render the profile.
+ var sb *strings.Builder = &strings.Builder{}
+ err := forkproxyProfileTpl.Execute(sb, map[string]interface{}{
+ "name": ForkproxyProfileName(inst, dev),
+ "varPath": shared.VarPath(""),
+ "rootPath": rootPath,
+ "snap": shared.InSnap(),
+ "libraryPath": strings.Split(os.Getenv("LD_LIBRARY_PATH"), ":"),
+ })
+ if err != nil {
+ return "", err
+ }
+
+ return sb.String(), nil
+}
+
+// ForkproxyProfileName returns the AppArmor profile name.
+func ForkproxyProfileName(inst instance, dev device) string {
+ path := shared.VarPath("")
+ name := fmt.Sprintf("%s_%s_<%s>", dev.Name(), project.Instance(inst.Project(), inst.Name()), path)
+ return profileName("", name)
+}
+
+// forkproxyProfileFilename returns the name of the on-disk profile name.
+func forkproxyProfileFilename(inst instance, dev device) string {
+ name := fmt.Sprintf("%s_%s", dev.Name(), project.Instance(inst.Project(), inst.Name()))
+ return profileName("forkproxy", name)
+}
+
+// ForkproxyLoad ensures that the instances's policy is loaded into the kernel so the it can boot.
+func ForkproxyLoad(state *state.State, inst instance, dev device) error {
+ /* 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", forkproxyProfileFilename(inst, dev))
+ content, err := ioutil.ReadFile(profile)
+ if err != nil && !os.IsNotExist(err) {
+ return err
+ }
+
+ updated, err := forkproxyProfile(state, inst, dev)
+ 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, forkproxyProfileFilename(inst, dev))
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// 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))
+}
+
+// ForkproxyDelete removes the policy from cache/disk.
+func ForkproxyDelete(state *state.State, inst instance, dev device) error {
+ return deleteProfile(state, forkproxyProfileFilename(inst, dev))
+}
From 519ea926437a69978ac8b2a5aadd8fd66ff6dde2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 25 Aug 2020 23:05:48 -0400
Subject: [PATCH 4/4] lxd/device/forkproxy: Add apparmor
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_forkproxy.go | 40 ++++++++++++++++++++++++++++++
lxd/device/proxy.go | 25 +++++++++++++++++++
2 files changed, 65 insertions(+)
diff --git a/lxd/apparmor/instance_forkproxy.go b/lxd/apparmor/instance_forkproxy.go
index 6625c4b0a2..ca3f4a2288 100644
--- a/lxd/apparmor/instance_forkproxy.go
+++ b/lxd/apparmor/instance_forkproxy.go
@@ -8,13 +8,16 @@ import (
"strings"
"text/template"
+ deviceConfig "github.com/lxc/lxd/lxd/device/config"
"github.com/lxc/lxd/lxd/project"
"github.com/lxc/lxd/lxd/state"
+ "github.com/lxc/lxd/lxd/util"
"github.com/lxc/lxd/shared"
)
// Internal copy of the device interface.
type device interface {
+ Config() deviceConfig.Device
Name() string
}
@@ -23,16 +26,24 @@ profile "{{ .name }}" flags=(attach_disconnected,mediate_deleted) {
#include <abstractions/base>
# Capabilities
+ capability chown,
capability dac_read_search,
capability dac_override,
+ capability fowner,
+ capability fsetid,
capability kill,
capability net_bind_service,
+ capability setgid,
+ capability setuid,
capability sys_admin,
+ capability sys_chroot,
capability sys_ptrace,
# Network access
network inet dgram,
network inet6 dgram,
+ network inet stream,
+ network inet6 stream,
# Forkproxy operation
@{PROC}/** rw,
@@ -40,8 +51,14 @@ profile "{{ .name }}" flags=(attach_disconnected,mediate_deleted) {
ptrace (read),
# Needed for lxd fork commands
+ {{ .exePath }} mr,
@{PROC}/@{pid}/cmdline r,
{{ .rootPath }}/{etc,lib,usr/lib}/os-release r,
+{{if .sockets -}}
+{{range $index, $element := .sockets}}
+ {{$element}} rw,
+{{- end }}
+{{- end }}
# Things that we definitely don't need
deny @{PROC}/@{pid}/cgroup r,
@@ -73,6 +90,27 @@ func forkproxyProfile(state *state.State, inst instance, dev device) (string, er
rootPath = "/var/lib/snapd/hostfs"
}
+ // Add any socket used by forkproxy.
+ sockets := []string{}
+
+ fields := strings.SplitN(dev.Config()["listen"], ":", 2)
+ if fields[0] == "unix" && !strings.HasPrefix(fields[1], "@") {
+ if dev.Config()["bind"] == "host" || dev.Config()["bind"] == "" {
+ sockets = append(sockets, shared.HostPath(fields[1]))
+ } else {
+ sockets = append(sockets, fields[1])
+ }
+ }
+
+ fields = strings.SplitN(dev.Config()["connect"], ":", 2)
+ if fields[0] == "unix" && !strings.HasPrefix(fields[1], "@") {
+ if dev.Config()["bind"] == "host" || dev.Config()["bind"] == "" {
+ sockets = append(sockets, fields[1])
+ } else {
+ sockets = append(sockets, shared.HostPath(fields[1]))
+ }
+ }
+
// Render the profile.
var sb *strings.Builder = &strings.Builder{}
err := forkproxyProfileTpl.Execute(sb, map[string]interface{}{
@@ -80,7 +118,9 @@ func forkproxyProfile(state *state.State, inst instance, dev device) (string, er
"varPath": shared.VarPath(""),
"rootPath": rootPath,
"snap": shared.InSnap(),
+ "exePath": util.GetExecPath(),
"libraryPath": strings.Split(os.Getenv("LD_LIBRARY_PATH"), ":"),
+ "sockets": sockets,
})
if err != nil {
return "", err
diff --git a/lxd/device/proxy.go b/lxd/device/proxy.go
index 9d3420622e..80224ed23d 100644
--- a/lxd/device/proxy.go
+++ b/lxd/device/proxy.go
@@ -13,6 +13,7 @@ import (
"github.com/pkg/errors"
liblxc "gopkg.in/lxc/go-lxc.v2"
+ "github.com/lxc/lxd/lxd/apparmor"
deviceConfig "github.com/lxc/lxd/lxd/device/config"
"github.com/lxc/lxd/lxd/device/nictype"
"github.com/lxc/lxd/lxd/instance"
@@ -193,6 +194,12 @@ func (d *proxy) Start() (*deviceConfig.RunConfig, error) {
logFileName := fmt.Sprintf("proxy.%s.log", d.name)
logPath := filepath.Join(d.inst.LogPath(), logFileName)
+ // Load the apparmor profile
+ err = apparmor.ForkproxyLoad(d.state, d.inst, d)
+ if err != nil {
+ return err
+ }
+
// Spawn the daemon using subprocess
command := d.state.OS.ExecPath
forkproxyargs := []string{"forkproxy",
@@ -216,6 +223,8 @@ func (d *proxy) Start() (*deviceConfig.RunConfig, error) {
return fmt.Errorf("Failed to create subprocess: %s", err)
}
+ p.SetApparmor(apparmor.ForkproxyProfileName(d.inst, d))
+
err = p.StartWithFiles(proxyValues.inheritFds)
if err != nil {
return fmt.Errorf("Failed to run: %s %s: %v", command, strings.Join(forkproxyargs, " "), err)
@@ -309,6 +318,12 @@ func (d *proxy) Stop() (*deviceConfig.RunConfig, error) {
return nil, err
}
+ // Unload apparmor profile.
+ err = apparmor.ForkproxyUnload(d.state, d.inst, d)
+ if err != nil {
+ return nil, err
+ }
+
return nil, nil
}
@@ -545,3 +560,13 @@ func (d *proxy) killProxyProc(pidPath string) error {
os.Remove(pidPath)
return nil
}
+
+func (d *proxy) Remove() error {
+ // Delete apparmor profile.
+ err := apparmor.ForkproxyDelete(d.state, d.inst, d)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
More information about the lxc-devel
mailing list