PluginManager: Add Support for basic auth
In order to support the URL notation including a user/pass combination (like http://user:pass@host/plugin.zip) the auth info needs to be added manually.
This commit is contained in:
parent
60f8fa4451
commit
5a701367a7
|
@ -19,12 +19,13 @@
|
||||||
|
|
||||||
package org.elasticsearch.common.http.client;
|
package org.elasticsearch.common.http.client;
|
||||||
|
|
||||||
|
import com.google.common.base.Charsets;
|
||||||
|
import com.google.common.base.Strings;
|
||||||
import org.apache.lucene.util.IOUtils;
|
import org.apache.lucene.util.IOUtils;
|
||||||
import org.elasticsearch.ElasticsearchTimeoutException;
|
import org.elasticsearch.ElasticsearchTimeoutException;
|
||||||
import org.elasticsearch.Version;
|
import org.elasticsearch.Version;
|
||||||
|
import org.elasticsearch.common.Base64;
|
||||||
import org.elasticsearch.common.Nullable;
|
import org.elasticsearch.common.Nullable;
|
||||||
import org.elasticsearch.common.SuppressForbidden;
|
|
||||||
import org.elasticsearch.common.cli.Terminal;
|
|
||||||
import org.elasticsearch.common.unit.TimeValue;
|
import org.elasticsearch.common.unit.TimeValue;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
|
@ -266,6 +267,17 @@ public class HttpDownloadHelper {
|
||||||
connection.setIfModifiedSince(timestamp);
|
connection.setIfModifiedSince(timestamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// in case the plugin manager is its own project, this can become an authenticator
|
||||||
|
boolean isSecureProcotol = "https".equalsIgnoreCase(aSource.getProtocol());
|
||||||
|
boolean isAuthInfoSet = !Strings.isNullOrEmpty(aSource.getUserInfo());
|
||||||
|
if (isAuthInfoSet) {
|
||||||
|
if (!isSecureProcotol) {
|
||||||
|
throw new IOException("Basic auth is only supported for HTTPS!");
|
||||||
|
}
|
||||||
|
String basicAuth = Base64.encodeBytes(aSource.getUserInfo().getBytes(Charsets.UTF_8));
|
||||||
|
connection.setRequestProperty("Authorization", "Basic " + basicAuth);
|
||||||
|
}
|
||||||
|
|
||||||
if (connection instanceof HttpURLConnection) {
|
if (connection instanceof HttpURLConnection) {
|
||||||
((HttpURLConnection) connection).setInstanceFollowRedirects(false);
|
((HttpURLConnection) connection).setInstanceFollowRedirects(false);
|
||||||
((HttpURLConnection) connection).setUseCaches(true);
|
((HttpURLConnection) connection).setUseCaches(true);
|
||||||
|
|
|
@ -22,7 +22,6 @@ package org.elasticsearch.plugins;
|
||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
import com.google.common.collect.ImmutableSet;
|
import com.google.common.collect.ImmutableSet;
|
||||||
import com.google.common.collect.Iterators;
|
import com.google.common.collect.Iterators;
|
||||||
|
|
||||||
import org.apache.lucene.util.IOUtils;
|
import org.apache.lucene.util.IOUtils;
|
||||||
import org.elasticsearch.ElasticsearchTimeoutException;
|
import org.elasticsearch.ElasticsearchTimeoutException;
|
||||||
import org.elasticsearch.ExceptionsHelper;
|
import org.elasticsearch.ExceptionsHelper;
|
||||||
|
@ -132,6 +131,12 @@ public class PluginManager {
|
||||||
// first, try directly from the URL provided
|
// first, try directly from the URL provided
|
||||||
if (url != null) {
|
if (url != null) {
|
||||||
URL pluginUrl = new URL(url);
|
URL pluginUrl = new URL(url);
|
||||||
|
boolean isSecureProcotol = "https".equalsIgnoreCase(pluginUrl.getProtocol());
|
||||||
|
boolean isAuthInfoSet = !Strings.isNullOrEmpty(pluginUrl.getUserInfo());
|
||||||
|
if (isAuthInfoSet && !isSecureProcotol) {
|
||||||
|
throw new IOException("Basic auth is only supported for HTTPS!");
|
||||||
|
}
|
||||||
|
|
||||||
terminal.println("Trying %s ...", pluginUrl.toExternalForm());
|
terminal.println("Trying %s ...", pluginUrl.toExternalForm());
|
||||||
try {
|
try {
|
||||||
downloadHelper.download(pluginUrl, pluginFile, progress, this.timeout);
|
downloadHelper.download(pluginUrl, pluginFile, progress, this.timeout);
|
||||||
|
|
|
@ -18,9 +18,12 @@
|
||||||
*/
|
*/
|
||||||
package org.elasticsearch.plugins;
|
package org.elasticsearch.plugins;
|
||||||
|
|
||||||
|
import com.google.common.base.Charsets;
|
||||||
import org.apache.http.impl.client.HttpClients;
|
import org.apache.http.impl.client.HttpClients;
|
||||||
import org.apache.lucene.util.LuceneTestCase;
|
import org.apache.lucene.util.LuceneTestCase;
|
||||||
import org.elasticsearch.Version;
|
import org.elasticsearch.Version;
|
||||||
|
import org.elasticsearch.common.Base64;
|
||||||
|
import org.elasticsearch.common.cli.CliTool;
|
||||||
import org.elasticsearch.common.cli.CliTool.ExitStatus;
|
import org.elasticsearch.common.cli.CliTool.ExitStatus;
|
||||||
import org.elasticsearch.common.cli.CliToolTestCase.CaptureOutputTerminal;
|
import org.elasticsearch.common.cli.CliToolTestCase.CaptureOutputTerminal;
|
||||||
import org.elasticsearch.common.collect.Tuple;
|
import org.elasticsearch.common.collect.Tuple;
|
||||||
|
@ -32,11 +35,23 @@ import org.elasticsearch.test.ESIntegTestCase.ClusterScope;
|
||||||
import org.elasticsearch.test.junit.annotations.Network;
|
import org.elasticsearch.test.junit.annotations.Network;
|
||||||
import org.elasticsearch.test.rest.client.http.HttpRequestBuilder;
|
import org.elasticsearch.test.rest.client.http.HttpRequestBuilder;
|
||||||
import org.elasticsearch.test.rest.client.http.HttpResponse;
|
import org.elasticsearch.test.rest.client.http.HttpResponse;
|
||||||
|
import org.jboss.netty.bootstrap.ServerBootstrap;
|
||||||
|
import org.jboss.netty.channel.*;
|
||||||
|
import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;
|
||||||
|
import org.jboss.netty.handler.codec.http.*;
|
||||||
|
import org.jboss.netty.handler.ssl.SslContext;
|
||||||
|
import org.jboss.netty.handler.ssl.SslHandler;
|
||||||
|
import org.jboss.netty.handler.ssl.util.InsecureTrustManagerFactory;
|
||||||
|
import org.jboss.netty.handler.ssl.util.SelfSignedCertificate;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import javax.net.ssl.HttpsURLConnection;
|
||||||
|
import javax.net.ssl.SSLContext;
|
||||||
|
import javax.net.ssl.SSLSocketFactory;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.FileVisitResult;
|
import java.nio.file.FileVisitResult;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
|
@ -46,6 +61,8 @@ import java.nio.file.attribute.BasicFileAttributes;
|
||||||
import java.nio.file.attribute.PosixFileAttributeView;
|
import java.nio.file.attribute.PosixFileAttributeView;
|
||||||
import java.nio.file.attribute.PosixFileAttributes;
|
import java.nio.file.attribute.PosixFileAttributes;
|
||||||
import java.nio.file.attribute.PosixFilePermission;
|
import java.nio.file.attribute.PosixFilePermission;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.zip.ZipEntry;
|
import java.util.zip.ZipEntry;
|
||||||
import java.util.zip.ZipOutputStream;
|
import java.util.zip.ZipOutputStream;
|
||||||
|
@ -59,6 +76,7 @@ import static org.elasticsearch.test.ESIntegTestCase.Scope;
|
||||||
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertDirectoryExists;
|
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertDirectoryExists;
|
||||||
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertFileExists;
|
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertFileExists;
|
||||||
import static org.hamcrest.Matchers.*;
|
import static org.hamcrest.Matchers.*;
|
||||||
|
import static org.jboss.netty.handler.codec.http.HttpVersion.HTTP_1_1;
|
||||||
|
|
||||||
@ClusterScope(scope = Scope.TEST, numDataNodes = 0, transportClientRatio = 0.0)
|
@ClusterScope(scope = Scope.TEST, numDataNodes = 0, transportClientRatio = 0.0)
|
||||||
@LuceneTestCase.SuppressFileSystems("*") // TODO: clean up this test to allow extra files
|
@LuceneTestCase.SuppressFileSystems("*") // TODO: clean up this test to allow extra files
|
||||||
|
@ -477,6 +495,77 @@ public class PluginManagerIT extends ESIntegTestCase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testThatBasicAuthIsRejectedOnHttp() throws Exception {
|
||||||
|
assertStatus(String.format(Locale.ROOT, "install foo --url http://user:pass@localhost:12345/foo.zip --verbose"), CliTool.ExitStatus.IO_ERROR);
|
||||||
|
assertThat(terminal.getTerminalOutput(), hasItem(containsString("Basic auth is only supported for HTTPS!")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testThatBasicAuthIsSupportedWithHttps() throws Exception {
|
||||||
|
assumeTrue("test requires security manager to be disabled", System.getSecurityManager() == null);
|
||||||
|
|
||||||
|
SSLSocketFactory defaultSocketFactory = HttpsURLConnection.getDefaultSSLSocketFactory();
|
||||||
|
ServerBootstrap serverBootstrap = new ServerBootstrap(new NioServerSocketChannelFactory());
|
||||||
|
SelfSignedCertificate ssc = new SelfSignedCertificate("localhost");
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
// Create a trust manager that does not validate certificate chains:
|
||||||
|
SSLContext sc = SSLContext.getInstance("SSL");
|
||||||
|
sc.init(null, InsecureTrustManagerFactory.INSTANCE.getTrustManagers(), null);
|
||||||
|
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
|
||||||
|
|
||||||
|
final List<HttpRequest> requests = new ArrayList<>();
|
||||||
|
final SslContext sslContext = SslContext.newServerContext(ssc.certificate(), ssc.privateKey());
|
||||||
|
|
||||||
|
serverBootstrap.setPipelineFactory(new ChannelPipelineFactory() {
|
||||||
|
@Override
|
||||||
|
public ChannelPipeline getPipeline() throws Exception {
|
||||||
|
return Channels.pipeline(
|
||||||
|
new SslHandler(sslContext.newEngine()),
|
||||||
|
new HttpRequestDecoder(),
|
||||||
|
new HttpResponseEncoder(),
|
||||||
|
new LoggingServerHandler(requests)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Channel channel = serverBootstrap.bind(new InetSocketAddress("localhost", 0));
|
||||||
|
int port = ((InetSocketAddress) channel.getLocalAddress()).getPort();
|
||||||
|
// IO_ERROR because there is no real file delivered...
|
||||||
|
assertStatus(String.format(Locale.ROOT, "install foo --url https://user:pass@localhost:%s/foo.zip --verbose --timeout 1s", port), ExitStatus.IO_ERROR);
|
||||||
|
|
||||||
|
assertThat(requests, hasSize(1));
|
||||||
|
String msg = String.format(Locale.ROOT, "Request header did not contain Authorization header, terminal output was: %s", terminal.getTerminalOutput());
|
||||||
|
assertThat(msg, requests.get(0).headers().contains("Authorization"), is(true));
|
||||||
|
assertThat(msg, requests.get(0).headers().get("Authorization"), is("Basic " + Base64.encodeBytes("user:pass".getBytes(Charsets.UTF_8))));
|
||||||
|
} finally {
|
||||||
|
HttpsURLConnection.setDefaultSSLSocketFactory(defaultSocketFactory);
|
||||||
|
serverBootstrap.releaseExternalResources();
|
||||||
|
ssc.delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class LoggingServerHandler extends SimpleChannelUpstreamHandler {
|
||||||
|
|
||||||
|
private List<HttpRequest> requests;
|
||||||
|
|
||||||
|
public LoggingServerHandler(List<HttpRequest> requests) {
|
||||||
|
this.requests = requests;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void messageReceived(final ChannelHandlerContext ctx, final MessageEvent e) throws InterruptedException {
|
||||||
|
final HttpRequest request = (HttpRequest) e.getMessage();
|
||||||
|
requests.add(request);
|
||||||
|
final org.jboss.netty.handler.codec.http.HttpResponse response = new DefaultHttpResponse(HTTP_1_1, HttpResponseStatus.BAD_REQUEST);
|
||||||
|
ctx.getChannel().write(response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private Tuple<Settings, Environment> buildInitialSettings() throws IOException {
|
private Tuple<Settings, Environment> buildInitialSettings() throws IOException {
|
||||||
Settings settings = settingsBuilder()
|
Settings settings = settingsBuilder()
|
||||||
.put("discovery.zen.ping.multicast.enabled", false)
|
.put("discovery.zen.ping.multicast.enabled", false)
|
||||||
|
|
Loading…
Reference in New Issue