Commit 90ce4722 authored by Scott Frederick's avatar Scott Frederick

Support local socket path in DOCKER_HOST

Prior to this commit, if a DOCKER_HOST environment variable was present
when attempting to communicate with a Docker daemon, it was assumed
that the value of that variable was an address that could be used to
create an HTTP connection to a remote daemon. In some cases, the value
of the variable is the path to a local socket file, which would cause
the HTTP connection to fail.

This commit adds additional validation of the value of the DOCKER_HOST
environment variable to determine whether it is a remote address or
a local socket file and create the appropriate connection type.

Fixes gh-21173
parent 86e6ec04
...@@ -29,6 +29,7 @@ import org.springframework.boot.buildpack.platform.system.Environment; ...@@ -29,6 +29,7 @@ import org.springframework.boot.buildpack.platform.system.Environment;
* HTTP transport used for docker access. * HTTP transport used for docker access.
* *
* @author Phillip Webb * @author Phillip Webb
* @author Scott Frederick
* @since 2.3.0 * @since 2.3.0
*/ */
public interface HttpTransport { public interface HttpTransport {
...@@ -94,7 +95,7 @@ public interface HttpTransport { ...@@ -94,7 +95,7 @@ public interface HttpTransport {
*/ */
static HttpTransport create(Environment environment) { static HttpTransport create(Environment environment) {
HttpTransport remote = RemoteHttpClientTransport.createIfPossible(environment); HttpTransport remote = RemoteHttpClientTransport.createIfPossible(environment);
return (remote != null) ? remote : LocalHttpClientTransport.create(); return (remote != null) ? remote : LocalHttpClientTransport.create(environment);
} }
/** /**
......
...@@ -40,6 +40,7 @@ import org.apache.http.util.Args; ...@@ -40,6 +40,7 @@ import org.apache.http.util.Args;
import org.springframework.boot.buildpack.platform.socket.DomainSocket; import org.springframework.boot.buildpack.platform.socket.DomainSocket;
import org.springframework.boot.buildpack.platform.socket.NamedPipeSocket; import org.springframework.boot.buildpack.platform.socket.NamedPipeSocket;
import org.springframework.boot.buildpack.platform.system.Environment;
/** /**
* {@link HttpClientTransport} that talks to local Docker. * {@link HttpClientTransport} that talks to local Docker.
...@@ -49,15 +50,17 @@ import org.springframework.boot.buildpack.platform.socket.NamedPipeSocket; ...@@ -49,15 +50,17 @@ import org.springframework.boot.buildpack.platform.socket.NamedPipeSocket;
*/ */
final class LocalHttpClientTransport extends HttpClientTransport { final class LocalHttpClientTransport extends HttpClientTransport {
private static final String DOCKER_HOST = "DOCKER_HOST";
private static final HttpHost LOCAL_DOCKER_HOST = HttpHost.create("docker://localhost"); private static final HttpHost LOCAL_DOCKER_HOST = HttpHost.create("docker://localhost");
private LocalHttpClientTransport(CloseableHttpClient client) { private LocalHttpClientTransport(CloseableHttpClient client) {
super(client, LOCAL_DOCKER_HOST); super(client, LOCAL_DOCKER_HOST);
} }
static LocalHttpClientTransport create() { static LocalHttpClientTransport create(Environment environment) {
HttpClientBuilder builder = HttpClients.custom(); HttpClientBuilder builder = HttpClients.custom();
builder.setConnectionManager(new LocalConnectionManager()); builder.setConnectionManager(new LocalConnectionManager(environment.get(DOCKER_HOST)));
builder.setSchemePortResolver(new LocalSchemePortResolver()); builder.setSchemePortResolver(new LocalSchemePortResolver());
return new LocalHttpClientTransport(builder.build()); return new LocalHttpClientTransport(builder.build());
} }
...@@ -67,13 +70,13 @@ final class LocalHttpClientTransport extends HttpClientTransport { ...@@ -67,13 +70,13 @@ final class LocalHttpClientTransport extends HttpClientTransport {
*/ */
private static class LocalConnectionManager extends BasicHttpClientConnectionManager { private static class LocalConnectionManager extends BasicHttpClientConnectionManager {
LocalConnectionManager() { LocalConnectionManager(String host) {
super(getRegistry(), null, null, new LocalDnsResolver()); super(getRegistry(host), null, null, new LocalDnsResolver());
} }
private static Registry<ConnectionSocketFactory> getRegistry() { private static Registry<ConnectionSocketFactory> getRegistry(String host) {
RegistryBuilder<ConnectionSocketFactory> builder = RegistryBuilder.create(); RegistryBuilder<ConnectionSocketFactory> builder = RegistryBuilder.create();
builder.register("docker", new LocalConnectionSocketFactory()); builder.register("docker", new LocalConnectionSocketFactory(host));
return builder.build(); return builder.build();
} }
...@@ -103,12 +106,18 @@ final class LocalHttpClientTransport extends HttpClientTransport { ...@@ -103,12 +106,18 @@ final class LocalHttpClientTransport extends HttpClientTransport {
private static final String WINDOWS_NAMED_PIPE_PATH = "//./pipe/docker_engine"; private static final String WINDOWS_NAMED_PIPE_PATH = "//./pipe/docker_engine";
private final String host;
LocalConnectionSocketFactory(String host) {
this.host = host;
}
@Override @Override
public Socket createSocket(HttpContext context) throws IOException { public Socket createSocket(HttpContext context) throws IOException {
if (Platform.isWindows()) { if (Platform.isWindows()) {
return NamedPipeSocket.get(WINDOWS_NAMED_PIPE_PATH); return NamedPipeSocket.get((this.host != null) ? this.host : WINDOWS_NAMED_PIPE_PATH);
} }
return DomainSocket.get(DOMAIN_SOCKET_PATH); return DomainSocket.get((this.host != null) ? this.host : DOMAIN_SOCKET_PATH);
} }
@Override @Override
......
...@@ -16,6 +16,9 @@ ...@@ -16,6 +16,9 @@
package org.springframework.boot.buildpack.platform.docker.transport; package org.springframework.boot.buildpack.platform.docker.transport;
import java.nio.file.Files;
import java.nio.file.Paths;
import javax.net.ssl.SSLContext; import javax.net.ssl.SSLContext;
import org.apache.http.HttpHost; import org.apache.http.HttpHost;
...@@ -53,7 +56,10 @@ final class RemoteHttpClientTransport extends HttpClientTransport { ...@@ -53,7 +56,10 @@ final class RemoteHttpClientTransport extends HttpClientTransport {
static RemoteHttpClientTransport createIfPossible(Environment environment, SslContextFactory sslContextFactory) { static RemoteHttpClientTransport createIfPossible(Environment environment, SslContextFactory sslContextFactory) {
String host = environment.get(DOCKER_HOST); String host = environment.get(DOCKER_HOST);
return (host != null) ? create(environment, sslContextFactory, HttpHost.create(host)) : null; if (host == null || Files.exists(Paths.get(host))) {
return null;
}
return create(environment, sslContextFactory, HttpHost.create(host));
} }
private static RemoteHttpClientTransport create(Environment environment, SslContextFactory sslContextFactory, private static RemoteHttpClientTransport create(Environment environment, SslContextFactory sslContextFactory,
......
...@@ -16,10 +16,14 @@ ...@@ -16,10 +16,14 @@
package org.springframework.boot.buildpack.platform.docker.transport; package org.springframework.boot.buildpack.platform.docker.transport;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections; import java.util.Collections;
import java.util.Map; import java.util.Map;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
...@@ -27,16 +31,25 @@ import static org.assertj.core.api.Assertions.assertThat; ...@@ -27,16 +31,25 @@ import static org.assertj.core.api.Assertions.assertThat;
* Tests for {@link HttpTransport}. * Tests for {@link HttpTransport}.
* *
* @author Phillip Webb * @author Phillip Webb
* @author Scott Frederick
*/ */
class HttpTransportTests { class HttpTransportTests {
@Test @Test
void createWhenHasDockerHostVariableReturnsRemote() { void createWhenDockerHostVariableIsAddressReturnsRemote() {
Map<String, String> environment = Collections.singletonMap("DOCKER_HOST", "192.168.1.0"); Map<String, String> environment = Collections.singletonMap("DOCKER_HOST", "tcp://192.168.1.0");
HttpTransport transport = HttpTransport.create(environment::get); HttpTransport transport = HttpTransport.create(environment::get);
assertThat(transport).isInstanceOf(RemoteHttpClientTransport.class); assertThat(transport).isInstanceOf(RemoteHttpClientTransport.class);
} }
@Test
void createWhenDockerHostVariableIsFileReturnsLocal(@TempDir Path tempDir) throws IOException {
String dummySocketFilePath = Files.createTempFile(tempDir, "http-transport", null).toAbsolutePath().toString();
Map<String, String> environment = Collections.singletonMap("DOCKER_HOST", dummySocketFilePath);
HttpTransport transport = HttpTransport.create(environment::get);
assertThat(transport).isInstanceOf(LocalHttpClientTransport.class);
}
@Test @Test
void createWhenDoesNotHaveDockerHostVariableReturnsLocal() { void createWhenDoesNotHaveDockerHostVariableReturnsLocal() {
HttpTransport transport = HttpTransport.create((name) -> null); HttpTransport transport = HttpTransport.create((name) -> null);
......
...@@ -16,6 +16,9 @@ ...@@ -16,6 +16,9 @@
package org.springframework.boot.buildpack.platform.docker.transport; package org.springframework.boot.buildpack.platform.docker.transport;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import java.util.function.Consumer; import java.util.function.Consumer;
...@@ -24,6 +27,7 @@ import javax.net.ssl.SSLContext; ...@@ -24,6 +27,7 @@ import javax.net.ssl.SSLContext;
import org.apache.http.HttpHost; import org.apache.http.HttpHost;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.springframework.boot.buildpack.platform.docker.ssl.SslContextFactory; import org.springframework.boot.buildpack.platform.docker.ssl.SslContextFactory;
...@@ -40,7 +44,7 @@ import static org.mockito.Mockito.mock; ...@@ -40,7 +44,7 @@ import static org.mockito.Mockito.mock;
*/ */
class RemoteHttpClientTransportTests { class RemoteHttpClientTransportTests {
private Map<String, String> environment = new LinkedHashMap<>(); private final Map<String, String> environment = new LinkedHashMap<>();
@Test @Test
void createIfPossibleWhenDockerHostIsNotSetReturnsNull() { void createIfPossibleWhenDockerHostIsNotSetReturnsNull() {
...@@ -49,7 +53,16 @@ class RemoteHttpClientTransportTests { ...@@ -49,7 +53,16 @@ class RemoteHttpClientTransportTests {
} }
@Test @Test
void createIfPossibleWhenDockerHostIsSetReturnsTransport() { void createIfPossibleWhenDockerHostIsFileReturnsNull(@TempDir Path tempDir) throws IOException {
String dummySocketFilePath = Files.createTempFile(tempDir, "remote-transport", null).toAbsolutePath()
.toString();
this.environment.put("DOCKER_HOST", dummySocketFilePath);
RemoteHttpClientTransport transport = RemoteHttpClientTransport.createIfPossible(this.environment::get);
assertThat(transport).isNull();
}
@Test
void createIfPossibleWhenDockerHostIsAddressReturnsTransport() {
this.environment.put("DOCKER_HOST", "tcp://192.168.1.2:2376"); this.environment.put("DOCKER_HOST", "tcp://192.168.1.2:2376");
RemoteHttpClientTransport transport = RemoteHttpClientTransport.createIfPossible(this.environment::get); RemoteHttpClientTransport transport = RemoteHttpClientTransport.createIfPossible(this.environment::get);
assertThat(transport).isNotNull(); assertThat(transport).isNotNull();
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment