diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java index 176313650a..40004ab7ec 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java @@ -202,6 +202,11 @@ public class ServerProperties implements EmbeddedServletContainerCustomizer { private boolean accessLogEnabled = false; + private String internalProxies = "10\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|" // 10/8 + + "192\\.168\\.\\d{1,3}\\.\\d{1,3}|" // 192.168/16 + + "169\\.254\\.\\d{1,3}\\.\\d{1,3}|" // 169.254/16 + + "127\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}"; // 127/8 + private String protocolHeader = "x-forwarded-proto"; private String remoteIpHeader = "x-forwarded-for"; @@ -264,6 +269,14 @@ public class ServerProperties implements EmbeddedServletContainerCustomizer { this.accessLogPattern = accessLogPattern; } + public String getInternalProxies() { + return this.internalProxies; + } + + public void setInternalProxies(String internalProxies) { + this.internalProxies = internalProxies; + } + public String getProtocolHeader() { return this.protocolHeader; } @@ -307,6 +320,7 @@ public class ServerProperties implements EmbeddedServletContainerCustomizer { RemoteIpValve valve = new RemoteIpValve(); valve.setRemoteIpHeader(remoteIpHeader); valve.setProtocolHeader(protocolHeader); + valve.setInternalProxies(getInternalProxies()); factory.addContextValves(valve); } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/ServerPropertiesTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/ServerPropertiesTests.java index 866a024d00..0f0b936069 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/ServerPropertiesTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/ServerPropertiesTests.java @@ -21,13 +21,18 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; +import org.apache.catalina.Valve; +import org.apache.catalina.valves.RemoteIpValve; import org.junit.Test; import org.springframework.beans.MutablePropertyValues; import org.springframework.boot.bind.RelaxedDataBinder; import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer; +import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory; +import static org.hamcrest.core.IsInstanceOf.instanceOf; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; @@ -37,6 +42,7 @@ import static org.mockito.Mockito.verify; * * @author Dave Syer * @author Stephane Nicoll + * @author Andy Wilkinson */ public class ServerPropertiesTests { @@ -84,13 +90,15 @@ public class ServerPropertiesTests { map.put("server.tomcat.access_log_pattern", "%h %t '%r' %s %b"); map.put("server.tomcat.protocol_header", "X-Forwarded-Protocol"); map.put("server.tomcat.remote_ip_header", "Remote-Ip"); - new RelaxedDataBinder(this.properties, "server").bind(new MutablePropertyValues( - map)); + map.put("server.tomcat.internal_proxies", "10\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}"); + bindProperties(map); assertEquals("%h %t '%r' %s %b", this.properties.getTomcat() .getAccessLogPattern()); assertEquals("Remote-Ip", this.properties.getTomcat().getRemoteIpHeader()); assertEquals("X-Forwarded-Protocol", this.properties.getTomcat() .getProtocolHeader()); + assertEquals("10\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}", this.properties.getTomcat() + .getInternalProxies()); } @Test @@ -112,8 +120,7 @@ public class ServerPropertiesTests { public void testCustomizeUriEncoding() throws Exception { Map map = new HashMap(); map.put("server.tomcat.uriEncoding", "US-ASCII"); - new RelaxedDataBinder(this.properties, "server").bind(new MutablePropertyValues( - map)); + bindProperties(map); assertEquals("US-ASCII", this.properties.getTomcat().getUriEncoding()); } @@ -121,9 +128,66 @@ public class ServerPropertiesTests { public void testCustomizeTomcatHeaderSize() throws Exception { Map map = new HashMap(); map.put("server.tomcat.maxHttpHeaderSize", "9999"); - new RelaxedDataBinder(this.properties, "server").bind(new MutablePropertyValues( - map)); + bindProperties(map); assertEquals(9999, this.properties.getTomcat().getMaxHttpHeaderSize()); } + @Test + public void disableTomcatRemoteIpValve() throws Exception { + Map map = new HashMap(); + map.put("server.tomcat.remote_ip_header", ""); + map.put("server.tomcat.protocol_header", ""); + bindProperties(map); + + TomcatEmbeddedServletContainerFactory container = new TomcatEmbeddedServletContainerFactory(); + this.properties.customize(container); + + assertEquals(0, container.getValves().size()); + } + + @Test + public void defaultTomcatRemoteIpValve() throws Exception { + TomcatEmbeddedServletContainerFactory container = new TomcatEmbeddedServletContainerFactory(); + this.properties.customize(container); + + assertEquals(1, container.getValves().size()); + Valve valve = container.getValves().iterator().next(); + assertThat(valve, instanceOf(RemoteIpValve.class)); + RemoteIpValve remoteIpValve = (RemoteIpValve) valve; + assertEquals("x-forwarded-proto", remoteIpValve.getProtocolHeader()); + assertEquals("x-forwarded-for", remoteIpValve.getRemoteIpHeader()); + + String expectedInternalProxies = "10\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|" // 10/8 + + "192\\.168\\.\\d{1,3}\\.\\d{1,3}|" // 192.168/16 + + "169\\.254\\.\\d{1,3}\\.\\d{1,3}|" // 169.254/16 + + "127\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}"; // 127/8 + + assertEquals(expectedInternalProxies, remoteIpValve.getInternalProxies()); + } + + @Test + public void customTomcatRemoteIpValve() throws Exception { + Map map = new HashMap(); + map.put("server.tomcat.remote_ip_header", "x-my-remote-ip-header"); + map.put("server.tomcat.protocol_header", "x-my-protocol-header"); + map.put("server.tomcat.internal_proxies", "192.168.0.1"); + bindProperties(map); + + TomcatEmbeddedServletContainerFactory container = new TomcatEmbeddedServletContainerFactory(); + this.properties.customize(container); + + assertEquals(1, container.getValves().size()); + Valve valve = container.getValves().iterator().next(); + assertThat(valve, instanceOf(RemoteIpValve.class)); + RemoteIpValve remoteIpValve = (RemoteIpValve) valve; + assertEquals("x-my-protocol-header", remoteIpValve.getProtocolHeader()); + assertEquals("x-my-remote-ip-header", remoteIpValve.getRemoteIpHeader()); + assertEquals("192.168.0.1", remoteIpValve.getInternalProxies()); + } + + private void bindProperties(Map map) { + new RelaxedDataBinder(this.properties, "server").bind(new MutablePropertyValues( + map)); + } + } diff --git a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc index 1b643a93db..bf2d7ebf2b 100644 --- a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc +++ b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc @@ -70,6 +70,10 @@ content into your application; rather pick only the properties that you need. server.ssl.trust-store-type= server.tomcat.access-log-pattern= # log pattern of the access log server.tomcat.access-log-enabled=false # is access logging enabled + server.tomcat.internal-proxies=10\.\d{1,3}\.\d{1,3}\.\d{1,3}|\ + 192\.168\.\d{1,3}\.\d{1,3}|\ + 169\.254\.\d{1,3}\.\d{1,3}|\ + 127\.\d{1,3}\.\d{1,3}\.\d{1,3} # regular expression matching trusted IP addresses server.tomcat.protocol-header=x-forwarded-proto # ssl forward headers server.tomcat.remote-ip-header=x-forwarded-for server.tomcat.basedir=/tmp # base dir (usually not needed, defaults to tmp) diff --git a/spring-boot-docs/src/main/asciidoc/howto.adoc b/spring-boot-docs/src/main/asciidoc/howto.adoc index 4d3e3e1362..f488797432 100644 --- a/spring-boot-docs/src/main/asciidoc/howto.adoc +++ b/spring-boot-docs/src/main/asciidoc/howto.adoc @@ -465,20 +465,38 @@ HTTPS connector: [[howto-use-tomcat-behind-a-proxy-server]] === Use Tomcat behind a front-end proxy server -Spring Boot will automatically configure Tomcat's `RemoteIpValve` if it detects some -environment settings. This allows you to transparently use the standard `x-forwarded-for` -and `x-forwarded-proto` headers that most front-end proxy servers add. - -You can switch on the valve by adding some entries to application.properties, e.g. +Spring Boot will automatically configure Tomcat's `RemoteIpValve`. This allows you to +transparently use the standard `x-forwarded-for` and `x-forwarded-proto` headers that +most front-end proxy servers add. If your proxy uses different headers you can +customize the valve's configuration by adding some entries to `application.properties`, +e.g. [indent=0] ---- - server.tomcat.remote_ip_header=x-forwarded-for - server.tomcat.protocol_header=x-forwarded-proto + server.tomcat.remote_ip_header=x-your-remote-ip-header + server.tomcat.protocol_header=x-your-protocol-header ---- -Alternatively, you can add the `RemoteIpValve` yourself by adding a -`TomcatEmbeddedServletContainerFactory` bean. +The valve is also configured with a default regular expression that matches internal +proxies that are to be trusted. By default, IP addresses in 10/8, 192.168/16, 169.254/16 +and 127/8 are trusted. You can customize the valve's configuration by adding an entry +to `application.properties`, e.g. + +[indent=0] +---- + server.tomcat.internal_proxies=192\.168\.\d{1,3}\.\d{1,3} +---- + +Alternatively, you can take complete control of the configuration of the `RemoteIpValve` +by configuring and adding it in a `TomcatEmbeddedServletContainerFactory` bean. + +Lastly, you can switch off the valve by adding some entries to `application.properties`: + +[indent=0] +---- + server.tomcat.remote_ip_header= + server.tomcat.protocol_header= +----