Compare commits

...

10 Commits
main ... 2.2.x

  1. 20
      README.adoc
  2. 2
      docs/pom.xml
  3. 2
      docs/src/main/asciidoc/spring-cloud-gateway.adoc
  4. 10
      pom.xml
  5. 2
      spring-cloud-gateway-core/pom.xml
  6. 4
      spring-cloud-gateway-dependencies/pom.xml
  7. 2
      spring-cloud-gateway-integration-tests/mvc-failure-analyzer/pom.xml
  8. 2
      spring-cloud-gateway-integration-tests/pom.xml
  9. 2
      spring-cloud-gateway-mvc/pom.xml
  10. 2
      spring-cloud-gateway-sample/pom.xml
  11. 2
      spring-cloud-gateway-server/pom.xml
  12. 6
      spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java
  13. 51
      spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/headers/TransferEncodingNormalizationHeadersFilter.java
  14. 155
      spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/headers/TransferEncodingNormalizationHeadersFilterIntegrationTests.java
  15. 69
      spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/headers/TransferEncodingNormalizationHeadersFilterTests.java
  16. 13
      spring-cloud-gateway-server/src/test/resources/transfer-encoding/invalid-request.bin
  17. 7
      spring-cloud-gateway-server/src/test/resources/transfer-encoding/valid-request.bin
  18. 2
      spring-cloud-gateway-webflux/pom.xml
  19. 2
      spring-cloud-starter-gateway/pom.xml

20
README.adoc

