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

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) ===

From 8b6110431401fbfd81c8ab7f6aa403f397b3cc84 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgra...@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 <stgra...@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?= <stgra...@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 <stgra...@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?= <stgra...@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 <stgra...@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?= <stgra...@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 <stgra...@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
+}
_______________________________________________
lxc-devel mailing list
lxc-devel@lists.linuxcontainers.org
http://lists.linuxcontainers.org/listinfo/lxc-devel

Reply via email to