Disable SELinux label security policy when building images
When using the Gradle `bootBuildImage` task or Maven `spring-boot:build-image` goal on a Linux distribution with SELinux enabled, binding the Docker socket in the builder container might fail. This commit disables the `label` security policy in the builder container to prevent this type of failure. Fixes gh-32000
This commit is contained in:
parent
238493a7d4
commit
c16094e04b
|
@ -20,6 +20,8 @@ import java.io.Closeable;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import com.sun.jna.Platform;
|
||||||
|
|
||||||
import org.springframework.boot.buildpack.platform.docker.DockerApi;
|
import org.springframework.boot.buildpack.platform.docker.DockerApi;
|
||||||
import org.springframework.boot.buildpack.platform.docker.LogUpdateEvent;
|
import org.springframework.boot.buildpack.platform.docker.LogUpdateEvent;
|
||||||
import org.springframework.boot.buildpack.platform.docker.configuration.ResolvedDockerHost;
|
import org.springframework.boot.buildpack.platform.docker.configuration.ResolvedDockerHost;
|
||||||
|
@ -201,6 +203,9 @@ class Lifecycle implements Closeable {
|
||||||
else {
|
else {
|
||||||
phase.withBinding(Binding.from(DOMAIN_SOCKET_PATH, DOMAIN_SOCKET_PATH));
|
phase.withBinding(Binding.from(DOMAIN_SOCKET_PATH, DOMAIN_SOCKET_PATH));
|
||||||
}
|
}
|
||||||
|
if (!Platform.isWindows()) {
|
||||||
|
phase.withSecurityOption("label=disable");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isVerboseLogging() {
|
private boolean isVerboseLogging() {
|
||||||
|
|
|
@ -47,6 +47,8 @@ class Phase {
|
||||||
|
|
||||||
private final Map<String, String> env = new LinkedHashMap<>();
|
private final Map<String, String> env = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
private final List<String> securityOptions = new ArrayList<>();
|
||||||
|
|
||||||
private String networkMode;
|
private String networkMode;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -110,6 +112,14 @@ class Phase {
|
||||||
this.networkMode = networkMode;
|
this.networkMode = networkMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update this phase with a security option.
|
||||||
|
* @param option the security option
|
||||||
|
*/
|
||||||
|
void withSecurityOption(String option) {
|
||||||
|
this.securityOptions.add(option);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the name of the phase.
|
* Return the name of the phase.
|
||||||
* @return the phase name
|
* @return the phase name
|
||||||
|
@ -138,6 +148,7 @@ class Phase {
|
||||||
if (this.networkMode != null) {
|
if (this.networkMode != null) {
|
||||||
update.withNetworkMode(this.networkMode);
|
update.withNetworkMode(this.networkMode);
|
||||||
}
|
}
|
||||||
|
this.securityOptions.forEach(update::withSecurityOption);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,7 +48,8 @@ public class ContainerConfig {
|
||||||
private final String json;
|
private final String json;
|
||||||
|
|
||||||
ContainerConfig(String user, ImageReference image, String command, List<String> args, Map<String, String> labels,
|
ContainerConfig(String user, ImageReference image, String command, List<String> args, Map<String, String> labels,
|
||||||
List<Binding> bindings, Map<String, String> env, String networkMode) throws IOException {
|
List<Binding> bindings, Map<String, String> env, String networkMode, List<String> securityOptions)
|
||||||
|
throws IOException {
|
||||||
Assert.notNull(image, "Image must not be null");
|
Assert.notNull(image, "Image must not be null");
|
||||||
Assert.hasText(command, "Command must not be empty");
|
Assert.hasText(command, "Command must not be empty");
|
||||||
ObjectMapper objectMapper = SharedObjectMapper.get();
|
ObjectMapper objectMapper = SharedObjectMapper.get();
|
||||||
|
@ -70,6 +71,10 @@ public class ContainerConfig {
|
||||||
}
|
}
|
||||||
ArrayNode bindsNode = hostConfigNode.putArray("Binds");
|
ArrayNode bindsNode = hostConfigNode.putArray("Binds");
|
||||||
bindings.forEach((binding) -> bindsNode.add(binding.toString()));
|
bindings.forEach((binding) -> bindsNode.add(binding.toString()));
|
||||||
|
if (securityOptions != null && !securityOptions.isEmpty()) {
|
||||||
|
ArrayNode securityOptsNode = hostConfigNode.putArray("SecurityOpt");
|
||||||
|
securityOptions.forEach(securityOptsNode::add);
|
||||||
|
}
|
||||||
this.json = objectMapper.writeValueAsString(node);
|
this.json = objectMapper.writeValueAsString(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -120,6 +125,8 @@ public class ContainerConfig {
|
||||||
|
|
||||||
private String networkMode;
|
private String networkMode;
|
||||||
|
|
||||||
|
private final List<String> securityOptions = new ArrayList<>();
|
||||||
|
|
||||||
Update(ImageReference image) {
|
Update(ImageReference image) {
|
||||||
this.image = image;
|
this.image = image;
|
||||||
}
|
}
|
||||||
|
@ -128,7 +135,7 @@ public class ContainerConfig {
|
||||||
update.accept(this);
|
update.accept(this);
|
||||||
try {
|
try {
|
||||||
return new ContainerConfig(this.user, this.image, this.command, this.args, this.labels, this.bindings,
|
return new ContainerConfig(this.user, this.image, this.command, this.args, this.labels, this.bindings,
|
||||||
this.env, this.networkMode);
|
this.env, this.networkMode, this.securityOptions);
|
||||||
}
|
}
|
||||||
catch (IOException ex) {
|
catch (IOException ex) {
|
||||||
throw new IllegalStateException(ex);
|
throw new IllegalStateException(ex);
|
||||||
|
@ -197,6 +204,14 @@ public class ContainerConfig {
|
||||||
this.networkMode = networkMode;
|
this.networkMode = networkMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the container config with a security option.
|
||||||
|
* @param option the security option
|
||||||
|
*/
|
||||||
|
public void withSecurityOption(String option) {
|
||||||
|
this.securityOptions.add(option);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -143,4 +143,18 @@ class PhaseTests {
|
||||||
then(update).shouldHaveNoMoreInteractions();
|
then(update).shouldHaveNoMoreInteractions();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void applyWhenWithSecurityOptionsUpdatesConfigurationWithSecurityOptions() {
|
||||||
|
Phase phase = new Phase("test", true);
|
||||||
|
phase.withSecurityOption("option1=value1");
|
||||||
|
phase.withSecurityOption("option2=value2");
|
||||||
|
Update update = mock(Update.class);
|
||||||
|
phase.apply(update);
|
||||||
|
then(update).should().withCommand("/cnb/lifecycle/test");
|
||||||
|
then(update).should().withLabel("author", "spring-boot");
|
||||||
|
then(update).should().withSecurityOption("option1=value1");
|
||||||
|
then(update).should().withSecurityOption("option2=value2");
|
||||||
|
then(update).shouldHaveNoMoreInteractions();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,12 +61,13 @@ class ContainerConfigTests extends AbstractJsonTests {
|
||||||
update.withEnv("name1", "value1");
|
update.withEnv("name1", "value1");
|
||||||
update.withEnv("name2", "value2");
|
update.withEnv("name2", "value2");
|
||||||
update.withNetworkMode("test");
|
update.withNetworkMode("test");
|
||||||
|
update.withSecurityOption("option=value");
|
||||||
});
|
});
|
||||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||||
containerConfig.writeTo(outputStream);
|
containerConfig.writeTo(outputStream);
|
||||||
String actualJson = new String(outputStream.toByteArray(), StandardCharsets.UTF_8);
|
String actualJson = new String(outputStream.toByteArray(), StandardCharsets.UTF_8);
|
||||||
String expectedJson = StreamUtils.copyToString(getContent("container-config.json"), StandardCharsets.UTF_8);
|
String expectedJson = StreamUtils.copyToString(getContent("container-config.json"), StandardCharsets.UTF_8);
|
||||||
JSONAssert.assertEquals(expectedJson, actualJson, false);
|
JSONAssert.assertEquals(expectedJson, actualJson, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,6 +33,9 @@
|
||||||
"pack-cache-b35197ac41ea.launch:/launch-cache",
|
"pack-cache-b35197ac41ea.launch:/launch-cache",
|
||||||
"/host/src/path:/container/dest/path:ro",
|
"/host/src/path:/container/dest/path:ro",
|
||||||
"volume-name:/container/volume/path:rw"
|
"volume-name:/container/volume/path:rw"
|
||||||
|
],
|
||||||
|
"SecurityOpt" : [
|
||||||
|
"label=disable"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -31,6 +31,9 @@
|
||||||
"pack-app-aaaaaaaaaa:/workspace",
|
"pack-app-aaaaaaaaaa:/workspace",
|
||||||
"build-volume:/cache",
|
"build-volume:/cache",
|
||||||
"launch-volume:/launch-cache"
|
"launch-volume:/launch-cache"
|
||||||
|
],
|
||||||
|
"SecurityOpt" : [
|
||||||
|
"label=disable"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -32,6 +32,9 @@
|
||||||
"pack-app-aaaaaaaaaa:/workspace",
|
"pack-app-aaaaaaaaaa:/workspace",
|
||||||
"pack-cache-b35197ac41ea.build:/cache",
|
"pack-cache-b35197ac41ea.build:/cache",
|
||||||
"pack-cache-b35197ac41ea.launch:/launch-cache"
|
"pack-cache-b35197ac41ea.launch:/launch-cache"
|
||||||
|
],
|
||||||
|
"SecurityOpt" : [
|
||||||
|
"label=disable"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -31,6 +31,9 @@
|
||||||
"pack-app-aaaaaaaaaa:/workspace",
|
"pack-app-aaaaaaaaaa:/workspace",
|
||||||
"pack-cache-b35197ac41ea.build:/cache",
|
"pack-cache-b35197ac41ea.build:/cache",
|
||||||
"pack-cache-b35197ac41ea.launch:/launch-cache"
|
"pack-cache-b35197ac41ea.launch:/launch-cache"
|
||||||
|
],
|
||||||
|
"SecurityOpt" : [
|
||||||
|
"label=disable"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -31,6 +31,9 @@
|
||||||
"pack-app-aaaaaaaaaa:/workspace",
|
"pack-app-aaaaaaaaaa:/workspace",
|
||||||
"pack-cache-b35197ac41ea.build:/cache",
|
"pack-cache-b35197ac41ea.build:/cache",
|
||||||
"pack-cache-b35197ac41ea.launch:/launch-cache"
|
"pack-cache-b35197ac41ea.launch:/launch-cache"
|
||||||
|
],
|
||||||
|
"SecurityOpt" : [
|
||||||
|
"label=disable"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -32,6 +32,9 @@
|
||||||
"pack-app-aaaaaaaaaa:/workspace",
|
"pack-app-aaaaaaaaaa:/workspace",
|
||||||
"pack-cache-b35197ac41ea.build:/cache",
|
"pack-cache-b35197ac41ea.build:/cache",
|
||||||
"pack-cache-b35197ac41ea.launch:/launch-cache"
|
"pack-cache-b35197ac41ea.launch:/launch-cache"
|
||||||
|
],
|
||||||
|
"SecurityOpt" : [
|
||||||
|
"label=disable"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -7,6 +7,9 @@
|
||||||
"author" : "spring-boot"
|
"author" : "spring-boot"
|
||||||
},
|
},
|
||||||
"HostConfig" : {
|
"HostConfig" : {
|
||||||
"Binds" : [ "/var/run/docker.sock:/var/run/docker.sock", "pack-layers-aaaaaaaaaa:/layers", "pack-app-aaaaaaaaaa:/workspace", "pack-cache-b35197ac41ea.build:/cache", "pack-cache-b35197ac41ea.launch:/launch-cache" ]
|
"Binds" : [ "/var/run/docker.sock:/var/run/docker.sock", "pack-layers-aaaaaaaaaa:/layers", "pack-app-aaaaaaaaaa:/workspace", "pack-cache-b35197ac41ea.build:/cache", "pack-cache-b35197ac41ea.launch:/launch-cache" ],
|
||||||
|
"SecurityOpt" : [
|
||||||
|
"label=disable"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -31,6 +31,9 @@
|
||||||
"pack-app-aaaaaaaaaa:/workspace",
|
"pack-app-aaaaaaaaaa:/workspace",
|
||||||
"pack-cache-b35197ac41ea.build:/cache",
|
"pack-cache-b35197ac41ea.build:/cache",
|
||||||
"pack-cache-b35197ac41ea.launch:/launch-cache"
|
"pack-cache-b35197ac41ea.launch:/launch-cache"
|
||||||
|
],
|
||||||
|
"SecurityOpt" : [
|
||||||
|
"label=disable"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -17,6 +17,9 @@
|
||||||
"Binds": [
|
"Binds": [
|
||||||
"bind-source:bind-dest"
|
"bind-source:bind-dest"
|
||||||
],
|
],
|
||||||
"NetworkMode": "test"
|
"NetworkMode": "test",
|
||||||
|
"SecurityOpt": [
|
||||||
|
"option=value"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue