From df94011db26ef150fd6ea67764c3bac198738744 Mon Sep 17 00:00:00 2001 From: Qian Zhang Date: Fri, 26 Jul 2024 11:28:08 +0800 Subject: [PATCH 01/14] lxd/instance: Support `security.protection.start` config option Signed-off-by: Qian Zhang --- lxd/instance/drivers/driver_lxc.go | 5 +++++ lxd/instance/drivers/driver_qemu.go | 5 +++++ lxd/instance/instancetype/instance.go | 9 +++++++++ lxd/instances.go | 7 +++++-- 4 files changed, 24 insertions(+), 2 deletions(-) diff --git a/lxd/instance/drivers/driver_lxc.go b/lxd/instance/drivers/driver_lxc.go index eb55d59a2d27..12a5a4e99180 100644 --- a/lxd/instance/drivers/driver_lxc.go +++ b/lxd/instance/drivers/driver_lxc.go @@ -2582,6 +2582,11 @@ func (d *lxc) validateStartup(statusCode api.StatusCode) error { return fmt.Errorf("The image used by this instance requires nesting. Please set security.nesting=true on the instance") } + // Check if instance is start protected. + if shared.IsTrue(d.expandedConfig["security.protection.start"]) { + return fmt.Errorf("Instance is protected from being started") + } + return nil } diff --git a/lxd/instance/drivers/driver_qemu.go b/lxd/instance/drivers/driver_qemu.go index 5b1a660cfc87..354642bef5f0 100644 --- a/lxd/instance/drivers/driver_qemu.go +++ b/lxd/instance/drivers/driver_qemu.go @@ -1095,6 +1095,11 @@ func (d *qemu) validateStartup(stateful bool, statusCode api.StatusCode) error { return fmt.Errorf("Stateful start requires migration.stateful to be set to true") } + // Check if instance is start protected. + if shared.IsTrue(d.expandedConfig["security.protection.start"]) { + return fmt.Errorf("Instance is protected from being started") + } + return nil } diff --git a/lxd/instance/instancetype/instance.go b/lxd/instance/instancetype/instance.go index 0695cbe626ff..4a7426844f04 100644 --- a/lxd/instance/instancetype/instance.go +++ b/lxd/instance/instancetype/instance.go @@ -320,6 +320,15 @@ var InstanceConfigKeysAny = map[string]func(value string) error{ // shortdesc: Prevents the instance from being deleted "security.protection.delete": validate.Optional(validate.IsBool), + // lxdmeta:generate(entities=instance; group=security; key=security.protection.start) + // + // --- + // type: bool + // defaultdesc: `false` + // liveupdate: yes + // shortdesc: Whether to prevent the instance from being started + "security.protection.start": validate.Optional(validate.IsBool), + // lxdmeta:generate(entities=instance; group=snapshots; key=snapshots.schedule) // Specify either a cron expression (` `), a comma-separated list of schedule aliases (`@hourly`, `@daily`, `@midnight`, `@weekly`, `@monthly`, `@annually`, `@yearly`), or leave empty to disable automatic snapshots. // diff --git a/lxd/instances.go b/lxd/instances.go index 0c0ee97c2685..9b88268773b7 100644 --- a/lxd/instances.go +++ b/lxd/instances.go @@ -252,13 +252,16 @@ func (slice instanceAutostartList) Swap(i, j int) { var instancesStartMu sync.Mutex // instanceShouldAutoStart returns whether the instance should be auto-started. -// Returns true if boot.autostart is enabled or boot.autostart is not set and instance was previously running. +// Returns true if the conditions below are all met: +// 1. security.protection.start is not enabled or not set. +// 2. boot.autostart is enabled or boot.autostart is not set and instance was previously running. func instanceShouldAutoStart(inst instance.Instance) bool { config := inst.ExpandedConfig() autoStart := config["boot.autostart"] lastState := config["volatile.last_state.power"] + protectStart := config["security.protection.start"] - return shared.IsTrue(autoStart) || (autoStart == "" && lastState == instance.PowerStateRunning) + return shared.IsFalseOrEmpty(protectStart) && (shared.IsTrue(autoStart) || (autoStart == "" && lastState == instance.PowerStateRunning)) } func instancesStart(s *state.State, instances []instance.Instance) { From 8b69af49f2c55775ab7a4bd4005c0e82558da616 Mon Sep 17 00:00:00 2001 From: Qian Zhang Date: Fri, 26 Jul 2024 11:30:26 +0800 Subject: [PATCH 02/14] scripts/bash/lxd-client: Add `security.protection.start` config option Signed-off-by: Qian Zhang --- scripts/bash/lxd-client | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/bash/lxd-client b/scripts/bash/lxd-client index a8d4c210382c..19b7b55ad698 100644 --- a/scripts/bash/lxd-client +++ b/scripts/bash/lxd-client @@ -149,7 +149,7 @@ _have lxc && { security.devlxd security.devlxd.images \ security.idmap.base security.idmap.isolated security.idmap.size \ security.nesting security.privileged \ - security.protection.delete security.protection.shift \ + security.protection.delete security.protection.shift security.protection.start\ security.secureboot \ security.sev security.sev.es security.sev.policy.es security.sev.session.dh security.sev.session.data \ security.syscalls.allow \ From 77ecd5cee11c84d5a8e5d95673b021618f9d458d Mon Sep 17 00:00:00 2001 From: Qian Zhang Date: Fri, 26 Jul 2024 11:31:26 +0800 Subject: [PATCH 03/14] api: Add `instance_protection_start` extension Signed-off-by: Qian Zhang --- doc/api-extensions.md | 5 +++++ shared/version/api.go | 1 + 2 files changed, 6 insertions(+) diff --git a/doc/api-extensions.md b/doc/api-extensions.md index 1d6a57d9d3d5..82540716a611 100644 --- a/doc/api-extensions.md +++ b/doc/api-extensions.md @@ -2441,3 +2441,8 @@ In this scenario, the creation and startup is part of a single background operat Enables the {config:option}`instance-security:security.devlxd.images` configuration option for virtual machines. This controls the availability of a `/1.0/images/FINGERPRINT/export` API over `devlxd`. This can be used by a virtual machine running LXD to access raw images from the host. + +## `instance_protection_start` + +Enables setting the {config:option}`instance-security:security.protection.start` field which prevents instances +from being started if set to `true`. diff --git a/shared/version/api.go b/shared/version/api.go index d6846924bb2f..fc3d4ca70e85 100644 --- a/shared/version/api.go +++ b/shared/version/api.go @@ -411,6 +411,7 @@ var APIExtensions = []string{ "instance_import_conversion", "instance_create_start", "devlxd_images_vm", + "instance_protection_start", } // APIExtensionsCount returns the number of available API extensions. From b783523b7d1583f0b4c957d007b33098213c49ae Mon Sep 17 00:00:00 2001 From: Qian Zhang Date: Fri, 26 Jul 2024 11:36:28 +0800 Subject: [PATCH 04/14] test: Add test for `security.protection.start` Signed-off-by: Qian Zhang --- test/suites/security.sh | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/suites/security.sh b/test/suites/security.sh index 7a09a46e8401..7cfcaf384fc7 100644 --- a/test/suites/security.sh +++ b/test/suites/security.sh @@ -161,6 +161,18 @@ test_security_protection() { lxc profile unset default security.protection.delete + # Test start protection + lxc profile set default security.protection.start true + + lxc init testimage c1 + ! lxc start c1 || false + + lxc config set c1 security.protection.start false + lxc start c1 + lxc delete c1 --force + + lxc profile unset default security.protection.start + # Test shifting protection # Respawn LXD with kernel ID shifting support disabled to force manual shifting. From 7a51951ac19f8e66c624ccf4c5ebc44013379c31 Mon Sep 17 00:00:00 2001 From: Qian Zhang Date: Fri, 26 Jul 2024 11:38:36 +0800 Subject: [PATCH 05/14] metadata: Add `security.protection.start` Signed-off-by: Qian Zhang --- doc/metadata.txt | 10 +++++++++- lxd/metadata/configuration.json | 11 ++++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/doc/metadata.txt b/doc/metadata.txt index 6c3e2819dd3d..529ddca8ab84 100644 --- a/doc/metadata.txt +++ b/doc/metadata.txt @@ -2165,7 +2165,7 @@ See {ref}`container-security` for more information. ```{config:option} security.protection.delete instance-security :defaultdesc: "`false`" :liveupdate: "yes" -:shortdesc: "Prevents the instance from being deleted" +:shortdesc: "Whether to prevent the instance from being deleted" :type: "bool" ``` @@ -2179,6 +2179,14 @@ See {ref}`container-security` for more information. Set this option to `true` to prevent the instance's file system from being UID/GID shifted on startup. ``` +```{config:option} security.protection.start instance-security +:defaultdesc: "`false`" +:liveupdate: "yes" +:shortdesc: "Whether to prevent the instance from being started" +:type: "bool" + +``` + ```{config:option} security.secureboot instance-security :condition: "virtual machine" :defaultdesc: "`true`" diff --git a/lxd/metadata/configuration.json b/lxd/metadata/configuration.json index 0164c963b8d1..f5204889f0af 100644 --- a/lxd/metadata/configuration.json +++ b/lxd/metadata/configuration.json @@ -2452,7 +2452,7 @@ "defaultdesc": "`false`", "liveupdate": "yes", "longdesc": "", - "shortdesc": "Prevents the instance from being deleted", + "shortdesc": "Whether to prevent the instance from being deleted", "type": "bool" } }, @@ -2466,6 +2466,15 @@ "type": "bool" } }, + { + "security.protection.start": { + "defaultdesc": "`false`", + "liveupdate": "yes", + "longdesc": "", + "shortdesc": "Whether to prevent the instance from being started", + "type": "bool" + } + }, { "security.secureboot": { "condition": "virtual machine", From 811f1f34a87b7161f8d2f6f071889caad35c0b75 Mon Sep 17 00:00:00 2001 From: Qian Zhang Date: Fri, 26 Jul 2024 11:39:17 +0800 Subject: [PATCH 06/14] doc/howto: Add instructions on preventing accidental start of instances Signed-off-by: Qian Zhang --- doc/howto/instances_manage.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/doc/howto/instances_manage.md b/doc/howto/instances_manage.md index ffedfa95d948..5ae781517839 100644 --- a/doc/howto/instances_manage.md +++ b/doc/howto/instances_manage.md @@ -131,6 +131,11 @@ Once it is running, you can select the {guilabel}`Terminal` tab to access the in ``` ```` +### Prevent accidental start of instances + +To protect a specific instance from being started, set {config:option}`instance-security:security.protection.start` to `true` for the instance. +See {ref}`instances-configure` for instructions. + (instances-manage-stop)= ## Stop an instance From 80b53ccf802d403887e6ac176c0d92d5c88912ce Mon Sep 17 00:00:00 2001 From: Qian Zhang Date: Mon, 29 Jul 2024 22:19:18 +0800 Subject: [PATCH 07/14] lxd/instance:: Update the error messages for `security.protection.delete` Signed-off-by: Qian Zhang --- lxd/instance/drivers/driver_lxc.go | 2 +- lxd/instance/drivers/driver_qemu.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lxd/instance/drivers/driver_lxc.go b/lxd/instance/drivers/driver_lxc.go index 12a5a4e99180..b6a582d79951 100644 --- a/lxd/instance/drivers/driver_lxc.go +++ b/lxd/instance/drivers/driver_lxc.go @@ -3740,7 +3740,7 @@ func (d *lxc) delete(force bool) error { } if !force && shared.IsTrue(d.expandedConfig["security.protection.delete"]) && !d.IsSnapshot() { - err := fmt.Errorf("Instance is protected") + err := fmt.Errorf("Instance is protected from being deleted") d.logger.Warn("Failed to delete instance", logger.Ctx{"err": err}) return err } diff --git a/lxd/instance/drivers/driver_qemu.go b/lxd/instance/drivers/driver_qemu.go index 354642bef5f0..2b178b4ea744 100644 --- a/lxd/instance/drivers/driver_qemu.go +++ b/lxd/instance/drivers/driver_qemu.go @@ -6119,7 +6119,7 @@ func (d *qemu) delete(force bool) error { // Check if instance is delete protected. if !force && shared.IsTrue(d.expandedConfig["security.protection.delete"]) && !d.IsSnapshot() { - return fmt.Errorf("Instance is protected") + return fmt.Errorf("Instance is protected from being deleted") } // Delete any persistent warnings for instance. From e276e37837e0f25f9dd9377a760824e814ad9e69 Mon Sep 17 00:00:00 2001 From: Qian Zhang Date: Tue, 30 Jul 2024 20:10:37 +0800 Subject: [PATCH 08/14] lxd/instance:: Update the comments for `security.protection.delete` Signed-off-by: Qian Zhang --- lxd/instance/instancetype/instance.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lxd/instance/instancetype/instance.go b/lxd/instance/instancetype/instance.go index 4a7426844f04..c57075f5a398 100644 --- a/lxd/instance/instancetype/instance.go +++ b/lxd/instance/instancetype/instance.go @@ -317,7 +317,7 @@ var InstanceConfigKeysAny = map[string]func(value string) error{ // type: bool // defaultdesc: `false` // liveupdate: yes - // shortdesc: Prevents the instance from being deleted + // shortdesc: Whether to prevent the instance from being deleted "security.protection.delete": validate.Optional(validate.IsBool), // lxdmeta:generate(entities=instance; group=security; key=security.protection.start) From 6be3fbfdc02ca2ea50fc5aeed2e3ff802a06894a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Graber?= Date: Thu, 11 Jan 2024 14:09:51 -0500 Subject: [PATCH 09/14] api: disk_io_bus_virtio_blk MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Graber (cherry picked from commit 61b718d77c2a188279e617fde44608b27eaa454b) Signed-off-by: Kadin Sayani License: Apache-2.0 --- doc/api-extensions.md | 5 +++++ shared/version/api.go | 1 + 2 files changed, 6 insertions(+) diff --git a/doc/api-extensions.md b/doc/api-extensions.md index 82540716a611..61eb937d9b20 100644 --- a/doc/api-extensions.md +++ b/doc/api-extensions.md @@ -2446,3 +2446,8 @@ This can be used by a virtual machine running LXD to access raw images from the Enables setting the {config:option}`instance-security:security.protection.start` field which prevents instances from being started if set to `true`. + +## `disk_io_bus_virtio_blk` + +Adds a new `virtio-blk` value for `io.bus` on `disk` devices which allows +for the attached disk to be connected to the `virtio-blk` bus. diff --git a/shared/version/api.go b/shared/version/api.go index fc3d4ca70e85..c120618f044d 100644 --- a/shared/version/api.go +++ b/shared/version/api.go @@ -412,6 +412,7 @@ var APIExtensions = []string{ "instance_create_start", "devlxd_images_vm", "instance_protection_start", + "disk_io_bus_virtio_blk", } // APIExtensionsCount returns the number of available API extensions. From 277b3e6e95a2d581cc399da2747927f01b86f536 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Graber?= Date: Thu, 11 Jan 2024 14:12:51 -0500 Subject: [PATCH 10/14] lxd/device/disk: Add virtio-blk MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Graber (cherry picked from commit 6938b423a41847016bb448d88a4af85858b0d12c) Signed-off-by: Kadin Sayani License: Apache-2.0 --- lxd/device/disk.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lxd/device/disk.go b/lxd/device/disk.go index 0627a00e3a46..2f9dd57baab5 100644 --- a/lxd/device/disk.go +++ b/lxd/device/disk.go @@ -327,7 +327,7 @@ func (d *disk) validateConfig(instConf instance.ConfigReader) error { // required: no // condition: virtual machine // shortdesc: Bus for the device - "io.bus": validate.Optional(validate.IsOneOf("virtio-scsi", "nvme")), + "io.bus": validate.Optional(validate.IsOneOf("nvme", "virtio-blk", "virtio-scsi")), } err := d.config.Validate(rules) From 9ddf9b1504d03a5b36fb8490ce0d956745f5b752 Mon Sep 17 00:00:00 2001 From: Kadin Sayani Date: Fri, 9 Aug 2024 19:33:07 +0000 Subject: [PATCH 11/14] lxd/device/disk: Add virtio-blk as possible value Signed-off-by: Kadin Sayani --- lxd/device/disk.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lxd/device/disk.go b/lxd/device/disk.go index 2f9dd57baab5..ce924b9bdca0 100644 --- a/lxd/device/disk.go +++ b/lxd/device/disk.go @@ -320,7 +320,7 @@ func (d *disk) validateConfig(instConf instance.ConfigReader) error { // shortdesc: Caching mode for the device "io.cache": validate.Optional(validate.IsOneOf("none", "writeback", "unsafe")), // lxdmeta:generate(entities=device-disk; group=device-conf; key=io.bus) - // Possible values are `virtio-scsi` or `nvme`. + // Possible values are `virtio-scsi`, `virtio-blk` or `nvme`. // --- // type: string // defaultdesc: `virtio-scsi` From 8f1eb1b2bc0bb3b946938633c5c7acfdf9608e78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Graber?= Date: Thu, 11 Jan 2024 14:39:44 -0500 Subject: [PATCH 12/14] lxd/instance/qemu: Add virtio-blk support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Graber (cherry picked from commit f127b7aa631e7fe6304ea139ccc44f2dec71e895) Signed-off-by: Kadin Sayani License: Apache-2.0 --- lxd/instance/drivers/driver_qemu.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lxd/instance/drivers/driver_qemu.go b/lxd/instance/drivers/driver_qemu.go index 2b178b4ea744..e813749954d1 100644 --- a/lxd/instance/drivers/driver_qemu.go +++ b/lxd/instance/drivers/driver_qemu.go @@ -3439,9 +3439,9 @@ func (d *qemu) generateQemuConfigFile(cpuInfo *cpuTopology, mountInfo *storagePo } qemuDev := make(map[string]string) - if busName == "nvme" { + if util.ValueInSlice(busName, []string{"nvme", "virtio-blk"}) { // Allocate a PCI(e) port and write it to the config file so QMP can "hotplug" the - // NVME drive into it later. + // drive into it later. devBus, devAddr, multi := bus.allocate(busFunctionGroupNone) // Populate the qemu device with port info. @@ -4087,7 +4087,7 @@ func (d *qemu) addDriveConfig(qemuDev map[string]string, bootIndexes map[string] } else if media == "cdrom" { qemuDev["driver"] = "scsi-cd" } - } else if bus == "nvme" { + } else if util.ValueInSlice(bus, []string{"nvme", "virtio-blk"}) { if qemuDev["bus"] == "" { // Figure out a hotplug slot. pciDevID := qemuPCIDeviceIDStart @@ -4104,12 +4104,12 @@ func (d *qemu) addDriveConfig(qemuDev map[string]string, bootIndexes map[string] } pciDeviceName := fmt.Sprintf("%s%d", busDevicePortPrefix, pciDevID) - d.logger.Debug("Using PCI bus device to hotplug NVME into", logger.Ctx{"device": driveConf.DevName, "port": pciDeviceName}) + d.logger.Debug("Using PCI bus device to hotplug drive into", logger.Ctx{"device": driveConf.DevName, "port": pciDeviceName}) qemuDev["bus"] = pciDeviceName qemuDev["addr"] = "00.0" } - qemuDev["driver"] = "nvme" + qemuDev["driver"] = bus } if bootIndexes != nil { From 40b2ab11052b64f83da6914a1e1614210388675b Mon Sep 17 00:00:00 2001 From: Kadin Sayani Date: Fri, 9 Aug 2024 22:04:40 +0000 Subject: [PATCH 13/14] lxd/instance/qemu: Replece util.ValueInSlice() with shared.ValueInSlice() Signed-off-by: Kadin Sayani --- lxd/instance/drivers/driver_qemu.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lxd/instance/drivers/driver_qemu.go b/lxd/instance/drivers/driver_qemu.go index e813749954d1..e8ad3c7ba503 100644 --- a/lxd/instance/drivers/driver_qemu.go +++ b/lxd/instance/drivers/driver_qemu.go @@ -3439,7 +3439,7 @@ func (d *qemu) generateQemuConfigFile(cpuInfo *cpuTopology, mountInfo *storagePo } qemuDev := make(map[string]string) - if util.ValueInSlice(busName, []string{"nvme", "virtio-blk"}) { + if shared.ValueInSlice(busName, []string{"nvme", "virtio-blk"}) { // Allocate a PCI(e) port and write it to the config file so QMP can "hotplug" the // drive into it later. devBus, devAddr, multi := bus.allocate(busFunctionGroupNone) @@ -4087,7 +4087,7 @@ func (d *qemu) addDriveConfig(qemuDev map[string]string, bootIndexes map[string] } else if media == "cdrom" { qemuDev["driver"] = "scsi-cd" } - } else if util.ValueInSlice(bus, []string{"nvme", "virtio-blk"}) { + } else if shared.ValueInSlice(bus, []string{"nvme", "virtio-blk"}) { if qemuDev["bus"] == "" { // Figure out a hotplug slot. pciDevID := qemuPCIDeviceIDStart From 941902ba5a33df5801d345be60fc085d51fdc037 Mon Sep 17 00:00:00 2001 From: Kadin Sayani Date: Fri, 9 Aug 2024 22:08:45 +0000 Subject: [PATCH 14/14] doc/metadata: Add virtio-blk to metadata Signed-off-by: Kadin Sayani --- doc/metadata.txt | 2 +- lxd/metadata/configuration.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/metadata.txt b/doc/metadata.txt index 529ddca8ab84..9ebc2c3cafa6 100644 --- a/doc/metadata.txt +++ b/doc/metadata.txt @@ -56,7 +56,7 @@ See {ref}`devices-disk-initial-config` for more information. :required: "no" :shortdesc: "Bus for the device" :type: "string" -Possible values are `virtio-scsi` or `nvme`. +Possible values are `virtio-scsi`, `virtio-blk` or `nvme`. ``` ```{config:option} io.cache device-disk-device-conf diff --git a/lxd/metadata/configuration.json b/lxd/metadata/configuration.json index f5204889f0af..08fb66c907e1 100644 --- a/lxd/metadata/configuration.json +++ b/lxd/metadata/configuration.json @@ -63,7 +63,7 @@ "io.bus": { "condition": "virtual machine", "defaultdesc": "`virtio-scsi`", - "longdesc": "Possible values are `virtio-scsi` or `nvme`.", + "longdesc": "Possible values are `virtio-scsi`, `virtio-blk` or `nvme`.", "required": "no", "shortdesc": "Bus for the device", "type": "string"