@ -54,23 +54,9 @@ the `.mvn` configuration, so if you find you have to do it to make a @@ -54,23 +54,9 @@ the `.mvn` configuration, so if you find you have to do it to make a
build succeed, please raise a ticket to get the settings added to
source control.
For hints on how to build the project look in `.travis.yml` if there
is one. There should be a "script" and maybe "install" command. Also
look at the "services" section to see if any services need to be
running locally (e.g. mongo or rabbit). Ignore the git-related bits
that you might find in "before_install" since they're related to setting git
credentials and you already have those.
The projects that require middleware generally include a
`docker-compose.yml`, so consider using
https://docs.docker.com/compose/[Docker Compose] to run the middeware servers
in Docker containers. See the README in the
https://github.com/spring-cloud-samples/scripts[scripts demo
repository] for specific instructions about the common cases of mongo,
rabbit and redis.
NOTE: If all else fails, build with the command from `.travis.yml` (usually
`./mvnw install`).
The projects that require middleware (i.e. Redis) for testing generally
require that a local instance of [Docker](https://www.docker.com/get-started) is installed and running.
=== Documentation

2
docs/pom.xml

@ -6,7 +6,7 @@ @@ -6,7 +6,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-gateway</artifactId>
<version>2.2.9.BUILD-SNAPSHOT</version>
<version>2.2.11.BUILD-SNAPSHOT</version>
</parent>
<artifactId>spring-cloud-gateway-docs</artifactId>
<packaging>pom</packaging>

2
docs/src/main/asciidoc/spring-cloud-gateway.adoc

@ -2634,7 +2634,7 @@ In order to write a Route Predicate you will need to implement `RoutePredicateFa @@ -2634,7 +2634,7 @@ In order to write a Route Predicate you will need to implement `RoutePredicateFa
.MyRoutePredicateFactory.java
[source,java]
----
public class MyRoutePredicateFactory extends AbstractRoutePredicateFactory<HeaderRoutePredicateFactory.Config> {
public class MyRoutePredicateFactory extends AbstractRoutePredicateFactory<MyRoutePredicateFactory.Config> {
public MyRoutePredicateFactory() {
super(Config.class);

10
pom.xml

@ -6,7 +6,7 @@ @@ -6,7 +6,7 @@
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-gateway</artifactId>
<version>2.2.9.BUILD-SNAPSHOT</version>
<version>2.2.11.BUILD-SNAPSHOT</version>
<packaging>pom</packaging>
<name>Spring Cloud Gateway</name>
@ -15,7 +15,7 @@ @@ -15,7 +15,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-build</artifactId>
<version>2.3.4.RELEASE</version>
<version>2.3.5.RELEASE</version>
<relativePath/>
</parent>
<scm>
@ -52,9 +52,9 @@ @@ -52,9 +52,9 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud-commons.version>2.2.9.BUILD-SNAPSHOT</spring-cloud-commons.version>
<spring-cloud-netflix.version>2.2.9.BUILD-SNAPSHOT</spring-cloud-netflix.version>
<spring-cloud-circuitbreaker.version>1.0.6.BUILD-SNAPSHOT</spring-cloud-circuitbreaker.version>
<spring-cloud-commons.version>2.2.9.RELEASE</spring-cloud-commons.version>
<spring-cloud-netflix.version>2.2.9.RELEASE</spring-cloud-netflix.version>
<spring-cloud-circuitbreaker.version>1.0.6.RELEASE</spring-cloud-circuitbreaker.version>
<embedded-redis.version>0.6</embedded-redis.version>
<blockhound.version>1.0.3.RELEASE</blockhound.version>
</properties>

2
spring-cloud-gateway-core/pom.xml

@ -7,7 +7,7 @@ @@ -7,7 +7,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-gateway</artifactId>
<version>2.2.9.BUILD-SNAPSHOT</version>
<version>2.2.11.BUILD-SNAPSHOT</version>
<relativePath>..</relativePath> <!-- lookup parent from repository -->
</parent>
<artifactId>spring-cloud-gateway-core</artifactId>

4
spring-cloud-gateway-dependencies/pom.xml

@ -6,12 +6,12 @@ @@ -6,12 +6,12 @@
<parent>
<artifactId>spring-cloud-dependencies-parent</artifactId>
<groupId>org.springframework.cloud</groupId>
<version>2.3.5.BUILD-SNAPSHOT</version>
<version>2.3.5.RELEASE</version>
<relativePath/>
</parent>
<artifactId>spring-cloud-gateway-dependencies</artifactId>
<version>2.2.9.BUILD-SNAPSHOT</version>
<version>2.2.11.BUILD-SNAPSHOT</version>
<packaging>pom</packaging>
<name>spring-cloud-gateway-dependencies</name>

2
spring-cloud-gateway-integration-tests/mvc-failure-analyzer/pom.xml

@ -16,7 +16,7 @@ @@ -16,7 +16,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-gateway-integration-tests</artifactId>
<version>2.2.9.BUILD-SNAPSHOT</version>
<version>2.2.11.BUILD-SNAPSHOT</version>
<relativePath>..</relativePath> <!-- lookup parent from repository -->
</parent>

2
spring-cloud-gateway-integration-tests/pom.xml

@ -16,7 +16,7 @@ @@ -16,7 +16,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-gateway</artifactId>
<version>2.2.9.BUILD-SNAPSHOT</version>
<version>2.2.11.BUILD-SNAPSHOT</version>
<relativePath>..</relativePath> <!-- lookup parent from repository -->
</parent>

2
spring-cloud-gateway-mvc/pom.xml

@ -11,7 +11,7 @@ @@ -11,7 +11,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-gateway</artifactId>
<version>2.2.9.BUILD-SNAPSHOT</version>
<version>2.2.11.BUILD-SNAPSHOT</version>
<relativePath>..</relativePath>
</parent>

2
spring-cloud-gateway-sample/pom.xml

@ -16,7 +16,7 @@ @@ -16,7 +16,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-gateway</artifactId>
<version>2.2.9.BUILD-SNAPSHOT</version>
<version>2.2.11.BUILD-SNAPSHOT</version>
<relativePath>..</relativePath> <!-- lookup parent from repository -->
</parent>

2
spring-cloud-gateway-server/pom.xml

@ -23,7 +23,7 @@ @@ -23,7 +23,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-gateway</artifactId>
<version>2.2.9.BUILD-SNAPSHOT</version>
<version>2.2.11.BUILD-SNAPSHOT</version>
<relativePath>..</relativePath> <!-- lookup parent from repository -->
</parent>
<artifactId>spring-cloud-gateway-server</artifactId>

6
spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java

@ -108,6 +108,7 @@ import org.springframework.cloud.gateway.filter.factory.rewrite.ModifyResponseBo @@ -108,6 +108,7 @@ import org.springframework.cloud.gateway.filter.factory.rewrite.ModifyResponseBo
import org.springframework.cloud.gateway.filter.headers.ForwardedHeadersFilter;
import org.springframework.cloud.gateway.filter.headers.HttpHeadersFilter;
import org.springframework.cloud.gateway.filter.headers.RemoveHopByHopHeadersFilter;
import org.springframework.cloud.gateway.filter.headers.TransferEncodingNormalizationHeadersFilter;
import org.springframework.cloud.gateway.filter.headers.XForwardedHeadersFilter;
import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.cloud.gateway.filter.ratelimit.PrincipalNameKeyResolver;
@ -298,6 +299,11 @@ public class GatewayAutoConfiguration { @@ -298,6 +299,11 @@ public class GatewayAutoConfiguration {
return new XForwardedHeadersFilter();
}
@Bean
public TransferEncodingNormalizationHeadersFilter transferEncodingNormalizationHeadersFilter() {
return new TransferEncodingNormalizationHeadersFilter();
}
// GlobalFilter beans
@Bean

51
spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/headers/TransferEncodingNormalizationHeadersFilter.java

@ -0,0 +1,51 @@ @@ -0,0 +1,51 @@
/*
* Copyright 2013-2021 the original author or authors.
*
* Licensed 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
*
* https://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.springframework.cloud.gateway.filter.headers;
import org.springframework.core.Ordered;
import org.springframework.http.HttpHeaders;
import org.springframework.web.server.ServerWebExchange;
/**
* See https://datatracker.ietf.org/doc/html/rfc7230#section-3.3.3 for details.
*/
public class TransferEncodingNormalizationHeadersFilter
implements HttpHeadersFilter, Ordered {
@Override
public int getOrder() {
return 1000;
}
@Override
public HttpHeaders filter(HttpHeaders input, ServerWebExchange exchange) {
String transferEncoding = input.getFirst(HttpHeaders.TRANSFER_ENCODING);
if (transferEncoding != null
&& "chunked".equalsIgnoreCase(transferEncoding.trim())
&& input.containsKey(HttpHeaders.CONTENT_LENGTH)) {
HttpHeaders filtered = new HttpHeaders();
// avoids read only if input is read only
filtered.addAll(input);
filtered.remove(HttpHeaders.CONTENT_LENGTH);
return filtered;
}
return input;
}
}

155
spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/headers/TransferEncodingNormalizationHeadersFilterIntegrationTests.java

@ -0,0 +1,155 @@ @@ -0,0 +1,155 @@
/*
* Copyright 2013-2020 the original author or authors.
*
* Licensed 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
*
* https://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.springframework.cloud.gateway.filter.headers;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.netflix.loadbalancer.Server;
import com.netflix.loadbalancer.ServerList;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.jupiter.api.Test;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.cloud.gateway.test.PermitAllSecurityConfiguration;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.cloud.netflix.ribbon.StaticServerList;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Primary;
import org.springframework.core.log.LogMessage;
import org.springframework.http.MediaType;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.util.StreamUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
@SpringBootTest(properties = {}, webEnvironment = RANDOM_PORT)
@ActiveProfiles("transferencoding")
public class TransferEncodingNormalizationHeadersFilterIntegrationTests {
private static final Log log = LogFactory
.getLog(TransferEncodingNormalizationHeadersFilterIntegrationTests.class);
@LocalServerPort
private int port;
@Test
void legitRequestShouldNotFail() throws Exception {
final ClassLoader classLoader = this.getClass().getClassLoader();
// Issue a crafted request with smuggling attempt
assert200With("Should Fail", StreamUtils.copyToByteArray(classLoader
.getResourceAsStream("transfer-encoding/invalid-request.bin")));
// Issue a legit request, which should not fail
assert200With("Should Not Fail", StreamUtils.copyToByteArray(
classLoader.getResourceAsStream("transfer-encoding/valid-request.bin")));
}
private void assert200With(String name, byte[] payload) throws Exception {
final String response = execute("localhost", port, payload);
log.info(LogMessage.format("Request to localhost:%d %s\n%s", port, name,
new String(payload)));
assertThat(response).isNotNull();
log.info(LogMessage.format("Response %s\n%s", name, response));
assertThat(response).matches("HTTP/1.\\d 200 OK");
}
private String execute(String target, int port, byte[] payload) throws IOException {
final Socket socket = new Socket(target, port);
final OutputStream out = socket.getOutputStream();
final BufferedReader in = new BufferedReader(
new InputStreamReader(socket.getInputStream()));
out.write(payload);
final String headResponse = in.readLine();
out.close();
in.close();
return headResponse;
}
@EnableAutoConfiguration
@SpringBootConfiguration
@Import(PermitAllSecurityConfiguration.class)
@RibbonClient(name = "xferenc", configuration = TestLoadBalancerConfig.class)
@RestController
public static class TestConfig {
@PostMapping(value = "/echo", produces = { MediaType.APPLICATION_JSON_VALUE })
public Message message(@RequestBody Message message) throws IOException {
return message;
}
@Bean
public RouteLocator routeLocator(RouteLocatorBuilder builder) {
return builder.routes().route("echo", r -> r.path("/route/echo")
.filters(f -> f.stripPrefix(1)).uri("lb://xferenc")).build();
}
}
public static class Message {
private String message;
public Message(@JsonProperty("message") String message) {
this.message = message;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
public static class TestLoadBalancerConfig {
@LocalServerPort
protected int port = 0;
@Bean
@Primary
public ServerList<Server> ribbonServerList() {
return new StaticServerList<>(new Server("localhost", port));
}
}
}

69
spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/headers/TransferEncodingNormalizationHeadersFilterTests.java

@ -0,0 +1,69 @@ @@ -0,0 +1,69 @@
/*
* Copyright 2013-2020 the original author or authors.
*
* Licensed 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
*
* https://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.springframework.cloud.gateway.filter.headers;
import org.junit.Test;
import org.springframework.http.HttpHeaders;
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
import org.springframework.mock.web.server.MockServerWebExchange;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Spencer Gibb
*/
public class TransferEncodingNormalizationHeadersFilterTests {
@Test
public void noTransferEncodingWithContentLength() {
MockServerHttpRequest.BaseBuilder<?> builder = MockServerHttpRequest
.post("http://localhost/post").header(HttpHeaders.CONTENT_LENGTH, "6");
HttpHeaders headers = testFilter(MockServerWebExchange.from(builder));
assertThat(headers).containsKey(HttpHeaders.CONTENT_LENGTH)
.doesNotContainKey(HttpHeaders.TRANSFER_ENCODING);
}
@Test
public void transferEncodingWithContentLength() {
MockServerHttpRequest.BaseBuilder<?> builder = MockServerHttpRequest
.post("http://localhost/post").header(HttpHeaders.CONTENT_LENGTH, "6")
.header(HttpHeaders.TRANSFER_ENCODING, "chunked");
HttpHeaders headers = testFilter(MockServerWebExchange.from(builder));
assertThat(headers).doesNotContainKey(HttpHeaders.CONTENT_LENGTH)
.containsKey(HttpHeaders.TRANSFER_ENCODING);
}
@Test
public void transferEncodingCaseInsensitiveWithContentLength() {
MockServerHttpRequest.BaseBuilder<?> builder = MockServerHttpRequest
.post("http://localhost/post").header(HttpHeaders.CONTENT_LENGTH, "6")
.header(HttpHeaders.TRANSFER_ENCODING, "Chunked ");
HttpHeaders headers = testFilter(MockServerWebExchange.from(builder));
assertThat(headers).doesNotContainKey(HttpHeaders.CONTENT_LENGTH)
.containsKey(HttpHeaders.TRANSFER_ENCODING);
}
private HttpHeaders testFilter(MockServerWebExchange exchange) {
TransferEncodingNormalizationHeadersFilter filter = new TransferEncodingNormalizationHeadersFilter();
return filter.filter(exchange.getRequest().getHeaders(), exchange);
}
}

13
spring-cloud-gateway-server/src/test/resources/transfer-encoding/invalid-request.bin

@ -0,0 +1,13 @@ @@ -0,0 +1,13 @@
POST /route/echo HTTP/1.0
Host: localhost:8080
Content-Length: 19
Transfer-encoding: Chunked
Content-Type: application/json
Connection: close
22
{"message":"3"}
GET /nonexistantpath123 HTTP/1.0
0

7
spring-cloud-gateway-server/src/test/resources/transfer-encoding/valid-request.bin

@ -0,0 +1,7 @@ @@ -0,0 +1,7 @@
POST /route/echo HTTP/1.1
Host: localhost:8080
Content-Type: application/json
Content-Length: 15
Connection: close
{"message":"3"}

2
spring-cloud-gateway-webflux/pom.xml

@ -11,7 +11,7 @@ @@ -11,7 +11,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-gateway</artifactId>
<version>2.2.9.BUILD-SNAPSHOT</version>
<version>2.2.11.BUILD-SNAPSHOT</version>
<relativePath>..</relativePath>
</parent>

2
spring-cloud-starter-gateway/pom.xml

@ -6,7 +6,7 @@ @@ -6,7 +6,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-gateway</artifactId>
<version>2.2.9.BUILD-SNAPSHOT</version>
<version>2.2.11.BUILD-SNAPSHOT</version>
<relativePath>..</relativePath> <!-- lookup parent from repository -->
</parent>
<artifactId>spring-cloud-starter-gateway</artifactId>

Loading…
Cancel
Save