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
This commit is contained in:
Scott Frederick 2020-05-12 09:57:39 -05:00
parent 86e6ec04b2
commit 90ce472252
5 changed files with 56 additions and 14 deletions

View File

@ -29,6 +29,7 @@ import org.springframework.boot.buildpack.platform.system.Environment;
* HTTP transport used for docker access.
*
* @author Phillip Webb
* @author Scott Frederick
* @since 2.3.0
*/
public interface HttpTransport {
@ -94,7 +95,7 @@ public interface HttpTransport {
*/
static HttpTransport create(Environment environment) {
HttpTransport remote = RemoteHttpClientTransport.createIfPossible(environment);
return (remote != null) ? remote : LocalHttpClientTransport.create();
return (remote != null) ? remote : LocalHttpClientTransport.create(environment);
}
/**

View File

@ -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.NamedPipeSocket;
import org.springframework.boot.buildpack.platform.system.Environment;
/**
* {@link HttpClientTransport} that talks to local Docker.
@ -49,15 +50,17 @@ import org.springframework.boot.buildpack.platform.socket.NamedPipeSocket;
*/
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 LocalHttpClientTransport(CloseableHttpClient client) {
super(client, LOCAL_DOCKER_HOST);
}
static LocalHttpClientTransport create() {
static LocalHttpClientTransport create(Environment environment) {
HttpClientBuilder builder = HttpClients.custom();
builder.setConnectionManager(new LocalConnectionManager());
builder.setConnectionManager(new LocalConnectionManager(environment.get(DOCKER_HOST)));
builder.setSchemePortResolver(new LocalSchemePortResolver());
return new LocalHttpClientTransport(builder.build());
}
@ -67,13 +70,13 @@ final class LocalHttpClientTransport extends HttpClientTransport {
*/
private static class LocalConnectionManager extends BasicHttpClientConnectionManager {
LocalConnectionManager() {
super(getRegistry(), null, null, new LocalDnsResolver());
LocalConnectionManager(String host) {
super(getRegistry(host), null, null, new LocalDnsResolver());
}
private static Registry<ConnectionSocketFactory> getRegistry() {
private static Registry<ConnectionSocketFactory> getRegistry(String host) {
RegistryBuilder<ConnectionSocketFactory> builder = RegistryBuilder.create();
builder.register("docker", new LocalConnectionSocketFactory());
builder.register("docker", new LocalConnectionSocketFactory(host));
return builder.build();
}
@ -103,12 +106,18 @@ final class LocalHttpClientTransport extends HttpClientTransport {
private static final String WINDOWS_NAMED_PIPE_PATH = "//./pipe/docker_engine";
private final String host;
LocalConnectionSocketFactory(String host) {
this.host = host;
}
@Override
public Socket createSocket(HttpContext context) throws IOException {
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

View File

@ -16,6 +16,9 @@
package org.springframework.boot.buildpack.platform.docker.transport;
import java.nio.file.Files;
import java.nio.file.Paths;
import javax.net.ssl.SSLContext;
import org.apache.http.HttpHost;
@ -53,7 +56,10 @@ final class RemoteHttpClientTransport extends HttpClientTransport {
static RemoteHttpClientTransport createIfPossible(Environment environment, SslContextFactory sslContextFactory) {
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,

View File

@ -16,10 +16,14 @@
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.Map;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import static org.assertj.core.api.Assertions.assertThat;
@ -27,16 +31,25 @@ import static org.assertj.core.api.Assertions.assertThat;
* Tests for {@link HttpTransport}.
*
* @author Phillip Webb
* @author Scott Frederick
*/
class HttpTransportTests {
@Test
void createWhenHasDockerHostVariableReturnsRemote() {
Map<String, String> environment = Collections.singletonMap("DOCKER_HOST", "192.168.1.0");
void createWhenDockerHostVariableIsAddressReturnsRemote() {
Map<String, String> environment = Collections.singletonMap("DOCKER_HOST", "tcp://192.168.1.0");
HttpTransport transport = HttpTransport.create(environment::get);
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
void createWhenDoesNotHaveDockerHostVariableReturnsLocal() {
HttpTransport transport = HttpTransport.create((name) -> null);

View File

@ -16,6 +16,9 @@
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.Map;
import java.util.function.Consumer;
@ -24,6 +27,7 @@ import javax.net.ssl.SSLContext;
import org.apache.http.HttpHost;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.springframework.boot.buildpack.platform.docker.ssl.SslContextFactory;
@ -40,7 +44,7 @@ import static org.mockito.Mockito.mock;
*/
class RemoteHttpClientTransportTests {
private Map<String, String> environment = new LinkedHashMap<>();
private final Map<String, String> environment = new LinkedHashMap<>();
@Test
void createIfPossibleWhenDockerHostIsNotSetReturnsNull() {
@ -49,7 +53,16 @@ class RemoteHttpClientTransportTests {
}
@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");
RemoteHttpClientTransport transport = RemoteHttpClientTransport.createIfPossible(this.environment::get);
assertThat(transport).isNotNull();