add allow list for proxy.stream endpoint

This commit is contained in:
Ryan Baxter
2020-07-30 15:15:09 -04:00
parent 4de8761123
commit 624bbc8b50
4 changed files with 107 additions and 10 deletions

View File

@@ -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.

View File

@@ -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) {

View File

@@ -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;
}

View File

@@ -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();
}
}