Skip to content

Commit f1ccaaa

Browse files
committed
Changed the way request uris are generated to address #138. In the future, it would be ideal to get the full request uri from API Gateway in the event.
1 parent 6c90756 commit f1ccaaa

File tree

6 files changed

+124
-24
lines changed

6 files changed

+124
-24
lines changed

aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/SecurityUtils.java

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,11 @@
77

88
import java.io.File;
99
import java.io.IOException;
10+
import java.util.ArrayList;
11+
import java.util.HashSet;
1012
import java.util.List;
1113
import java.util.Locale;
14+
import java.util.Set;
1215

1316

1417
/**
@@ -18,6 +21,51 @@
1821
public final class SecurityUtils {
1922
private static Logger log = LoggerFactory.getLogger(SecurityUtils.class);
2023

24+
private static Set<String> SCHEMES = new HashSet<String>() {{
25+
add("http");
26+
add("https");
27+
add("HTTP");
28+
add("HTTPS");
29+
}};
30+
31+
private static Set<Integer> PORTS = new HashSet<Integer>() {{
32+
add(443);
33+
add(80);
34+
add(3000); // we allow port 3000 for SAM local
35+
}};
36+
37+
public static boolean isValidPort(String port) {
38+
if (port == null) {
39+
return false;
40+
}
41+
try {
42+
int intPort = Integer.parseInt(port);
43+
return PORTS.contains(intPort);
44+
} catch (NumberFormatException e) {
45+
log.error("Invalid port parameter: " + crlf(port));
46+
return false;
47+
}
48+
}
49+
50+
public static boolean isValidScheme(String scheme) {
51+
return SCHEMES.contains(scheme);
52+
}
53+
54+
public static boolean isValidHost(String host, String apiId, String region) {
55+
if (host == null) {
56+
return false;
57+
}
58+
if (host.endsWith(".amazonaws.com")) {
59+
String defaultHost = new StringBuilder().append(apiId)
60+
.append(".execute-api.")
61+
.append(region)
62+
.append(".amazonaws.com").toString();
63+
return host.equals(defaultHost);
64+
} else {
65+
return LambdaContainerHandler.getContainerConfig().getCustomDomainNames().contains(host);
66+
}
67+
}
68+
2169
/**
2270
* Replaces CRLF characters in a string with empty string ("").
2371
* @param s The string to be cleaned

aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletRequest.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@ public abstract class AwsHttpServletRequest implements HttpServletRequest {
6464
// We need this to pickup the protocol from the CloudFront header since Lambda doesn't receive this
6565
// information from anywhere else
6666
static final String CF_PROTOCOL_HEADER_NAME = "CloudFront-Forwarded-Proto";
67+
static final String PROTOCOL_HEADER_NAME = "X-Forwarded-Proto";
68+
static final String HOST_HEADER_NAME = "Host";
69+
static final String PORT_HEADER_NAME = "X-Forwarded-Port";
6770

6871

6972
//-------------------------------------------------------------

aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequest.java

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -501,22 +501,44 @@ public String getProtocol() {
501501

502502
@Override
503503
public String getScheme() {
504-
String headerValue = getHeaderCaseInsensitive(CF_PROTOCOL_HEADER_NAME);
505-
if (headerValue == null) {
506-
return "https";
504+
String cfScheme = getHeaderCaseInsensitive(CF_PROTOCOL_HEADER_NAME);
505+
if (cfScheme != null && SecurityUtils.isValidScheme(cfScheme)) {
506+
return cfScheme;
507+
}
508+
String gwScheme = getHeaderCaseInsensitive(PROTOCOL_HEADER_NAME);
509+
if (gwScheme != null && SecurityUtils.isValidScheme(gwScheme)) {
510+
return gwScheme;
507511
}
508-
return headerValue;
512+
// https is our default scheme
513+
return "https";
509514
}
510515

511-
512516
@Override
513517
public String getServerName() {
514-
String name = getHeaderCaseInsensitive(HttpHeaders.HOST);
518+
String region = System.getenv("AWS_REGION");
519+
if (region == null) {
520+
// this is not a critical failure, we just put a static region in the URI
521+
region = "us-east-1";
522+
}
515523

516-
if (name == null || name.length() == 0) {
517-
name = "lambda.amazonaws.com";
524+
String hostHeader = getHeaderCaseInsensitive(HOST_HEADER_NAME);
525+
if (hostHeader != null && SecurityUtils.isValidHost(hostHeader, request.getRequestContext().getApiId(), region)) {
526+
return hostHeader;
527+
}
528+
529+
return new StringBuilder().append(request.getRequestContext().getApiId())
530+
.append(".execute-api.")
531+
.append(region)
532+
.append(".amazonaws.com").toString();
533+
}
534+
535+
public int getServerPort() {
536+
String port = getHeaderCaseInsensitive(PORT_HEADER_NAME);
537+
if (SecurityUtils.isValidPort(port)) {
538+
return Integer.parseInt(port);
539+
} else {
540+
return 443; // default port
518541
}
519-
return name;
520542
}
521543

522544

aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/ContainerConfig.java

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,11 @@ public static ContainerConfig defaultConfig() {
3535
private boolean consolidateSetCookieHeaders;
3636
private boolean useStageAsServletContext;
3737
private List<String> validFilePaths;
38+
private List<String> customDomainNames;
3839

3940
public ContainerConfig() {
4041
validFilePaths = new ArrayList<>();
42+
customDomainNames = new ArrayList<>();
4143
}
4244

4345

@@ -168,4 +170,31 @@ public void setValidFilePaths(List<String> validFilePaths) {
168170
public void addValidFilePath(String filePath) {
169171
validFilePaths.add(filePath);
170172
}
173+
174+
175+
/**
176+
* Adds a new custom domain name to the list of allowed domains
177+
* @param name The new custom domain name, excluding the scheme ("https") and port
178+
*/
179+
public void addCustomDomain(String name) {
180+
customDomainNames.add(name);
181+
}
182+
183+
184+
/**
185+
* Returns the list of custom domain names enabled for the application
186+
* @return The configured custom domain names
187+
*/
188+
public List<String> getCustomDomainNames() {
189+
return customDomainNames;
190+
}
191+
192+
193+
/**
194+
* Enables localhost custom domain name for testing. This setting should be used only in local
195+
* with SAM local
196+
*/
197+
public void enableLocalhost() {
198+
customDomainNames.add("localhost");
199+
}
171200
}

