The following pull request was submitted through Github. It can be accessed and reviewed at: https://github.com/lxc/lxd/pull/7764
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) === This branch has the eventual goal of running `forkproxy` under an apparmor profile as well as running it as an unprivileged user. To achieve this I needed a few extra things: - Ability to run a subprocess as another user/group - An unprivileged group to use Additionally because `lxd forkdns` is a sub-command of LXD, a lot of code gets loaded just because it's LXD. Part of that was loading the whole USB database which we only need for `lxd/resource` so I've changed that code a bit. We still are accessing a bunch of files on startup that we shouldn't be. The cgroup and apparmor bits come from us loading liblxc. No idea where the hugepage one comes from.
From dd4e97f3bb84cd4919b48e207dc5796abc68dc14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgra...@ubuntu.com> Date: Mon, 10 Aug 2020 17:34:35 -0400 Subject: [PATCH 01/10] shared/usbid: Don't auto-load MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Graber <stgra...@ubuntu.com> --- shared/usbid/load.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/shared/usbid/load.go b/shared/usbid/load.go index 39f6c4d831..65ef8f067f 100644 --- a/shared/usbid/load.go +++ b/shared/usbid/load.go @@ -28,7 +28,8 @@ var ( Classes map[ClassCode]*Class ) -func init() { +// Load reads the USB database from disk. +func Load() { usbids, err := os.Open("/usr/share/misc/usb.ids") if err != nil { if !os.IsNotExist(err) { From 3699560ddca30d453d9950225fa25e5a0550cb77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgra...@ubuntu.com> Date: Mon, 10 Aug 2020 17:34:44 -0400 Subject: [PATCH 02/10] lxd/resources: Load USB database 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/resources/usb.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lxd/resources/usb.go b/lxd/resources/usb.go index b848711a3c..6479d252fc 100644 --- a/lxd/resources/usb.go +++ b/lxd/resources/usb.go @@ -17,6 +17,9 @@ var sysBusUSB = "/sys/bus/usb/devices" // GetUSB returns a filled api.ResourcesUSB struct ready for use by LXD func GetUSB() (*api.ResourcesUSB, error) { + // Load the USB database. + usbid.Load() + usb := api.ResourcesUSB{} if !sysfsExists(sysBusUSB) { From 50dc6c718a586555d852deee7104c539ca43fcab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgra...@ubuntu.com> Date: Mon, 10 Aug 2020 15:46:44 -0400 Subject: [PATCH 03/10] lxd/apparmor: Move dnsmasq functions 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.go | 57 ------------------------------- lxd/apparmor/network_dnsmasq.go | 59 +++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 57 deletions(-) diff --git a/lxd/apparmor/network.go b/lxd/apparmor/network.go index e3615c6812..15094083b1 100644 --- a/lxd/apparmor/network.go +++ b/lxd/apparmor/network.go @@ -1,16 +1,11 @@ package apparmor import ( - "crypto/sha256" - "fmt" - "io" "io/ioutil" "os" "path/filepath" - "strings" "github.com/lxc/lxd/lxd/state" - "github.com/lxc/lxd/shared" ) // Internal copy of the network interface. @@ -18,35 +13,6 @@ type network interface { Name() string } -// DnsmasqProfileName returns the AppArmor profile name. -func DnsmasqProfileName(n network) string { - path := shared.VarPath("") - name := fmt.Sprintf("%s_<%s>", n.Name(), path) - - // Max length in AppArmor is 253 chars. - if len(name)+12 >= 253 { - hash := sha256.New() - io.WriteString(hash, name) - name = fmt.Sprintf("%x", hash.Sum(nil)) - } - - return fmt.Sprintf("lxd_dnsmasq-%s", name) -} - -// dnsmasqProfileFilename returns the name of the on-disk profile name. -func dnsmasqProfileFilename(n network) string { - name := n.Name() - - // Max length in AppArmor is 253 chars. - if len(name)+12 >= 253 { - hash := sha256.New() - io.WriteString(hash, name) - name = fmt.Sprintf("%x", hash.Sum(nil)) - } - - return fmt.Sprintf("lxd_dnsmasq-%s", name) -} - // NetworkLoad ensures that the network's profiles are loaded into the kernel. func NetworkLoad(state *state.State, n network) error { /* In order to avoid forcing a profile parse (potentially slow) on @@ -101,26 +67,3 @@ func NetworkUnload(state *state.State, n network) error { func NetworkDelete(state *state.State, n network) error { return deleteProfile(state, dnsmasqProfileFilename(n)) } - -// dnsmasqProfile generates the AppArmor profile template from the given network. -func dnsmasqProfile(state *state.State, n network) (string, error) { - rootPath := "" - if shared.InSnap() { - rootPath = "/var/lib/snapd/hostfs" - } - - // Render the profile. - var sb *strings.Builder = &strings.Builder{} - err := dnsmasqProfileTpl.Execute(sb, map[string]interface{}{ - "name": DnsmasqProfileName(n), - "networkName": n.Name(), - "varPath": shared.VarPath(""), - "rootPath": rootPath, - "snap": shared.InSnap(), - }) - if err != nil { - return "", err - } - - return sb.String(), nil -} diff --git a/lxd/apparmor/network_dnsmasq.go b/lxd/apparmor/network_dnsmasq.go index ea37daf863..0b63dc6048 100644 --- a/lxd/apparmor/network_dnsmasq.go +++ b/lxd/apparmor/network_dnsmasq.go @@ -1,7 +1,14 @@ package apparmor import ( + "crypto/sha256" + "fmt" + "io" + "strings" "text/template" + + "github.com/lxc/lxd/lxd/state" + "github.com/lxc/lxd/shared" ) var dnsmasqProfileTpl = template.Must(template.New("dnsmasqProfile").Parse(`#include <tunables/global> @@ -59,3 +66,55 @@ profile "{{ .name }}" flags=(attach_disconnected,mediate_deleted) { {{- end }} } `)) + +// dnsmasqProfile generates the AppArmor profile template from the given network. +func dnsmasqProfile(state *state.State, n network) (string, error) { + rootPath := "" + if shared.InSnap() { + rootPath = "/var/lib/snapd/hostfs" + } + + // Render the profile. + var sb *strings.Builder = &strings.Builder{} + err := dnsmasqProfileTpl.Execute(sb, map[string]interface{}{ + "name": DnsmasqProfileName(n), + "networkName": n.Name(), + "varPath": shared.VarPath(""), + "rootPath": rootPath, + "snap": shared.InSnap(), + }) + if err != nil { + return "", err + } + + return sb.String(), nil +} + +// DnsmasqProfileName returns the AppArmor profile name. +func DnsmasqProfileName(n network) string { + path := shared.VarPath("") + name := fmt.Sprintf("%s_<%s>", n.Name(), path) + + // Max length in AppArmor is 253 chars. + if len(name)+12 >= 253 { + hash := sha256.New() + io.WriteString(hash, name) + name = fmt.Sprintf("%x", hash.Sum(nil)) + } + + return fmt.Sprintf("lxd_dnsmasq-%s", name) +} + +// dnsmasqProfileFilename returns the name of the on-disk profile name. +func dnsmasqProfileFilename(n network) string { + name := n.Name() + + // Max length in AppArmor is 253 chars. + if len(name)+12 >= 253 { + hash := sha256.New() + io.WriteString(hash, name) + name = fmt.Sprintf("%x", hash.Sum(nil)) + } + + return fmt.Sprintf("lxd_dnsmasq-%s", name) +} From 6488290da7a97b62925e7f73fbe86a8a0621c10c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgra...@ubuntu.com> Date: Mon, 10 Aug 2020 15:47:41 -0400 Subject: [PATCH 04/10] lxd/apparmor: forkdns profile 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.go | 52 +++++++++++++++- lxd/apparmor/network_forkdns.go | 101 ++++++++++++++++++++++++++++++++ 2 files changed, 152 insertions(+), 1 deletion(-) create mode 100644 lxd/apparmor/network_forkdns.go diff --git a/lxd/apparmor/network.go b/lxd/apparmor/network.go index 15094083b1..f3efe516e5 100644 --- a/lxd/apparmor/network.go +++ b/lxd/apparmor/network.go @@ -10,6 +10,7 @@ import ( // Internal copy of the network interface. type network interface { + Config() map[string]string Name() string } @@ -26,6 +27,8 @@ func NetworkLoad(state *state.State, n network) error { * version out so that the new changes are reflected and we definitely * force a recompile. */ + + // dnsmasq profile := filepath.Join(aaPath, "profiles", dnsmasqProfileFilename(n)) content, err := ioutil.ReadFile(profile) if err != nil && !os.IsNotExist(err) { @@ -49,21 +52,68 @@ func NetworkLoad(state *state.State, n network) error { return err } + // forkdns + if n.Config()["bridge.mode"] == "fan" { + profile := filepath.Join(aaPath, "profiles", forkdnsProfileFilename(n)) + content, err := ioutil.ReadFile(profile) + if err != nil && !os.IsNotExist(err) { + return err + } + + updated, err := forkdnsProfile(state, n) + 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, forkdnsProfileFilename(n)) + if err != nil { + return err + } + } + return nil } // NetworkUnload ensures that the network's profiles are unloaded to free kernel memory. // This does not delete the policy from disk or cache. func NetworkUnload(state *state.State, n network) error { + // dnsmasq err := unloadProfile(state, dnsmasqProfileFilename(n)) if err != nil { return err } + // forkdns + if n.Config()["bridge.mode"] == "fan" { + err := unloadProfile(state, forkdnsProfileFilename(n)) + if err != nil { + return err + } + } + return nil } // NetworkDelete removes the profiles from cache/disk. func NetworkDelete(state *state.State, n network) error { - return deleteProfile(state, dnsmasqProfileFilename(n)) + err := deleteProfile(state, dnsmasqProfileFilename(n)) + if err != nil { + return err + } + + if n.Config()["bridge.mode"] == "fan" { + err := deleteProfile(state, forkdnsProfileFilename(n)) + if err != nil { + return err + } + } + + return nil } diff --git a/lxd/apparmor/network_forkdns.go b/lxd/apparmor/network_forkdns.go new file mode 100644 index 0000000000..396e87be67 --- /dev/null +++ b/lxd/apparmor/network_forkdns.go @@ -0,0 +1,101 @@ +package apparmor + +import ( + "crypto/sha256" + "fmt" + "io" + "strings" + "text/template" + + "github.com/lxc/lxd/lxd/state" + "github.com/lxc/lxd/shared" +) + +var forkdnsProfileTpl = template.Must(template.New("forkdnsProfile").Parse(`#include <tunables/global> +profile "{{ .name }}" flags=(attach_disconnected,mediate_deleted) { + #include <abstractions/base> + + # Capabilities + capability net_bind_service, + + # Network access + network inet dgram, + network inet6 dgram, + + # Network-specific paths + {{ .varPath }}/networks/{{ .networkName }}/dnsmasq.leases r, + {{ .varPath }}/networks/{{ .networkName }}/forkdns.servers/servers.conf r, + + # 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/current/bin/lxd mr, + /snap/lxd/*/bin/lxd mr, + + # Snap-specific libraries + /snap/lxd/current/lib/**.so* mr, + /snap/lxd/*/lib/**.so* mr, +{{- end }} +} +`)) + +// forkdnsProfile generates the AppArmor profile template from the given network. +func forkdnsProfile(state *state.State, n network) (string, error) { + rootPath := "" + if shared.InSnap() { + rootPath = "/var/lib/snapd/hostfs" + } + + // Render the profile. + var sb *strings.Builder = &strings.Builder{} + err := forkdnsProfileTpl.Execute(sb, map[string]interface{}{ + "name": ForkdnsProfileName(n), + "networkName": n.Name(), + "varPath": shared.VarPath(""), + "rootPath": rootPath, + "snap": shared.InSnap(), + }) + if err != nil { + return "", err + } + + return sb.String(), nil +} + +// ForkdnsProfileName returns the AppArmor profile name. +func ForkdnsProfileName(n network) string { + path := shared.VarPath("") + name := fmt.Sprintf("%s_<%s>", n.Name(), path) + + // Max length in AppArmor is 253 chars. + if len(name)+12 >= 253 { + hash := sha256.New() + io.WriteString(hash, name) + name = fmt.Sprintf("%x", hash.Sum(nil)) + } + + return fmt.Sprintf("lxd_forkdns-%s", name) +} + +// forkdnsProfileFilename returns the name of the on-disk profile name. +func forkdnsProfileFilename(n network) string { + name := n.Name() + + // Max length in AppArmor is 253 chars. + if len(name)+12 >= 253 { + hash := sha256.New() + io.WriteString(hash, name) + name = fmt.Sprintf("%x", hash.Sum(nil)) + } + + return fmt.Sprintf("lxd_forkdns-%s", name) +} From 47b47dae003c1e72bd959710f2d585c481944749 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgra...@ubuntu.com> Date: Mon, 10 Aug 2020 16:02:04 -0400 Subject: [PATCH 05/10] lxd/sys: Add unpriv uid/group 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/sys/os.go | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/lxd/sys/os.go b/lxd/sys/os.go index e18d917ed1..5dc6d1df61 100644 --- a/lxd/sys/os.go +++ b/lxd/sys/os.go @@ -49,8 +49,12 @@ type OS struct { MockMode bool // If true some APIs will be mocked (for testing) Nodev bool RunningInUserNS bool - UnprivUser string - UnprivUID int + + // Privilege dropping + UnprivUser string + UnprivUID uint32 + UnprivGroup string + UnprivGID uint32 // Apparmor features AppArmorAdmin bool @@ -109,7 +113,7 @@ func (s *OS) Init() error { logger.Error("Error detecting backing fs", log.Ctx{"err": err}) } - // Detect if it is possible to run daemons as an unprivileged user. + // Detect if it is possible to run daemons as an unprivileged user and group. for _, user := range []string{"lxd", "nobody"} { uid, err := shared.UserId(user) if err != nil { @@ -117,7 +121,18 @@ func (s *OS) Init() error { } s.UnprivUser = user - s.UnprivUID = uid + s.UnprivUID = uint32(uid) + break + } + + for _, group := range []string{"lxd", "nogroup"} { + gid, err := shared.GroupId(group) + if err != nil { + continue + } + + s.UnprivGroup = group + s.UnprivGID = uint32(gid) break } From e38968777992e09519345aa51b3ca3bc6db6ae2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgra...@ubuntu.com> Date: Mon, 10 Aug 2020 16:16:26 -0400 Subject: [PATCH 06/10] lxd/instances: Update for OS type change 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/instance/drivers/driver_qemu.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lxd/instance/drivers/driver_qemu.go b/lxd/instance/drivers/driver_qemu.go index cc12ed6d14..793d3668a3 100644 --- a/lxd/instance/drivers/driver_qemu.go +++ b/lxd/instance/drivers/driver_qemu.go @@ -766,7 +766,7 @@ func (vm *qemu) Start(stateful bool) error { return err } - err = os.Chown(path, vm.state.OS.UnprivUID, -1) + err = os.Chown(path, int(vm.state.OS.UnprivUID), -1) if err != nil { op.Done(err) return err From ea00a24332dfb01836cd49007db5cc8bb0811ae2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgra...@ubuntu.com> Date: Mon, 10 Aug 2020 16:17:44 -0400 Subject: [PATCH 07/10] shared/subprocess: s/Pid/PID/ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Graber <stgra...@ubuntu.com> --- shared/subprocess/proc.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/shared/subprocess/proc.go b/shared/subprocess/proc.go index 45a02cddf3..9dc70abf56 100644 --- a/shared/subprocess/proc.go +++ b/shared/subprocess/proc.go @@ -25,7 +25,7 @@ type Process struct { Name string `yaml:"name"` Args []string `yaml:"args,flow"` Apparmor string `yaml:"apparmor"` - Pid int64 `yaml:"pid"` + PID int64 `yaml:"pid"` Stdout string `yaml:"stdout"` Stderr string `yaml:"stderr"` } @@ -45,10 +45,10 @@ func (p *Process) hasApparmor() bool { // GetPid returns the pid for the given process object func (p *Process) GetPid() (int64, error) { - pr, _ := os.FindProcess(int(p.Pid)) + pr, _ := os.FindProcess(int(p.PID)) err := pr.Signal(syscall.Signal(0)) if err == nil { - return p.Pid, nil + return p.PID, nil } return 0, ErrNotRunning @@ -61,7 +61,7 @@ func (p *Process) SetApparmor(profile string) { // Stop will stop the given process object func (p *Process) Stop() error { - pr, _ := os.FindProcess(int(p.Pid)) + pr, _ := os.FindProcess(int(p.PID)) // Check if process exists. err := pr.Signal(syscall.Signal(0)) @@ -128,7 +128,7 @@ func (p *Process) Start() error { return errors.Wrapf(err, "Unable to start process") } - p.Pid = int64(cmd.Process.Pid) + p.PID = int64(cmd.Process.Pid) // Reset exitCode/exitErr p.exitCode = 0 @@ -171,7 +171,7 @@ func (p *Process) Restart() error { // Reload sends the SIGHUP signal to the given process object func (p *Process) Reload() error { - pr, _ := os.FindProcess(int(p.Pid)) + pr, _ := os.FindProcess(int(p.PID)) err := pr.Signal(syscall.Signal(0)) if err == nil { err = pr.Signal(syscall.SIGHUP) @@ -203,7 +203,7 @@ func (p *Process) Save(path string) error { // Signal will send a signal to the given process object given a signal value func (p *Process) Signal(signal int64) error { - pr, _ := os.FindProcess(int(p.Pid)) + pr, _ := os.FindProcess(int(p.PID)) err := pr.Signal(syscall.Signal(0)) if err == nil { err = pr.Signal(syscall.Signal(signal)) From 6a0cd60068ef6472df22d73000de518d9d1c7eaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgra...@ubuntu.com> Date: Mon, 10 Aug 2020 16:18:57 -0400 Subject: [PATCH 08/10] shared/subprocess: Add credentials MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Graber <stgra...@ubuntu.com> --- shared/subprocess/proc.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/shared/subprocess/proc.go b/shared/subprocess/proc.go index 9dc70abf56..060637e681 100644 --- a/shared/subprocess/proc.go +++ b/shared/subprocess/proc.go @@ -28,6 +28,10 @@ type Process struct { PID int64 `yaml:"pid"` Stdout string `yaml:"stdout"` Stderr string `yaml:"stderr"` + + UID uint32 `yaml:"uid"` + GID uint32 `yaml:"gid"` + SetGroups bool `yaml:"set_groups"` } func (p *Process) hasApparmor() bool { @@ -59,6 +63,12 @@ func (p *Process) SetApparmor(profile string) { p.Apparmor = profile } +// SetCreds allows setting process credentials. +func (p *Process) SetCreds(uid uint32, gid uint32) { + p.UID = uid + p.GID = gid +} + // Stop will stop the given process object func (p *Process) Stop() error { pr, _ := os.FindProcess(int(p.PID)) @@ -101,6 +111,12 @@ func (p *Process) Start() error { cmd.SysProcAttr = &syscall.SysProcAttr{} cmd.SysProcAttr.Setsid = true + if p.UID != 0 || p.GID != 0 { + cmd.SysProcAttr.Credential = &syscall.Credential{} + cmd.SysProcAttr.Credential.Uid = p.UID + cmd.SysProcAttr.Credential.Gid = p.GID + } + // Setup output capture. if p.Stdout != "" { out, err := os.Create(p.Stdout) From 7d13669ca6abb80c791c617dceaadca7e289e3d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgra...@ubuntu.com> Date: Mon, 10 Aug 2020 16:19:14 -0400 Subject: [PATCH 09/10] lxd/network: forkdns and creds drop for forkdns 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/network/driver_bridge.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lxd/network/driver_bridge.go b/lxd/network/driver_bridge.go index cb098e4b88..99542a2580 100644 --- a/lxd/network/driver_bridge.go +++ b/lxd/network/driver_bridge.go @@ -1544,6 +1544,12 @@ func (n *bridge) spawnForkDNS(listenAddress string) error { return fmt.Errorf("Failed to create subprocess: %s", err) } + // Drop privileges. + p.SetCreds(n.state.OS.UnprivUID, n.state.OS.UnprivGID) + + // Apply AppArmor profile. + p.SetApparmor(apparmor.ForkdnsProfileName(n)) + err = p.Start() if err != nil { return fmt.Errorf("Failed to run: %s %s: %v", command, strings.Join(forkdnsargs, " "), err) From e008641ea17315147f1c3585dc1b23c58479a1ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgra...@ubuntu.com> Date: Mon, 10 Aug 2020 16:26:54 -0400 Subject: [PATCH 10/10] lxd/network: Run dnsmasq as unpriv group 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/network/driver_bridge.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lxd/network/driver_bridge.go b/lxd/network/driver_bridge.go index 99542a2580..6891af0033 100644 --- a/lxd/network/driver_bridge.go +++ b/lxd/network/driver_bridge.go @@ -1269,6 +1269,9 @@ func (n *bridge) setup(oldConfig map[string]string) error { if n.state.OS.UnprivUser != "" { dnsmasqCmd = append(dnsmasqCmd, []string{"-u", n.state.OS.UnprivUser}...) } + if n.state.OS.UnprivGroup != "" { + dnsmasqCmd = append(dnsmasqCmd, []string{"-g", n.state.OS.UnprivGroup}...) + } // Create DHCP hosts directory. if !shared.PathExists(shared.VarPath("networks", n.name, "dnsmasq.hosts")) {
_______________________________________________ lxc-devel mailing list lxc-devel@lists.linuxcontainers.org http://lists.linuxcontainers.org/listinfo/lxc-devel