Repository: docker/compose (go)

Issue:
Unsupported volume bind option SELinux label :z and :Z in v2
<!--
If you are reporting a new issue, make sure that we do not have any duplicates
already open. You can ensure this by searching the issue list for this
repository. If there is a duplicate, please close your issue and add a comment
to the existing issue instead.

If you suspect your issue is a bug, please edit your issue description to
include the BUG REPORT INFORMATION shown below. If you fail to provide this
information within 7 days, we cannot debug your issue and will close it. We
will, however, reopen it if you later provide the information.

For more information about reporting issues, see
https://github.com/docker/compose-cli/blob/master/CONTRIBUTING.md#reporting-other-issues

---------------------------------------------------
GENERAL SUPPORT INFORMATION
---------------------------------------------------

The GitHub issue tracker is for bug reports and feature requests.
General support can be found at the following locations:

- Docker Support Forums - https://forums.docker.com
- Docker Community Slack - https://dockr.ly/slack
- Post a question on StackOverflow, using the Docker tag

---------------------------------------------------
BUG REPORT INFORMATION
---------------------------------------------------
Use the commands below to provide key information from your environment:
You do NOT have to include this information if this is a FEATURE REQUEST
-->

**Description**

Unsupported volume bind option SELinux label `:z` and `:Z` in v2. It is a regression compared to v1 written in Python.
The v2 uses the [compose-spec/compose-go](https://github.com/compose-spec/compose-go) to parse Compose
YAML specification files but there is missing support for volume bind option SELinux label `:z` and `:Z` in parser.

**Steps to reproduce the issue:**

It requires Linux distribution with enabled SELinux (like Fedora).

1. Download `docker-compose` binary or compile it from source for version v2.0.0 to v2.2.2 (latest)
2. Create a new local directory `dir`:

```plaintext
mkdir -p dir
```

3. Set the SELinux label `user_home_t` for the local directory `dir`:

```plaintext
sudo chcon -R --type user_home_t ./dir
```

4. Create very simple `compose.yml` YAML file:

```yaml
---
version: '3.8'

services:
    test:
        image: alpine
        volumes:
            - ./dir:/dir:ro,z
        command: [ls, -al, /dir]
...
```

5. Start service:

```plaintext
docker compose up
```

**Describe the results you received:**

```plaintext
compose-test-1  | ls: can't open '/dir': Permission denied
compose-test-1  | total 0
compose-test-1 exited with code 1
```

**Describe the results you expected:**

```plaintext
compose-test-1  | total 0
compose-test-1  | drwxrwxr-x    1 1000     1000             0 Jan  1 13:26 .
compose-test-1  | drwxr-xr-x    1 root     root           140 Jan  1 13:29 ..
compose-test-1 exited with code 0
```

**Additional information you deem important (e.g. issue happens only occasionally):**

It happens always and it is a regression compared to v1. Working workarounds:
- `sudo chcon -R --type container_file_t ./dir`
- `privileged: true`
- `security_opt: [label=disable]`
- disable SELinux

**Output of `docker compose version`:**

```plaintext
Docker Compose version v2.2.2
```

**Output of `docker info`:**

```plaintext
Client:
 Context:    default
 Debug Mode: false
 Plugins:
  compose: Docker Compose (Docker Inc., v2.2.2)

Server:
 Containers: 20
  Running: 0
  Paused: 0
  Stopped: 20
 Images: 753
 Server Version: 20.10.11
 Storage Driver: btrfs
  Build Version: Btrfs v5.14.2 
  Library Version: 102
 Logging Driver: journald
 Cgroup Driver: systemd
 Cgroup Version: 2
 Plugins:
  Volume: local
  Network: bridge host ipvlan macvlan null overlay
  Log: awslogs fluentd gcplogs gelf journald json-file local logentries splunk syslog
 Swarm: inactive
 Runtimes: io.containerd.runtime.v1.linux runc io.containerd.runc.v2
 Default Runtime: runc
 Init Binary: /usr/libexec/docker/docker-init
 containerd version: 
 runc version: c42bf99-dirty
 init version: 
 Security Options:
  seccomp
   Profile: default
  selinux
  cgroupns
 Kernel Version: 5.15.12-200.fc35.x86_64
 Operating System: Fedora Linux 35.20220101.0 (Kinoite)
 OSType: linux
 Architecture: x86_64
 CPUs: 32
 Total Memory: 125.8GiB
 Name: fedora
 ID: UY32:WCEB:7QP2:NC2Q:SA77:4A3M:5K6L:VITH:PUZB:6M6R:SIBV:J62A
 Docker Root Dir: /var/lib/docker
 Debug Mode: false
 Registry: https://index.docker.io/v1/
 Labels:
 Experimental: false
 Insecure Registries:
  127.0.0.0/8
 Live Restore Enabled: true
```

**Additional environment details:**

None

Pull request description:
Added volume bind option SELinux label :z :Z
**What I did**

Added unsupported volume bind option SELinux label `:z` and `:Z` in v2.

It is a regression compared to v1 written in Python.

The v2 uses the [compose-spec/compose-go](https://github.com/compose-spec/compose-go) to parse Compose YAML
specification files but there was missing support for volume bind option
SELinux label `:z` and `:Z` in parser. It is fixed in:

- https://github.com/compose-spec/compose-go/pull/213

References:

- https://docs.docker.com/storage/bind-mounts/#configure-the-selinux-label

**Related issue**
<!-- If this is a bug fix, make sure your description includes "fixes #xxxx", or "closes #xxxx" -->

It fixes #9072

**(not mandatory) A picture of a cute animal, if possible in relation with what you did**

![image](https://user-images.githubusercontent.com/8367378/147854299-78454b47-697c-4e32-b2c1-32988cab2c02.png)

Patch:
diff --git a/go.mod b/go.mod
index f9ee4e34d..0f44e17f1 100644
--- a/go.mod
+++ b/go.mod
@@ -6,7 +6,7 @@ require (
 	github.com/AlecAivazis/survey/v2 v2.3.2
 	github.com/buger/goterm v1.0.3
 	github.com/cnabio/cnab-to-oci v0.3.1-beta1
-	github.com/compose-spec/compose-go v1.0.8
+	github.com/compose-spec/compose-go v1.0.9-0.20220101154228-91ed80f52afe
 	github.com/containerd/console v1.0.3
 	github.com/containerd/containerd v1.5.8
 	github.com/distribution/distribution/v3 v3.0.0-20210316161203-a01c71e2477e
diff --git a/go.sum b/go.sum
index dbe3143bc..4267f4414 100644
--- a/go.sum
+++ b/go.sum
@@ -221,8 +221,8 @@ github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWH
 github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
 github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
 github.com/codahale/hdrhistogram v0.0.0-20160425231609-f8ad88b59a58/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
-github.com/compose-spec/compose-go v1.0.8 h1:fgT7mYYu5Sp37i2lUIAAvwJpkAHk6dP5ITHy/LlutUk=
-github.com/compose-spec/compose-go v1.0.8/go.mod h1:REnCbBugoIdHB7S1sfkN/aJ7AJpNApGNjNiVjA9L8x4=
+github.com/compose-spec/compose-go v1.0.9-0.20220101154228-91ed80f52afe h1:eBklPN+uwonUSLAXWHDsyIOY0V4dVgE0YZOcIfXt5sc=
+github.com/compose-spec/compose-go v1.0.9-0.20220101154228-91ed80f52afe/go.mod h1:oCDjpdV9PA3EGSXw278816A+t8B7U5c7/16iLdRyMpk=
 github.com/compose-spec/godotenv v1.1.1 h1:lp+WpAInnw06YN9sV/XLUOV/9z4C+6wjJdWlrdVac7o=
 github.com/compose-spec/godotenv v1.1.1/go.mod h1:zF/3BOa18Z24tts5qnO/E9YURQanJTBUf7nlcCTNsyc=
 github.com/containerd/aufs v0.0.0-20200908144142-dab0cbea06f4/go.mod h1:nukgQABAEopAHvB6j7cnP5zJ+/3aVcE7hCYqvIwAHyE=
@@ -828,7 +828,6 @@ github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0Qu
 github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
 github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
 github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
-github.com/mitchellh/mapstructure v1.4.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
 github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGgCcyj8cs=
 github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
 github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A=
diff --git a/pkg/compose/create.go b/pkg/compose/create.go
index 2deed06ff..1bd57cf0e 100644
--- a/pkg/compose/create.go
+++ b/pkg/compose/create.go
@@ -719,11 +719,7 @@ MOUNTS:
 		if m.Type == mount.TypeBind || m.Type == mount.TypeNamedPipe {
 			for _, v := range service.Volumes {
 				if v.Target == m.Target && v.Bind != nil && v.Bind.CreateHostPath {
-					mode := "rw"
-					if m.ReadOnly {
-						mode = "ro"
-					}
-					binds = append(binds, fmt.Sprintf("%s:%s:%s", m.Source, m.Target, mode))
+					binds = append(binds, fmt.Sprintf("%s:%s:%s", m.Source, m.Target, getBindMode(v.Bind, m.ReadOnly)))
 					continue MOUNTS
 				}
 			}
@@ -733,6 +729,23 @@ MOUNTS:
 	return volumeMounts, binds, mounts, nil
 }
 
+func getBindMode(bind *types.ServiceVolumeBind, readOnly bool) string {
+	mode := "rw"
+
+	if readOnly {
+		mode = "ro"
+	}
+
+	switch bind.SELinux {
+	case types.SELinuxShared:
+		mode += ",z"
+	case types.SELinuxPrivate:
+		mode += ",Z"
+	}
+
+	return mode
+}
+
 func buildContainerMountOptions(p types.Project, s types.ServiceConfig, img moby.ImageInspect, inherit *moby.Container) ([]mount.Mount, error) {
 	var mounts = map[string]mount.Mount{}
 	if inherit != nil {


Test patch:
diff --git a/pkg/compose/create_test.go b/pkg/compose/create_test.go
index 02736c0bf..254e0b004 100644
--- a/pkg/compose/create_test.go
+++ b/pkg/compose/create_test.go
@@ -142,3 +142,12 @@ func TestBuildContainerMountOptions(t *testing.T) {
 	assert.Equal(t, mounts[0].Target, "/var/myvolume1")
 	assert.Equal(t, mounts[1].Target, "/var/myvolume2")
 }
+
+func TestGetBindMode(t *testing.T) {
+	assert.Equal(t, getBindMode(&composetypes.ServiceVolumeBind{}, false), "rw")
+	assert.Equal(t, getBindMode(&composetypes.ServiceVolumeBind{}, true), "ro")
+	assert.Equal(t, getBindMode(&composetypes.ServiceVolumeBind{SELinux: composetypes.SELinuxShared}, false), "rw,z")
+	assert.Equal(t, getBindMode(&composetypes.ServiceVolumeBind{SELinux: composetypes.SELinuxPrivate}, false), "rw,Z")
+	assert.Equal(t, getBindMode(&composetypes.ServiceVolumeBind{SELinux: composetypes.SELinuxShared}, true), "ro,z")
+	assert.Equal(t, getBindMode(&composetypes.ServiceVolumeBind{SELinux: composetypes.SELinuxPrivate}, true), "ro,Z")
+}