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
pull/32861/head
Scott Frederick 2 years ago
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…
Cancel
Save