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