aws-serverless-java-container-jersey/src/main/java/com/amazonaws/serverless/proxy/jersey/JerseyHandlerFilter.java

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -165,23 +165,19 @@ private ContainerRequest servletRequestToContainerRequest(ServletRequest request
165165
return requestContext;
166166
}
167167

168+
@SuppressFBWarnings("SERVLET_SERVER_NAME")
168169
private URI getBaseUri(ServletRequest request, String basePath) {
169-
ApiGatewayRequestContext apiGatewayCtx = (ApiGatewayRequestContext) request.getAttribute(API_GATEWAY_CONTEXT_PROPERTY);
170-
String region = System.getenv("AWS_REGION");
171-
if (region == null) {
172-
// this is not a critical failure, we just put a static region in the URI
173-
region = "us-east-1";
170+
String finalBasePath = basePath;
171+
if (!finalBasePath.startsWith("/")) {
172+
finalBasePath = "/" + finalBasePath;
174173
}
175-
StringBuilder uriBuilder = new StringBuilder();
176-
uriBuilder.append("https://") // we assume it's always https
177-
.append(apiGatewayCtx.getApiId())
178-
.append(".execute-api.")
179-
.append(region)
180-
.append(".amazonaws.com")
181-
.append("/");
182-
183-
184-
return UriBuilder.fromUri(uriBuilder.toString()).build();
174+
String uriString = new StringBuilder().append(request.getScheme())
175+
.append("://")
176+
.append(request.getServerName())
177+
.append(":")
178+
.append(request.getServerPort())
179+
.append(finalBasePath).toString();
180+
return UriBuilder.fromUri(uriString).build();
185181
}
186182

187183
//-------------------------------------------------------------

aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringAwsProxyTest.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.amazonaws.serverless.proxy.spring;
22

3+
import com.amazonaws.serverless.proxy.internal.LambdaContainerHandler;
34
import com.amazonaws.serverless.proxy.model.AwsProxyRequest;
45
import com.amazonaws.serverless.proxy.model.AwsProxyResponse;
56
import com.amazonaws.serverless.proxy.internal.servlet.AwsServletContext;
@@ -335,6 +336,7 @@ public void contextPath_generateLink_returnsCorrectPath() {
335336
.serverName("api.myserver.com")
336337
.stage("prod")
337338
.build();
339+
LambdaContainerHandler.getContainerConfig().addCustomDomain("api.myserver.com");
338340
SpringLambdaContainerHandler.getContainerConfig().setUseStageAsServletContext(true);
339341

340342
AwsProxyResponse output = handler.proxy(request, lambdaContext);

0 commit comments

Comments
 (0)