Update lxc config to allow mounting loop devices #1826

Merged
merged 10 commits into from Mar 17, 2015
@@ -481,6 +481,7 @@ type ContainerConfig struct {
AptProxy proxy.Settings
AptMirror string
PreferIPv6 bool
+ AllowLXCLoopMounts bool
*UpdateBehavior
}
@@ -270,6 +270,7 @@ func (p *ProvisionerAPI) ContainerConfig() (params.ContainerConfig, error) {
result.Proxy = config.ProxySettings()
result.AptProxy = config.AptProxySettings()
result.PreferIPv6 = config.PreferIPv6()
+ result.AllowLXCLoopMounts, _ = config.AllowLXCLoopMounts()
return result, nil
}
@@ -1297,7 +1297,8 @@ func (s *withoutStateServerSuite) TestContainerManagerConfigNoIPForwarding(c *gc
func (s *withoutStateServerSuite) TestContainerConfig(c *gc.C) {
attrs := map[string]interface{}{
- "http-proxy": "http://proxy.example.com:9000",
+ "http-proxy": "http://proxy.example.com:9000",
+ "allow-lxc-loop-mounts": true,
}
err := s.State.UpdateEnvironConfig(attrs, nil, nil)
c.Assert(err, jc.ErrorIsNil)
@@ -1314,6 +1315,7 @@ func (s *withoutStateServerSuite) TestContainerConfig(c *gc.C) {
c.Check(results.Proxy, gc.DeepEquals, expectedProxy)
c.Check(results.AptProxy, gc.DeepEquals, expectedProxy)
c.Check(results.PreferIPv6, jc.IsTrue)
+ c.Check(results.AllowLXCLoopMounts, jc.IsTrue)
}
func (s *withoutStateServerSuite) TestSetSupportedContainers(c *gc.C) {
View
@@ -33,7 +33,8 @@ type Manager interface {
CreateContainer(
machineConfig *cloudinit.MachineConfig,
series string,
- network *NetworkConfig) (instance.Instance, *instance.HardwareCharacteristics, error)
+ network *NetworkConfig,
+ storage *StorageConfig) (instance.Instance, *instance.HardwareCharacteristics, error)
// DestroyContainer stops and destroyes the container identified by
// instance id.
View
@@ -104,6 +104,7 @@ func (manager *containerManager) CreateContainer(
machineConfig *cloudinit.MachineConfig,
series string,
networkConfig *container.NetworkConfig,
+ storageConfig *container.StorageConfig,
) (instance.Instance, *instance.HardwareCharacteristics, error) {
name := names.NewMachineTag(machineConfig.MachineId).String()
@@ -97,7 +97,7 @@ func createContainer(c *gc.C, manager container.Manager, machineId string) insta
err = environs.FinishMachineConfig(machineConfig, environConfig)
c.Assert(err, jc.ErrorIsNil)
- inst, hardware, err := manager.CreateContainer(machineConfig, "precise", network)
+ inst, hardware, err := manager.CreateContainer(machineConfig, "precise", network, nil)
c.Assert(err, jc.ErrorIsNil)
c.Assert(hardware, gc.NotNil)
expected := fmt.Sprintf("arch=%s cpu-cores=1 mem=512M root-disk=8192M", version.Current.Arch)
View
@@ -195,6 +195,7 @@ func (manager *containerManager) CreateContainer(
machineConfig *cloudinit.MachineConfig,
series string,
networkConfig *container.NetworkConfig,
+ storageConfig *container.StorageConfig,
) (inst instance.Instance, _ *instance.HardwareCharacteristics, err error) {
// Check our preconditions
if manager == nil {
@@ -203,6 +204,8 @@ func (manager *containerManager) CreateContainer(
panic("series not set")
} else if networkConfig == nil {
panic("networkConfig is nil")
+ } else if storageConfig == nil {
+ panic("storageConfig is nil")
}
// Log how long the start took
@@ -318,6 +321,12 @@ func (manager *containerManager) CreateContainer(
if err := mountHostLogDir(name, manager.logdir); err != nil {
return nil, nil, errors.Annotate(err, "failed to mount the directory to log to")
}
+ if storageConfig.AllowMount {
+ // Add config to allow loop devices to be mounted inside the container.
+ if err := allowLoopbackBlockDevices(name); err != nil {
+ return nil, nil, errors.Annotate(err, "failed to configure the container for loopback devices")
+ }
+ }
// Update the network settings inside the run-time config of the
// container (e.g. /var/lib/lxc/<name>/config) before starting it.
netConfig := generateNetworkConfig(networkConfig)
@@ -754,6 +763,15 @@ func mountHostLogDir(name, logDir string) error {
return appendToContainerConfig(name, line)
}
+func allowLoopbackBlockDevices(name string) error {
+ const allowLoopDevicesCfg = `
+lxc.aa_profile = lxc-container-default-with-mounting
+lxc.cgroup.devices.allow = b 7:* rwm
+lxc.cgroup.devices.allow = c 10:237 rwm
+`
+ return appendToContainerConfig(name, allowLoopDevicesCfg)
+}
+
func (manager *containerManager) DestroyContainer(id instance.Id) error {
start := time.Now()
name := string(id)
@@ -38,6 +38,8 @@ import (
"github.com/juju/juju/network"
"github.com/juju/juju/provider/dummy"
"github.com/juju/juju/service"
+ "github.com/juju/juju/storage"
+ "github.com/juju/juju/storage/provider"
coretesting "github.com/juju/juju/testing"
)
@@ -285,25 +287,26 @@ func (s *LxcSuite) TestUpdateContainerConfig(c *gc.C) {
DeviceIndex: 1,
InterfaceName: "eth1",
}})
+ storageConfig := container.NewStorageConfig([]storage.VolumeParams{{Provider: provider.LoopProviderType}})
manager := s.makeManager(c, "test")
machineConfig, err := containertesting.MockMachineConfig("1/lxc/0")
c.Assert(err, jc.ErrorIsNil)
envConfig, err := config.New(config.NoDefaults, dummy.SampleConfig())
c.Assert(err, jc.ErrorIsNil)
machineConfig.Config = envConfig
- instance := containertesting.CreateContainerWithMachineAndNetworkConfig(
- c, manager, machineConfig, networkConfig,
+ instance := containertesting.CreateContainerWithMachineAndNetworkAndStorageConfig(
+ c, manager, machineConfig, networkConfig, storageConfig,
)
name := string(instance.Id())
// Append a few extra lines to the config.
extraLines := []string{
" lxc.rootfs = /some/thing # else ",
"",
- " # just comment ",
+ " # just comment",
"lxc.network.vlan.id=42",
- "something else # ignore ",
+ "something else # ignore",
"lxc.network.type=veth",
"lxc.network.link = foo # comment",
"lxc.network.hwaddr = bar",
@@ -337,6 +340,10 @@ lxc.network.mtu = 4321
lxc.mount.entry = %s var/log/juju none defaults,bind 0 0
+
+lxc.aa_profile = lxc-container-default-with-mounting
+lxc.cgroup.devices.allow = b 7:* rwm
+lxc.cgroup.devices.allow = c 10:237 rwm
`, s.logDir) + strings.Join(extraLines, "\n") + "\n"
lxcConfContents, err := ioutil.ReadFile(configPath)
@@ -383,11 +390,15 @@ lxc.network.name = em1
lxc.network.mtu = 4321
+
+lxc.aa_profile = lxc-container-default-with-mounting
+lxc.cgroup.devices.allow = b 7:* rwm
+lxc.cgroup.devices.allow = c 10:237 rwm
lxc.rootfs = /foo/bar
- # just comment
+ # just comment
lxc.network.vlan.id = 69
-something else # ignore
+something else # ignore
lxc.network.type = phys
lxc.network.link = foo # comment
lxc.network.hwaddr = deadbeef
@@ -985,6 +996,39 @@ lxc.mount.entry = %s var/log/juju none defaults,bind 0 0
c.Assert(autostartLink, jc.DoesNotExist)
}
+func (s *LxcSuite) TestCreateContainerWithBlockStorage(c *gc.C) {
+ err := os.Remove(s.RestartDir)
+ c.Assert(err, jc.ErrorIsNil)
+
+ manager := s.makeManager(c, "test")
+ machineConfig, err := containertesting.MockMachineConfig("1/lxc/0")
+ c.Assert(err, jc.ErrorIsNil)
+ storageConfig := container.NewStorageConfig([]storage.VolumeParams{{Provider: provider.LoopProviderType}})
+ networkConfig := container.BridgeNetworkConfig("nic42", nil)
+ instance := containertesting.CreateContainerWithMachineAndNetworkAndStorageConfig(c, manager, machineConfig, networkConfig, storageConfig)
+ name := string(instance.Id())
+ autostartLink := lxc.RestartSymlink(name)
+ config, err := ioutil.ReadFile(lxc.ContainerConfigFilename(name))
+ c.Assert(err, jc.ErrorIsNil)
+ expected := fmt.Sprintf(`
+# network config
+# interface "eth0"
+lxc.network.type = veth
+lxc.network.link = nic42
+lxc.network.flags = up
+lxc.network.mtu = 4321
+
+lxc.start.auto = 1
+lxc.mount.entry = %s var/log/juju none defaults,bind 0 0
+
+lxc.aa_profile = lxc-container-default-with-mounting
+lxc.cgroup.devices.allow = b 7:* rwm
+lxc.cgroup.devices.allow = c 10:237 rwm
+`, s.logDir)
+ c.Assert(string(config), gc.Equals, expected)
+ c.Assert(autostartLink, jc.DoesNotExist)
+}
+
func (s *LxcSuite) TestDestroyContainerRemovesAutostartLink(c *gc.C) {
manager := s.makeManager(c, "test")
instance := containertesting.CreateContainer(c, manager, "1/lxc/0")
View
@@ -0,0 +1,44 @@
+// Copyright 2015 Canonical Ltd.
+// Licensed under the AGPLv3, see LICENCE file for details.
+
+package container
+
+import (
+ "errors"
+
+ "github.com/juju/juju/storage"
+ "github.com/juju/juju/storage/provider"
+)
+
+// ErrLoopMountNotAllowed is used when loop devices are requested to be
+// mounted inside an LXC container, but this has not been allowed using
+// an environment config setting.
+var ErrLoopMountNotAllowed = errors.New(`
+Mounting of loop devices inside LXC containers must be explicitly enabled using this environment config setting:
+ allow-lxc-loop-mounts=true
+`[1:])
+
+// StorageConfig defines how the container will be configured to support
+// storage requirements.
+type StorageConfig struct {
+
+ // AllowMount is true is the container is required to allow
+ // mounting block devices.
+ AllowMount bool
+}
+
+// NewStorageConfig returns a StorageConfig used to specify the
+// configuration the container uses to support storage.
+func NewStorageConfig(volumes []storage.VolumeParams) *StorageConfig {
+ allowMount := false
+ // If there is a volume using a loop provider, then
+ // allow mount must be true.
+ for _, v := range volumes {
+ allowMount = v.Provider == provider.LoopProviderType
+ if allowMount {
+ break
+ }
+ }
+ // TODO(wallyworld) - add config for HostLoopProviderType
+ return &StorageConfig{allowMount}
+}
@@ -59,21 +59,23 @@ func CreateContainerWithMachineConfig(
) instance.Instance {
networkConfig := container.BridgeNetworkConfig("nic42", nil)
- return CreateContainerWithMachineAndNetworkConfig(c, manager, machineConfig, networkConfig)
+ storageConfig := container.NewStorageConfig(nil)
+ return CreateContainerWithMachineAndNetworkAndStorageConfig(c, manager, machineConfig, networkConfig, storageConfig)
}
-func CreateContainerWithMachineAndNetworkConfig(
+func CreateContainerWithMachineAndNetworkAndStorageConfig(
c *gc.C,
manager container.Manager,
machineConfig *cloudinit.MachineConfig,
networkConfig *container.NetworkConfig,
+ storageConfig *container.StorageConfig,
) instance.Instance {
if networkConfig != nil && len(networkConfig.Interfaces) > 0 {
name := "test-" + names.NewMachineTag(machineConfig.MachineId).String()
EnsureRootFSEtcNetwork(c, name)
}
- inst, hardware, err := manager.CreateContainer(machineConfig, "quantal", networkConfig)
+ inst, hardware, err := manager.CreateContainer(machineConfig, "quantal", networkConfig, storageConfig)
c.Assert(err, jc.ErrorIsNil)
c.Assert(hardware, gc.NotNil)
c.Assert(hardware.String(), gc.Not(gc.Equals), "")
@@ -114,8 +116,9 @@ func CreateContainerTest(c *gc.C, manager container.Manager, machineId string) (
machineConfig.Config = envConfig
network := container.BridgeNetworkConfig("nic42", nil)
+ storage := container.NewStorageConfig(nil)
- inst, hardware, err := manager.CreateContainer(machineConfig, "quantal", network)
+ inst, hardware, err := manager.CreateContainer(machineConfig, "quantal", network, storage)
if err != nil {
return nil, errors.Trace(err)
@@ -150,6 +150,11 @@ const (
// The default block storage source.
StorageDefaultBlockSourceKey = "storage-default-block-source"
+ // For LXC containers, is the container allowed to mount block
+ // devices. A theoretical security issue, so must be explicitly
+ // allowed by the user.
+ AllowLXCLoopMounts = "allow-lxc-loop-mounts"
+
//
// Deprecated Settings Attributes
//
@@ -1124,6 +1129,13 @@ func (c *Config) StorageDefaultBlockSource() (string, bool) {
return bs, bs != ""
}
+// AllowLXCLoopMounts returns whether loop devices are allowed
+// to be mounted inside lxc containers.
+func (c *Config) AllowLXCLoopMounts() (bool, bool) {
+ v, ok := c.defined[AllowLXCLoopMounts].(bool)
+ return v, ok
+}
+
// UnknownAttrs returns a copy of the raw configuration attributes
// that are supposedly specific to the environment type. They could
// also be wrong attributes, though. Only the specific environment
@@ -1215,6 +1227,7 @@ var fields = schema.Fields{
PreventRemoveObjectKey: schema.Bool(),
PreventAllChangesKey: schema.Bool(),
StorageDefaultBlockSourceKey: schema.String(),
+ AllowLXCLoopMounts: schema.Bool(),
// Deprecated fields, retain for backwards compatibility.
ToolsMetadataURLKey: schema.String(),
@@ -1257,6 +1270,7 @@ var alwaysOptional = schema.Defaults{
"disable-network-management": schema.Omit,
AgentStreamKey: schema.Omit,
SetNumaControlPolicyKey: DefaultNumaControlPolicy,
+ AllowLXCLoopMounts: false,
// Storage related config.
// Environ providers will specify their own defaults.
@@ -196,6 +196,27 @@ var configTests = []configTest{
"lxc-use-clone": false,
"lxc-clone": true,
},
+ }, {
+ about: "Allow LXC loop mounts true",
+ useDefaults: config.UseDefaults,
+ attrs: testing.Attrs{
+ "type": "my-type",
+ "name": "my-name",
+ "allow-lxc-loop-mounts": "true",
+ },
+ }, {
+ about: "Allow LXC loop mounts default",
+ useDefaults: config.UseDefaults,
+ attrs: testing.Attrs{
+ "type": "my-type",
+ "name": "my-name",
+ "allow-lxc-loop-mounts": "false",
+ },
+ expected: testing.Attrs{
+ "type": "my-type",
+ "name": "my-name",
+ "allow-lxc-loop-mounts": false,
+ },
}, {
about: "CA cert & key from path",
useDefaults: config.UseDefaults,
@@ -1345,6 +1366,7 @@ func (s *ConfigSuite) TestConfigAttrs(c *gc.C) {
attrs["lxc-clone-aufs"] = false
attrs["prefer-ipv6"] = false
attrs["set-numa-control-policy"] = false
+ attrs["allow-lxc-loop-mounts"] = false
// Default firewall mode is instance
attrs["firewall-mode"] = string(config.FwInstance)
Oops, something went wrong.