HttpServer: Support relative plugin paths in configuration
When specifying relative paths on startup, handling plugin paths failed due to recently added security fix. This fix ensures normalization of the plugin path as well. In addition a new matcher has been added to easily check for a status code of an HTTP response likes this assertThat(response, hasStatus(OK)); Closes #10958
This commit is contained in:
parent
8831ae6e5c
commit
f05808d59e
|
@ -183,7 +183,7 @@ public class HttpServer extends AbstractLifecycleComponent<HttpServer> {
|
|||
Path file = siteFile.resolve(sitePath);
|
||||
|
||||
// return not found instead of forbidden to prevent malicious requests to find out if files exist or dont exist
|
||||
if (!Files.exists(file) || Files.isHidden(file) || !file.toAbsolutePath().normalize().startsWith(siteFile.toAbsolutePath())) {
|
||||
if (!Files.exists(file) || Files.isHidden(file) || !file.toAbsolutePath().normalize().startsWith(siteFile.toAbsolutePath().normalize())) {
|
||||
channel.sendResponse(new BytesRestResponse(NOT_FOUND));
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -18,19 +18,18 @@
|
|||
*/
|
||||
package org.elasticsearch.plugins;
|
||||
|
||||
import org.apache.http.impl.client.HttpClients;
|
||||
import org.elasticsearch.common.settings.ImmutableSettings;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.http.HttpServerTransport;
|
||||
import org.elasticsearch.rest.RestStatus;
|
||||
import org.elasticsearch.plugins.responseheader.TestResponseHeaderPlugin;
|
||||
import org.elasticsearch.test.ElasticsearchIntegrationTest;
|
||||
import org.elasticsearch.test.ElasticsearchIntegrationTest.ClusterScope;
|
||||
import org.elasticsearch.test.rest.client.http.HttpRequestBuilder;
|
||||
import org.elasticsearch.test.rest.client.http.HttpResponse;
|
||||
import org.elasticsearch.plugins.responseheader.TestResponseHeaderPlugin;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.elasticsearch.rest.RestStatus.OK;
|
||||
import static org.elasticsearch.rest.RestStatus.UNAUTHORIZED;
|
||||
import static org.elasticsearch.test.ElasticsearchIntegrationTest.Scope;
|
||||
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.hasStatus;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
|
||||
/**
|
||||
|
@ -52,11 +51,11 @@ public class ResponseHeaderPluginTests extends ElasticsearchIntegrationTest {
|
|||
public void testThatSettingHeadersWorks() throws Exception {
|
||||
ensureGreen();
|
||||
HttpResponse response = httpClient().method("GET").path("/_protected").execute();
|
||||
assertThat(response.getStatusCode(), equalTo(RestStatus.UNAUTHORIZED.getStatus()));
|
||||
assertThat(response, hasStatus(UNAUTHORIZED));
|
||||
assertThat(response.getHeaders().get("Secret"), equalTo("required"));
|
||||
|
||||
HttpResponse authResponse = httpClient().method("GET").path("/_protected").addHeader("Secret", "password").execute();
|
||||
assertThat(authResponse.getStatusCode(), equalTo(RestStatus.OK.getStatus()));
|
||||
assertThat(authResponse, hasStatus(OK));
|
||||
assertThat(authResponse.getHeaders().get("Secret"), equalTo("granted"));
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.elasticsearch.plugins;
|
||||
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClients;
|
||||
import org.elasticsearch.common.io.PathUtils;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.http.HttpServerTransport;
|
||||
import org.elasticsearch.test.ElasticsearchIntegrationTest;
|
||||
import org.elasticsearch.test.ElasticsearchIntegrationTest.ClusterScope;
|
||||
import org.elasticsearch.test.rest.client.http.HttpRequestBuilder;
|
||||
import org.elasticsearch.test.rest.client.http.HttpResponse;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.apache.lucene.util.Constants.WINDOWS;
|
||||
import static org.elasticsearch.common.settings.ImmutableSettings.settingsBuilder;
|
||||
import static org.elasticsearch.rest.RestStatus.OK;
|
||||
import static org.elasticsearch.test.ElasticsearchIntegrationTest.Scope.SUITE;
|
||||
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.hasStatus;
|
||||
|
||||
@ClusterScope(scope = SUITE, numDataNodes = 1)
|
||||
public class SitePluginRelativePathConfigTests extends ElasticsearchIntegrationTest {
|
||||
|
||||
private final Path root = PathUtils.get(".").toAbsolutePath().getRoot();
|
||||
|
||||
@Override
|
||||
protected Settings nodeSettings(int nodeOrdinal) {
|
||||
String cwdToRoot = getRelativePath(PathUtils.get(".").toAbsolutePath());
|
||||
Path pluginDir = PathUtils.get(cwdToRoot, relativizeToRootIfNecessary(getDataPath("/org/elasticsearch/plugins")).toString());
|
||||
|
||||
Path tempDir = createTempDir();
|
||||
boolean useRelativeInMiddleOfPath = randomBoolean();
|
||||
if (useRelativeInMiddleOfPath) {
|
||||
pluginDir = PathUtils.get(tempDir.toString(), getRelativePath(tempDir), pluginDir.toString());
|
||||
}
|
||||
|
||||
return settingsBuilder()
|
||||
.put(super.nodeSettings(nodeOrdinal))
|
||||
.put("path.plugins", pluginDir)
|
||||
.put("force.http.enabled", true)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testThatRelativePathsDontAffectPlugins() throws Exception {
|
||||
HttpResponse response = httpClient().method("GET").path("/_plugin/dummy/").execute();
|
||||
assertThat(response, hasStatus(OK));
|
||||
}
|
||||
|
||||
private Path relativizeToRootIfNecessary(Path path) {
|
||||
if (WINDOWS) {
|
||||
return root.relativize(path);
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
private String getRelativePath(Path path) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < path.getNameCount(); i++) {
|
||||
sb.append("..");
|
||||
sb.append(path.getFileSystem().getSeparator());
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public HttpRequestBuilder httpClient() {
|
||||
CloseableHttpClient httpClient = HttpClients.createDefault();
|
||||
return new HttpRequestBuilder(httpClient).httpTransport(internalCluster().getDataNodeInstance(HttpServerTransport.class));
|
||||
}
|
||||
}
|
|
@ -21,27 +21,24 @@ package org.elasticsearch.plugins;
|
|||
import org.apache.http.client.config.RequestConfig;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClients;
|
||||
import org.elasticsearch.common.io.PathUtils;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.http.HttpServerTransport;
|
||||
import org.elasticsearch.rest.RestStatus;
|
||||
import org.elasticsearch.test.ElasticsearchIntegrationTest;
|
||||
import org.elasticsearch.test.ElasticsearchIntegrationTest.ClusterScope;
|
||||
import org.elasticsearch.test.rest.client.http.HttpRequestBuilder;
|
||||
import org.elasticsearch.test.rest.client.http.HttpResponse;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import static org.elasticsearch.common.settings.ImmutableSettings.settingsBuilder;
|
||||
import static org.elasticsearch.rest.RestStatus.*;
|
||||
import static org.elasticsearch.test.ElasticsearchIntegrationTest.Scope;
|
||||
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.hasStatus;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
|
||||
/**
|
||||
* We want to test site plugins
|
||||
|
@ -70,12 +67,12 @@ public class SitePluginTests extends ElasticsearchIntegrationTest {
|
|||
public void testRedirectSitePlugin() throws Exception {
|
||||
// We use an HTTP Client to test redirection
|
||||
HttpResponse response = httpClient().method("GET").path("/_plugin/dummy").execute();
|
||||
assertThat(response.getStatusCode(), equalTo(RestStatus.MOVED_PERMANENTLY.getStatus()));
|
||||
assertThat(response, hasStatus(MOVED_PERMANENTLY));
|
||||
assertThat(response.getBody(), containsString("/_plugin/dummy/"));
|
||||
|
||||
// We test the real URL
|
||||
response = httpClient().method("GET").path("/_plugin/dummy/").execute();
|
||||
assertThat(response.getStatusCode(), equalTo(RestStatus.OK.getStatus()));
|
||||
assertThat(response, hasStatus(OK));
|
||||
assertThat(response.getBody(), containsString("<title>Dummy Site Plugin</title>"));
|
||||
}
|
||||
|
||||
|
@ -85,7 +82,7 @@ public class SitePluginTests extends ElasticsearchIntegrationTest {
|
|||
@Test
|
||||
public void testAnyPage() throws Exception {
|
||||
HttpResponse response = httpClient().path("/_plugin/dummy/index.html").execute();
|
||||
assertThat(response.getStatusCode(), equalTo(RestStatus.OK.getStatus()));
|
||||
assertThat(response, hasStatus(OK));
|
||||
assertThat(response.getBody(), containsString("<title>Dummy Site Plugin</title>"));
|
||||
}
|
||||
|
||||
|
@ -108,12 +105,12 @@ public class SitePluginTests extends ElasticsearchIntegrationTest {
|
|||
for (String uri : notFoundUris) {
|
||||
HttpResponse response = httpClient().path(uri).execute();
|
||||
String message = String.format(Locale.ROOT, "URI [%s] expected to be not found", uri);
|
||||
assertThat(message, response.getStatusCode(), equalTo(RestStatus.NOT_FOUND.getStatus()));
|
||||
assertThat(message, response, hasStatus(NOT_FOUND));
|
||||
}
|
||||
|
||||
// using relative path inside of the plugin should work
|
||||
HttpResponse response = httpClient().path("/_plugin/dummy/dir1/../dir1/../index.html").execute();
|
||||
assertThat(response.getStatusCode(), equalTo(RestStatus.OK.getStatus()));
|
||||
assertThat(response, hasStatus(OK));
|
||||
assertThat(response.getBody(), containsString("<title>Dummy Site Plugin</title>"));
|
||||
}
|
||||
|
||||
|
@ -124,14 +121,14 @@ public class SitePluginTests extends ElasticsearchIntegrationTest {
|
|||
@Test
|
||||
public void testWelcomePageInSubDirs() throws Exception {
|
||||
HttpResponse response = httpClient().path("/_plugin/subdir/dir/").execute();
|
||||
assertThat(response.getStatusCode(), equalTo(RestStatus.OK.getStatus()));
|
||||
assertThat(response, hasStatus(OK));
|
||||
assertThat(response.getBody(), containsString("<title>Dummy Site Plugin (subdir)</title>"));
|
||||
|
||||
response = httpClient().path("/_plugin/subdir/dir_without_index/").execute();
|
||||
assertThat(response.getStatusCode(), equalTo(RestStatus.FORBIDDEN.getStatus()));
|
||||
assertThat(response, hasStatus(FORBIDDEN));
|
||||
|
||||
response = httpClient().path("/_plugin/subdir/dir_without_index/page.html").execute();
|
||||
assertThat(response.getStatusCode(), equalTo(RestStatus.OK.getStatus()));
|
||||
assertThat(response, hasStatus(OK));
|
||||
assertThat(response.getBody(), containsString("<title>Dummy Site Plugin (page)</title>"));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -66,6 +66,7 @@ import org.elasticsearch.rest.RestStatus;
|
|||
import org.elasticsearch.search.SearchHit;
|
||||
import org.elasticsearch.search.suggest.Suggest;
|
||||
import org.elasticsearch.test.VersionUtils;
|
||||
import org.elasticsearch.test.rest.client.http.HttpResponse;
|
||||
import org.hamcrest.Matcher;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.Assert;
|
||||
|
@ -490,6 +491,10 @@ public class ElasticsearchAssertions {
|
|||
return new ElasticsearchMatchers.SearchHitHasScoreMatcher(score);
|
||||
}
|
||||
|
||||
public static Matcher<HttpResponse> hasStatus(RestStatus restStatus) {
|
||||
return new ElasticsearchMatchers.HttpResponseHasStatusMatcher(restStatus);
|
||||
}
|
||||
|
||||
public static <T extends Query> T assertBooleanSubQuery(Query query, Class<T> subqueryType, int i) {
|
||||
assertThat(query, instanceOf(BooleanQuery.class));
|
||||
BooleanQuery q = (BooleanQuery) query;
|
||||
|
|
|
@ -18,8 +18,11 @@
|
|||
*/
|
||||
package org.elasticsearch.test.hamcrest;
|
||||
|
||||
import org.elasticsearch.rest.RestStatus;
|
||||
import org.elasticsearch.search.SearchHit;
|
||||
import org.elasticsearch.test.rest.client.http.HttpResponse;
|
||||
import org.hamcrest.Description;
|
||||
import org.hamcrest.Matcher;
|
||||
import org.hamcrest.TypeSafeMatcher;
|
||||
|
||||
public class ElasticsearchMatchers {
|
||||
|
@ -115,4 +118,28 @@ public class ElasticsearchMatchers {
|
|||
description.appendText("searchHit score should be ").appendValue(score);
|
||||
}
|
||||
}
|
||||
|
||||
public static class HttpResponseHasStatusMatcher extends TypeSafeMatcher<HttpResponse> {
|
||||
|
||||
private RestStatus restStatus;
|
||||
|
||||
public HttpResponseHasStatusMatcher(RestStatus restStatus) {
|
||||
this.restStatus = restStatus;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean matchesSafely(HttpResponse response) {
|
||||
return response.getStatusCode() == restStatus.getStatus();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void describeMismatchSafely(final HttpResponse response, final Description mismatchDescription) {
|
||||
mismatchDescription.appendText(" was ").appendValue(response.getStatusCode());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void describeTo(final Description description) {
|
||||
description.appendText("HTTP response status code should be ").appendValue(restStatus.getStatus());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue