add allow list for proxy.stream endpoint
This commit is contained in:
@@ -753,6 +753,10 @@ Then visit `/hystrix` and point the dashboard to an individual instance's `/hyst
|
||||
NOTE: When connecting to a `/hystrix.stream` endpoint that uses HTTPS, the certificate used by the server must be trusted by the JVM.
|
||||
If the certificate is not trusted, you must import the certificate into the JVM in order for the Hystrix Dashboard to make a successful connection to the stream endpoint.
|
||||
|
||||
NOTE: In order to use the `/proxy.stream` endpoint you must configure a list of hosts to allow connections to. To set the
|
||||
list of allowed hosts use `hystrix.dashboard.proxyStreamAllowList`. You can use an Ant-style pattern in the host name to
|
||||
match against a wider range of host names.
|
||||
|
||||
=== Turbine
|
||||
|
||||
Looking at an individual instance's Hystrix data is not very useful in terms of the overall health of the system. https://github.com/Netflix/Turbine[Turbine] is an application that aggregates all of the relevant `/hystrix.stream` endpoints into a combined `/turbine.stream` for use in the Hystrix Dashboard.
|
||||
|
||||
@@ -19,7 +19,10 @@ package org.springframework.cloud.netflix.hystrix.dashboard;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
@@ -47,6 +50,8 @@ import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.ui.freemarker.SpringTemplateLoader;
|
||||
import org.springframework.util.AntPathMatcher;
|
||||
import org.springframework.util.PathMatcher;
|
||||
import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;
|
||||
|
||||
/**
|
||||
@@ -88,8 +93,9 @@ public class HystrixDashboardConfiguration {
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ServletRegistrationBean proxyStreamServlet() {
|
||||
final ProxyStreamServlet proxyStreamServlet = new ProxyStreamServlet();
|
||||
public ServletRegistrationBean proxyStreamServlet(
|
||||
HystrixDashboardProperties properties) {
|
||||
final ProxyStreamServlet proxyStreamServlet = new ProxyStreamServlet(properties);
|
||||
proxyStreamServlet.setEnableIgnoreConnectionCloseHeader(
|
||||
this.dashboardProperties.isEnableIgnoreConnectionCloseHeader());
|
||||
final ServletRegistrationBean registration = new ServletRegistrationBean(
|
||||
@@ -118,6 +124,8 @@ public class HystrixDashboardConfiguration {
|
||||
|
||||
private boolean enableIgnoreConnectionCloseHeader = false;
|
||||
|
||||
private HystrixDashboardProperties properties;
|
||||
|
||||
public void setEnableIgnoreConnectionCloseHeader(
|
||||
boolean enableIgnoreConnectionCloseHeader) {
|
||||
this.enableIgnoreConnectionCloseHeader = enableIgnoreConnectionCloseHeader;
|
||||
@@ -125,6 +133,11 @@ public class HystrixDashboardConfiguration {
|
||||
|
||||
public ProxyStreamServlet() {
|
||||
super();
|
||||
this.properties = new HystrixDashboardProperties();
|
||||
}
|
||||
|
||||
public ProxyStreamServlet(HystrixDashboardProperties properties) {
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -169,10 +182,18 @@ public class HystrixDashboardConfiguration {
|
||||
url.append(key).append("=").append(value);
|
||||
}
|
||||
}
|
||||
String proxyUrl = url.toString();
|
||||
log.info("\n\nProxy opening connection to: " + proxyUrl + "\n\n");
|
||||
String proxyUrlString = url.toString();
|
||||
|
||||
if (!isAllowedToProxy(proxyUrlString)) {
|
||||
log.warn("Origin parameter: " + origin
|
||||
+ " is not in the allowed list of proxy host names. If it "
|
||||
+ "should be allowed add it to hystrix.dashboard.proxyStreamAllowList.");
|
||||
return;
|
||||
}
|
||||
|
||||
log.info("\n\nProxy opening connection to: " + proxyUrlString + "\n\n");
|
||||
try {
|
||||
httpget = new HttpGet(proxyUrl);
|
||||
httpget = new HttpGet(proxyUrlString);
|
||||
HttpClient client = ProxyConnectionManager.httpClient;
|
||||
HttpResponse httpResponse = client.execute(httpget);
|
||||
int statusCode = httpResponse.getStatusLine().getStatusCode();
|
||||
@@ -218,7 +239,7 @@ public class HystrixDashboardConfiguration {
|
||||
}
|
||||
}
|
||||
else {
|
||||
log.warn("Failed opening connection to " + proxyUrl + " : "
|
||||
log.warn("Failed opening connection to " + proxyUrlString + " : "
|
||||
+ statusCode + " : " + httpResponse.getStatusLine());
|
||||
}
|
||||
}
|
||||
@@ -250,6 +271,22 @@ public class HystrixDashboardConfiguration {
|
||||
|
||||
}
|
||||
|
||||
private boolean isAllowedToProxy(String proxyUrlString)
|
||||
throws MalformedURLException {
|
||||
|
||||
URL proxyUrl = new URL(proxyUrlString);
|
||||
String host = proxyUrl.getHost();
|
||||
PathMatcher pathMatcher = new AntPathMatcher(".");
|
||||
Optional<String> optionalPattern = properties.getProxyStreamAllowList()
|
||||
.stream().filter(pattern -> pathMatcher.match(pattern, host))
|
||||
.findFirst();
|
||||
|
||||
if (optionalPattern.isPresent()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void copyHeadersToServletResponse(Header[] headers,
|
||||
HttpServletResponse response) {
|
||||
for (Header header : headers) {
|
||||
|
||||
@@ -16,7 +16,10 @@
|
||||
|
||||
package org.springframework.cloud.netflix.hystrix.dashboard;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
@@ -41,6 +44,20 @@ public class HystrixDashboardProperties {
|
||||
*/
|
||||
private Map<String, String> initParameters = new HashMap<>();
|
||||
|
||||
private List<String> proxyStreamAllowList = new ArrayList<>();
|
||||
|
||||
public List<String> getProxyStreamAllowList() {
|
||||
return proxyStreamAllowList;
|
||||
}
|
||||
|
||||
public void setProxyStreamAllowList(List<String> proxyStreamAllowList) {
|
||||
this.proxyStreamAllowList = proxyStreamAllowList;
|
||||
}
|
||||
|
||||
public void setProxyStreamAllowList(String... regex) {
|
||||
this.proxyStreamAllowList = Arrays.asList(regex);
|
||||
}
|
||||
|
||||
public boolean isEnableIgnoreConnectionCloseHeader() {
|
||||
return enableIgnoreConnectionCloseHeader;
|
||||
}
|
||||
|
||||
@@ -41,7 +41,8 @@ public class HystrixDashboardConfigurationTests {
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
Header[] headers = new Header[1];
|
||||
headers[0] = new BasicHeader("Content-Type", "text/proxy.stream");
|
||||
HystrixDashboardConfiguration.ProxyStreamServlet proxyStreamServlet = new HystrixDashboardConfiguration.ProxyStreamServlet();
|
||||
HystrixDashboardConfiguration.ProxyStreamServlet proxyStreamServlet = new HystrixDashboardConfiguration.ProxyStreamServlet(
|
||||
new HystrixDashboardProperties());
|
||||
ReflectionTestUtils.invokeMethod(proxyStreamServlet,
|
||||
"copyHeadersToServletResponse", headers, response);
|
||||
assertThat(response.getHeaderNames().size()).isEqualTo(1);
|
||||
@@ -54,7 +55,8 @@ public class HystrixDashboardConfigurationTests {
|
||||
Header[] headers = new Header[2];
|
||||
headers[0] = new BasicHeader("Content-Type", "text/proxy.stream");
|
||||
headers[1] = new BasicHeader("Connection", "close");
|
||||
HystrixDashboardConfiguration.ProxyStreamServlet proxyStreamServlet = new HystrixDashboardConfiguration.ProxyStreamServlet();
|
||||
HystrixDashboardConfiguration.ProxyStreamServlet proxyStreamServlet = new HystrixDashboardConfiguration.ProxyStreamServlet(
|
||||
new HystrixDashboardProperties());
|
||||
ReflectionTestUtils.invokeMethod(proxyStreamServlet,
|
||||
"copyHeadersToServletResponse", headers, response);
|
||||
assertThat(response.getHeaderNames().size()).isEqualTo(2);
|
||||
@@ -68,7 +70,8 @@ public class HystrixDashboardConfigurationTests {
|
||||
Header[] headers = new Header[2];
|
||||
headers[0] = new BasicHeader("Content-Type", "text/proxy.stream");
|
||||
headers[1] = new BasicHeader("Connection", "close");
|
||||
HystrixDashboardConfiguration.ProxyStreamServlet proxyStreamServlet = new HystrixDashboardConfiguration.ProxyStreamServlet();
|
||||
HystrixDashboardConfiguration.ProxyStreamServlet proxyStreamServlet = new HystrixDashboardConfiguration.ProxyStreamServlet(
|
||||
new HystrixDashboardProperties());
|
||||
proxyStreamServlet.setEnableIgnoreConnectionCloseHeader(true);
|
||||
ReflectionTestUtils.invokeMethod(proxyStreamServlet,
|
||||
"copyHeadersToServletResponse", headers, response);
|
||||
@@ -83,7 +86,8 @@ public class HystrixDashboardConfigurationTests {
|
||||
Header[] headers = new Header[2];
|
||||
headers[0] = new BasicHeader("Content-Type", "text/proxy.stream");
|
||||
headers[1] = new BasicHeader("Connection", "close");
|
||||
HystrixDashboardConfiguration.ProxyStreamServlet proxyStreamServlet = new HystrixDashboardConfiguration.ProxyStreamServlet();
|
||||
HystrixDashboardConfiguration.ProxyStreamServlet proxyStreamServlet = new HystrixDashboardConfiguration.ProxyStreamServlet(
|
||||
new HystrixDashboardProperties());
|
||||
proxyStreamServlet.setEnableIgnoreConnectionCloseHeader(false);
|
||||
ReflectionTestUtils.invokeMethod(proxyStreamServlet,
|
||||
"copyHeadersToServletResponse", headers, response);
|
||||
@@ -111,4 +115,39 @@ public class HystrixDashboardConfigurationTests {
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void allowedHostsTest() {
|
||||
HystrixDashboardProperties properties = new HystrixDashboardProperties();
|
||||
HystrixDashboardConfiguration.ProxyStreamServlet proxyStreamServlet = new HystrixDashboardConfiguration.ProxyStreamServlet(
|
||||
properties);
|
||||
boolean allowed = ReflectionTestUtils.invokeMethod(proxyStreamServlet,
|
||||
"isAllowedToProxy", "http://foo.com");
|
||||
assertThat(allowed).isFalse();
|
||||
|
||||
properties.setProxyStreamAllowList("foo.com", "bar.*", "*world.com");
|
||||
allowed = ReflectionTestUtils.invokeMethod(proxyStreamServlet, "isAllowedToProxy",
|
||||
"http://user:password@foo.com");
|
||||
assertThat(allowed).isTrue();
|
||||
|
||||
allowed = ReflectionTestUtils.invokeMethod(proxyStreamServlet, "isAllowedToProxy",
|
||||
"http://bar.com");
|
||||
assertThat(allowed).isTrue();
|
||||
|
||||
allowed = ReflectionTestUtils.invokeMethod(proxyStreamServlet, "isAllowedToProxy",
|
||||
"http://bar.org");
|
||||
assertThat(allowed).isTrue();
|
||||
|
||||
allowed = ReflectionTestUtils.invokeMethod(proxyStreamServlet, "isAllowedToProxy",
|
||||
"http://helloworld.com");
|
||||
assertThat(allowed).isTrue();
|
||||
|
||||
allowed = ReflectionTestUtils.invokeMethod(proxyStreamServlet, "isAllowedToProxy",
|
||||
"http://world.com");
|
||||
assertThat(allowed).isTrue();
|
||||
|
||||
allowed = ReflectionTestUtils.invokeMethod(proxyStreamServlet, "isAllowedToProxy",
|
||||
"http://world.org");
|
||||
assertThat(allowed).isFalse();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user