diff --git a/doc/api-extensions.md b/doc/api-extensions.md index 7e1e93caa3c..ef629419b1e 100644 --- a/doc/api-extensions.md +++ b/doc/api-extensions.md @@ -2646,3 +2646,13 @@ As part of this, the following configuration options have been added: ## `custom_volume_refresh_exclude_older_snapshots` This adds support for excluding source snapshots earlier than latest target snapshot. + +## `storage_initial_owner` + +This adds ability to set the initial owner of a custom volume. + +The following configuration options have been added: + +* `initial.gid` +* `initial.mode` +* `initial.uid` diff --git a/doc/reference/storage_btrfs.md b/doc/reference/storage_btrfs.md index 2734a155905..e4b8b12491a 100644 --- a/doc/reference/storage_btrfs.md +++ b/doc/reference/storage_btrfs.md @@ -72,6 +72,9 @@ Key | Type | Default | Descr Key | Type | Condition | Default | Description :-- | :--- | :-------- | :------ | :---------- +`initial.gid` | int | custom volume with content type `filesystem` | same as `volume.initial.uid` or `0` | GID of the volume owner in the instance +`initial.mode` | int | custom volume with content type `filesystem` | same as `volume.initial.mode` or `711` | Mode of the volume in the instance +`initial.uid` | int | custom volume with content type `filesystem` | same as `volume.initial.gid` or `0` | UID of the volume owner in the instance `security.shared` | bool | custom block volume | same as `volume.security.shared` or `false` | Enable sharing the volume across multiple instances `security.shifted` | bool | custom volume | same as `volume.security.shifted` or `false` | {{enable_ID_shifting}} `security.unmapped` | bool | custom volume | same as `volume.security.unmapped` or `false` | Disable ID mapping for the volume diff --git a/doc/reference/storage_ceph.md b/doc/reference/storage_ceph.md index 84fa6e53b71..937d3f3a06a 100644 --- a/doc/reference/storage_ceph.md +++ b/doc/reference/storage_ceph.md @@ -100,6 +100,9 @@ Key | Type | Condition | Default :-- | :--- | :-------- | :------ | :---------- `block.filesystem` | string | block-based volume with content type `filesystem` | same as `volume.block.filesystem` | {{block_filesystem}} `block.mount_options` | string | block-based volume with content type `filesystem` | same as `volume.block.mount_options` | Mount options for block-backed file system volumes +`initial.gid` | int | custom volume with content type `filesystem` | same as `volume.initial.uid` or `0` | GID of the volume owner in the instance +`initial.mode` | int | custom volume with content type `filesystem` | same as `volume.initial.mode` or `711` | Mode of the volume in the instance +`initial.uid` | int | custom volume with content type `filesystem` | same as `volume.initial.gid` or `0` | UID of the volume owner in the instance `security.shared` | bool | custom block volume | same as `volume.security.shared` or `false` | Enable sharing the volume across multiple instances `security.shifted` | bool | custom volume | same as `volume.security.shifted` or `false` | {{enable_ID_shifting}} `security.unmapped` | bool | custom volume | same as `volume.security.unmapped` or `false` | Disable ID mapping for the volume diff --git a/doc/reference/storage_cephfs.md b/doc/reference/storage_cephfs.md index 87960d11e7a..39542326418 100644 --- a/doc/reference/storage_cephfs.md +++ b/doc/reference/storage_cephfs.md @@ -77,6 +77,9 @@ Key | Type | Default Key | Type | Condition | Default | Description :-- | :--- | :-------- | :------ | :---------- +`initial.gid` | int | custom volume with content type `filesystem` | same as `volume.initial.uid` or `0` | GID of the volume owner in the instance +`initial.mode` | int | custom volume with content type `filesystem` | same as `volume.initial.mode` or `711` | Mode of the volume in the instance +`initial.uid` | int | custom volume with content type `filesystem` | same as `volume.initial.gid` or `0` | UID of the volume owner in the instance `security.shared` | bool | custom block volume | same as `volume.security.shared` or `false` | Enable sharing the volume across multiple instances `security.shifted` | bool | custom volume | same as `volume.security.shifted` or `false` | {{enable_ID_shifting}} `security.unmapped` | bool | custom volume | same as `volume.security.unmapped` or `false` | Disable ID mapping for the volume diff --git a/doc/reference/storage_dir.md b/doc/reference/storage_dir.md index f4b79c54035..8714ebb9be0 100644 --- a/doc/reference/storage_dir.md +++ b/doc/reference/storage_dir.md @@ -37,6 +37,9 @@ Key | Type | Default Key | Type | Condition | Default | Description :-- | :--- | :-------- | :------ | :---------- +`initial.gid` | int | custom volume with content type `filesystem` | same as `volume.initial.uid` or `0` | GID of the volume owner in the instance +`initial.mode` | int | custom volume with content type `filesystem` | same as `volume.initial.mode` or `711` | Mode of the volume in the instance +`initial.uid` | int | custom volume with content type `filesystem` | same as `volume.initial.gid` or `0` | UID of the volume owner in the instance `security.shared` | bool | custom block volume | same as `volume.security.shared` or `false` | Enable sharing the volume across multiple instances `security.shifted` | bool | custom volume | same as `volume.security.shifted` or `false` | {{enable_ID_shifting}} `security.unmapped` | bool | custom volume | same as `volume.security.unmapped` or `false` | Disable ID mapping for the volume diff --git a/doc/reference/storage_lvm.md b/doc/reference/storage_lvm.md index 3a2e2a27bad..19b6b1d7548 100644 --- a/doc/reference/storage_lvm.md +++ b/doc/reference/storage_lvm.md @@ -85,6 +85,9 @@ Key | Type | Condition :-- | :--- | :------ | :------ | :---------- `block.filesystem` | string | block-based volume with content type `filesystem` | same as `volume.block.filesystem` | {{block_filesystem}} `block.mount_options` | string | block-based volume with content type `filesystem` | same as `volume.block.mount_options` | Mount options for block-backed file system volumes +`initial.gid` | int | custom volume with content type `filesystem` | same as `volume.initial.uid` or `0` | GID of the volume owner in the instance +`initial.mode` | int | custom volume with content type `filesystem` | same as `volume.initial.mode` or `711` | Mode of the volume in the instance +`initial.uid` | int | custom volume with content type `filesystem` | same as `volume.initial.gid` or `0` | UID of the volume owner in the instance `lvm.stripes` | string | | same as `volume.lvm.stripes` | Number of stripes to use for new volumes (or thin pool volume) `lvm.stripes.size` | string | | same as `volume.lvm.stripes.size` | Size of stripes to use (at least 4096 bytes and multiple of 512 bytes) `security.shifted` | bool | custom volume | same as `volume.security.shifted` or `false` | {{enable_ID_shifting}} diff --git a/doc/reference/storage_zfs.md b/doc/reference/storage_zfs.md index a6d986e5ce0..cc9a01e9c73 100644 --- a/doc/reference/storage_zfs.md +++ b/doc/reference/storage_zfs.md @@ -109,6 +109,9 @@ Key | Type | Condition | Default :-- | :--- | :-------- | :------ | :---------- `block.filesystem` | string | block-based volume with content type `filesystem` (`zfs.block_mode` enabled) | same as `volume.block.filesystem` | {{block_filesystem}} `block.mount_options` | string | block-based volume with content type `filesystem` (`zfs.block_mode` enabled) | same as `volume.block.mount_options` | Mount options for block-backed file system volumes +`initial.gid` | int | custom volume with content type `filesystem` | same as `volume.initial.uid` or `0` | GID of the volume owner in the instance +`initial.mode` | int | custom volume with content type `filesystem` | same as `volume.initial.mode` or `711` | Mode of the volume in the instance +`initial.uid` | int | custom volume with content type `filesystem` | same as `volume.initial.gid` or `0` | UID of the volume owner in the instance `security.shared` | bool | custom block volume | same as `volume.security.shared` or `false` | Enable sharing the volume across multiple instances `security.shifted` | bool | custom volume | same as `volume.security.shifted` or `false` | {{enable_ID_shifting}} `security.unmapped` | bool | custom volume | same as `volume.security.unmapped` or `false` | Disable ID mapping for the volume diff --git a/internal/server/storage/drivers/volume.go b/internal/server/storage/drivers/volume.go index de3a7a2a2dc..56f9b864c9c 100644 --- a/internal/server/storage/drivers/volume.go +++ b/internal/server/storage/drivers/volume.go @@ -6,6 +6,7 @@ import ( "fmt" "os" "slices" + "strconv" "strings" internalInstance "github.com/lxc/incus/v6/internal/instance" @@ -233,8 +234,46 @@ func (v Volume) EnsureMountPath() error { revert.Add(func() { _ = os.Remove(volPath) }) } - // Set very restrictive mode 0100 for non-custom, non-bucket and non-image volumes. mode := os.FileMode(0711) + if v.volType == VolumeTypeCustom && v.contentType == ContentTypeFS { + initialMode := v.ExpandedConfig("initial.mode") + if initialMode != "" { + m, err := strconv.ParseInt(initialMode, 8, 0) + if err != nil { + return err + } + + mode = os.FileMode(m) + } + + uid, gid := 0, 0 + var err error + initialUID := v.ExpandedConfig("initial.uid") + if initialUID != "" { + uid, err = strconv.Atoi(initialUID) + if err != nil { + return err + } + } + + initialGID := v.ExpandedConfig("initial.gid") + if initialGID != "" { + gid, err = strconv.Atoi(initialGID) + if err != nil { + return err + } + } + + // Set the owner of a custom volume if uid or gid have been set. + if uid != 0 || gid != 0 { + err = os.Chown(volPath, uid, gid) + if err != nil { + return err + } + } + } + + // Set very restrictive mode 0100 for non-custom, non-bucket and non-image volumes. if v.volType != VolumeTypeCustom && v.volType != VolumeTypeImage && v.volType != VolumeTypeBucket { mode = os.FileMode(0100) } diff --git a/internal/server/storage/utils.go b/internal/server/storage/utils.go index 3cfd54e12fa..106369e4f4e 100644 --- a/internal/server/storage/utils.go +++ b/internal/server/storage/utils.go @@ -491,10 +491,13 @@ func poolAndVolumeCommonRules(vol *drivers.Volume) map[string]func(string) error "snapshots.pattern": validate.IsAny, } - // security.shifted and security.unmapped are only relevant for custom filesystem volumes. + // Options relevant for custom filesystem volumes. if (vol == nil) || (vol != nil && vol.Type() == drivers.VolumeTypeCustom && vol.ContentType() == drivers.ContentTypeFS) { rules["security.shifted"] = validate.Optional(validate.IsBool) rules["security.unmapped"] = validate.Optional(validate.IsBool) + rules["initial.uid"] = validate.Optional(validate.IsInt64) + rules["initial.gid"] = validate.Optional(validate.IsInt64) + rules["initial.mode"] = validate.Optional(validate.IsInt64) } // security.shared is only relevant for custom block volumes. diff --git a/internal/version/api.go b/internal/version/api.go index 6e0990de633..3903f5ca3ed 100644 --- a/internal/version/api.go +++ b/internal/version/api.go @@ -450,6 +450,7 @@ var APIExtensions = []string{ "instances_scriptlet_get_instances_count", "cluster_rebalance", "custom_volume_refresh_exclude_older_snapshots", + "storage_initial_owner", } // APIExtensionsCount returns the number of available API extensions. diff --git a/test/suites/storage_volume_initial_config.sh b/test/suites/storage_volume_initial_config.sh index 0269c2935cc..5132369b40f 100644 --- a/test/suites/storage_volume_initial_config.sh +++ b/test/suites/storage_volume_initial_config.sh @@ -138,6 +138,28 @@ test_storage_volume_initial_config() { incus delete c --force fi + # Test initial owner of a custom volume configuration options. + incus storage volume create "${pool}" testvolume1 + incus storage volume create "${pool}" testvolume2 initial.uid=101 initial.gid=101 initial.mode=0700 + + incus launch testimage c + + incus storage volume attach "${pool}" testvolume1 c /testvolume1 + incus storage volume attach "${pool}" testvolume2 c /testvolume2 + + [ "$(incus exec c -- stat -c %u:%g /testvolume1 )" = "0:0" ] + [ "$(incus exec c -- stat -c %a /testvolume1 )" = "711" ] + [ "$(incus exec c -- stat -c %u:%g /testvolume2 )" = "101:101" ] + [ "$(incus exec c -- stat -c %a /testvolume2 )" = "700" ] + + incus storage volume detach "${pool}" testvolume1 c + incus storage volume detach "${pool}" testvolume2 c + + incus delete c --force + + incus storage volume delete "${pool}" testvolume1 + incus storage volume delete "${pool}" testvolume2 + # Cleanup incus profile delete "${profile}"