Skip to content

Commit 7920d48

Browse files
Oleg AnastasyevKostyaSha
authored andcommitted
SELinux context support to volumes
1 parent 0ad23b4 commit 7920d48

File tree

3 files changed

+153
-20
lines changed

3 files changed

+153
-20
lines changed

src/main/java/com/github/dockerjava/api/model/Bind.java

Lines changed: 57 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
import org.apache.commons.lang.builder.HashCodeBuilder;
55

66
/**
7-
* Represents a host path being bind mounted as a {@link Volume} in a Docker container. The Bind can be in read only or read write access
8-
* mode.
7+
* Represents a host path being bind mounted as a {@link Volume} in a Docker container.
8+
* The Bind can be in read only or read write access mode.
99
*/
1010
public class Bind {
1111

@@ -15,14 +15,24 @@ public class Bind {
1515

1616
private AccessMode accessMode;
1717

18+
/**
19+
* @since {@link com.github.dockerjava.core.RemoteApiVersion#VERSION_1_17}
20+
*/
21+
private SELContext secMode;
22+
1823
public Bind(String path, Volume volume) {
19-
this(path, volume, AccessMode.DEFAULT);
24+
this(path, volume, AccessMode.DEFAULT, SELContext.DEFAULT);
2025
}
2126

2227
public Bind(String path, Volume volume, AccessMode accessMode) {
28+
this(path, volume, accessMode, SELContext.DEFAULT);
29+
}
30+
31+
public Bind(String path, Volume volume, AccessMode accessMode, SELContext secMode) {
2332
this.path = path;
2433
this.volume = volume;
2534
this.accessMode = accessMode;
35+
this.secMode = secMode;
2636
}
2737

2838
public String getPath() {
@@ -37,6 +47,10 @@ public AccessMode getAccessMode() {
3747
return accessMode;
3848
}
3949

50+
public SELContext getSecMode() {
51+
return secMode;
52+
}
53+
4054
/**
4155
* Parses a bind mount specification to a {@link Bind}.
4256
*
@@ -50,47 +64,70 @@ public static Bind parse(String serialized) {
5064
try {
5165
String[] parts = serialized.split(":");
5266
switch (parts.length) {
53-
case 2: {
54-
return new Bind(parts[0], new Volume(parts[1]));
55-
}
56-
case 3: {
57-
AccessMode accessMode = AccessMode.valueOf(parts[2].toLowerCase());
58-
return new Bind(parts[0], new Volume(parts[1]), accessMode);
59-
}
60-
default: {
61-
throw new IllegalArgumentException();
67+
case 2: {
68+
return new Bind(parts[0], new Volume(parts[1]));
69+
}
70+
case 3: {
71+
String[] flags = parts[2].split(",");
72+
AccessMode accessMode = AccessMode.DEFAULT;
73+
SELContext seMode = SELContext.DEFAULT;
74+
for (String p : flags) {
75+
if (p.length() == 2) {
76+
accessMode = AccessMode.valueOf(p.toLowerCase());
77+
} else {
78+
seMode = SELContext.fromString(p);
79+
}
6280
}
81+
82+
return new Bind(parts[0], new Volume(parts[1]), accessMode, seMode);
83+
}
84+
default: {
85+
throw new IllegalArgumentException();
86+
}
6387
}
6488
} catch (Exception e) {
65-
throw new IllegalArgumentException("Error parsing Bind '" + serialized + "'");
89+
throw new IllegalArgumentException("Error parsing Bind '" + serialized + "'", e);
6690
}
6791
}
6892

6993
@Override
7094
public boolean equals(Object obj) {
7195
if (obj instanceof Bind) {
7296
Bind other = (Bind) obj;
73-
return new EqualsBuilder().append(path, other.getPath()).append(volume, other.getVolume())
74-
.append(accessMode, other.getAccessMode()).isEquals();
97+
return new EqualsBuilder()
98+
.append(path, other.getPath())
99+
.append(volume, other.getVolume())
100+
.append(accessMode, other.getAccessMode())
101+
.append(secMode, other.getSecMode())
102+
.isEquals();
75103
} else {
76104
return super.equals(obj);
77105
}
78106
}
79107

80108
@Override
81109
public int hashCode() {
82-
return new HashCodeBuilder().append(path).append(volume).append(accessMode).toHashCode();
110+
return new HashCodeBuilder()
111+
.append(path)
112+
.append(volume)
113+
.append(accessMode)
114+
.append(secMode)
115+
.toHashCode();
83116
}
84117

85118
/**
86-
* Returns a string representation of this {@link Bind} suitable for inclusion in a JSON message. The format is
87-
* <code>&lt;host path&gt;:&lt;container path&gt;:&lt;access mode&gt;</code>, like the argument in {@link #parse(String)}.
119+
* Returns a string representation of this {@link Bind} suitable for inclusion in a JSON message.
120+
* The format is <code>&lt;host path&gt;:&lt;container path&gt;:&lt;access mode&gt;</code>,
121+
* like the argument in {@link #parse(String)}.
88122
*
89123
* @return a string representation of this {@link Bind}
90124
*/
91125
@Override
92126
public String toString() {
93-
return path + ":" + volume.getPath() + ":" + accessMode.toString();
127+
return String.format("%s:%s:%s%s",
128+
path,
129+
volume.getPath(),
130+
accessMode.toString(),
131+
secMode != SELContext.none ? "," + secMode.toString() : "");
94132
}
95-
96133
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package com.github.dockerjava.api.model;
2+
3+
/**
4+
* The context mode of bound volume in SELinux.
5+
* Host path <code>shared</code> - can be used by all containers, <code>private</code> - by only this container
6+
*
7+
* @see http://www.projectatomic.io/blog/2015/06/using-volumes-with-docker-can-cause-problems-with-selinux/
8+
* @since 1.17
9+
*/
10+
public enum SELContext {
11+
/** no selinux */
12+
none(""),
13+
14+
/** z option */
15+
shared("z"),
16+
17+
/** Z option */
18+
single("Z");
19+
20+
/**
21+
* The default {@link SELContext}: {@link #none}
22+
*/
23+
public static final SELContext DEFAULT = none;
24+
25+
private String value;
26+
27+
SELContext(String v) {
28+
this.value = v;
29+
}
30+
31+
@Override
32+
public String toString() {
33+
return value;
34+
}
35+
36+
public static SELContext fromString(String p) {
37+
switch (p) {
38+
case "z":
39+
return shared;
40+
case "Z":
41+
return single;
42+
}
43+
return none;
44+
}
45+
}

src/test/java/com/github/dockerjava/api/model/BindTest.java

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ public void parseUsingDefaultAccessMode() {
1414
assertEquals(bind.getPath(), "/host");
1515
assertEquals(bind.getVolume().getPath(), "/container");
1616
assertEquals(bind.getAccessMode(), AccessMode.DEFAULT);
17+
assertEquals(bind.getSecMode(), SELContext.none);
1718
}
1819

1920
@Test
@@ -22,6 +23,7 @@ public void parseReadWrite() {
2223
assertEquals(bind.getPath(), "/host");
2324
assertEquals(bind.getVolume().getPath(), "/container");
2425
assertEquals(bind.getAccessMode(), rw);
26+
assertEquals(bind.getSecMode(), SELContext.none);
2527
}
2628

2729
@Test
@@ -30,6 +32,40 @@ public void parseReadOnly() {
3032
assertEquals(bind.getPath(), "/host");
3133
assertEquals(bind.getVolume().getPath(), "/container");
3234
assertEquals(bind.getAccessMode(), ro);
35+
assertEquals(bind.getSecMode(), SELContext.none);
36+
}
37+
38+
@Test
39+
public void parseSELOnly() {
40+
Bind bind = Bind.parse("/host:/container:Z");
41+
assertEquals(bind.getPath(), "/host");
42+
assertEquals(bind.getVolume().getPath(), "/container");
43+
assertEquals(bind.getAccessMode(), AccessMode.DEFAULT);
44+
assertEquals(bind.getSecMode(), SELContext.single);
45+
46+
bind = Bind.parse("/host:/container:z");
47+
assertEquals(bind.getPath(), "/host");
48+
assertEquals(bind.getVolume().getPath(), "/container");
49+
assertEquals(bind.getAccessMode(), AccessMode.DEFAULT);
50+
assertEquals(bind.getSecMode(), SELContext.shared);
51+
}
52+
53+
@Test
54+
public void parseReadWriteSEL() {
55+
Bind bind = Bind.parse("/host:/container:rw,Z");
56+
assertEquals(bind.getPath(), "/host");
57+
assertEquals(bind.getVolume().getPath(), "/container");
58+
assertEquals(bind.getAccessMode(), rw);
59+
assertEquals(bind.getSecMode(), SELContext.single);
60+
}
61+
62+
@Test
63+
public void parseReadOnlySEL() {
64+
Bind bind = Bind.parse("/host:/container:ro,z");
65+
assertEquals(bind.getPath(), "/host");
66+
assertEquals(bind.getVolume().getPath(), "/container");
67+
assertEquals(bind.getAccessMode(), ro);
68+
assertEquals(bind.getSecMode(), SELContext.shared);
3369
}
3470

3571
@Test(expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = "Error parsing Bind.*")
@@ -62,4 +98,19 @@ public void toStringDefaultAccessMode() {
6298
assertEquals(Bind.parse("/host:/container").toString(), "/host:/container:rw");
6399
}
64100

101+
@Test
102+
public void toStringReadOnlySEL() {
103+
assertEquals(Bind.parse("/host:/container:ro,Z").toString(), "/host:/container:ro,Z");
104+
}
105+
106+
@Test
107+
public void toStringReadWriteSEL() {
108+
assertEquals(Bind.parse("/host:/container:rw,z").toString(), "/host:/container:rw,z");
109+
}
110+
111+
@Test
112+
public void toStringDefaultSEL() {
113+
assertEquals(Bind.parse("/host:/container:Z").toString(), "/host:/container:rw,Z");
114+
}
115+
65116
}

0 commit comments

Comments
 (0)