diff --git a/aws-serverless-java-container-core/pom.xml b/aws-serverless-java-container-core/pom.xml
index 10e06b180..380013d32 100644
--- a/aws-serverless-java-container-core/pom.xml
+++ b/aws-serverless-java-container-core/pom.xml
@@ -27,6 +27,13 @@
1.2.3
+
+ org.jetbrains
+ annotations
+ 24.0.1
+ provided
+
+
jakarta.servlet
jakarta.servlet-api
diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/AwsVPCLatticeV2SecurityContextWriter.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/AwsVPCLatticeV2SecurityContextWriter.java
new file mode 100644
index 000000000..0f3d75e27
--- /dev/null
+++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/AwsVPCLatticeV2SecurityContextWriter.java
@@ -0,0 +1,13 @@
+package com.amazonaws.serverless.proxy;
+
+import com.amazonaws.serverless.proxy.internal.jaxrs.AwsVpcLatticeV2SecurityContext;
+import com.amazonaws.serverless.proxy.model.VPCLatticeV2RequestEvent;
+import com.amazonaws.services.lambda.runtime.Context;
+import jakarta.ws.rs.core.SecurityContext;
+
+public class AwsVPCLatticeV2SecurityContextWriter implements SecurityContextWriter{
+ @Override
+ public SecurityContext writeSecurityContext(VPCLatticeV2RequestEvent event, Context lambdaContext) {
+ return new AwsVpcLatticeV2SecurityContext(lambdaContext, event);
+ }
+}
diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/RequestReader.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/RequestReader.java
index d8293d649..a2fb32b80 100644
--- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/RequestReader.java
+++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/RequestReader.java
@@ -40,6 +40,11 @@ public abstract class RequestReader {
*/
public static final String API_GATEWAY_CONTEXT_PROPERTY = "com.amazonaws.apigateway.request.context";
+ /**
+ * The key for the VPC Lattice V2 context property in the PropertiesDelegate object
+ */
+ public static final String VPC_LATTICE_V2_CONTEXT_PROPERTY = "com.amazonaws.vpclattice.request.context";
+
/**
* The key for the API Gateway stage variables property in the PropertiesDelegate object
*/
@@ -55,6 +60,11 @@ public abstract class RequestReader {
*/
public static final String API_GATEWAY_EVENT_PROPERTY = "com.amazonaws.apigateway.request";
+ /**
+ * The key to store the entire VPC Lattice V2 event
+ */
+ public static final String VPC_LATTICE_V2_EVENT_PROPERTY = "com.amazonaws.vpclattice.request";
+
/**
* The key for the AWS Lambda context property in the PropertiesDelegate object
*/
diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/jaxrs/AwsVpcLatticeV2SecurityContext.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/jaxrs/AwsVpcLatticeV2SecurityContext.java
new file mode 100644
index 000000000..71a58d90d
--- /dev/null
+++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/jaxrs/AwsVpcLatticeV2SecurityContext.java
@@ -0,0 +1,59 @@
+package com.amazonaws.serverless.proxy.internal.jaxrs;
+
+import com.amazonaws.serverless.proxy.model.VPCLatticeV2RequestEvent;
+import com.amazonaws.services.lambda.runtime.Context;
+import jakarta.ws.rs.core.SecurityContext;
+
+import java.security.Principal;
+import java.util.Objects;
+
+/**
+ * default implementation of the SecurityContext
object. This class supports 1 VPC Lattice authentication type:
+ * AWS_IAM.
+ */
+public class AwsVpcLatticeV2SecurityContext implements SecurityContext {
+
+ static final String AUTH_SCHEME_AWS_IAM = "AWS_IAM";
+
+
+ private final VPCLatticeV2RequestEvent event;
+
+ public AwsVpcLatticeV2SecurityContext(Context lambdaContext, VPCLatticeV2RequestEvent event) {
+ this.event = event;
+ }
+
+ //-------------------------------------------------------------
+ // Implementation - SecurityContext
+ //-------------------------------------------------------------
+ @Override
+ public Principal getUserPrincipal() {
+ if (Objects.equals(getAuthenticationScheme(), AUTH_SCHEME_AWS_IAM)) {
+ return () -> getEvent().getRequestContext().getIdentity().getPrincipal();
+ }
+ return null;
+ }
+
+ private VPCLatticeV2RequestEvent getEvent() {
+ return event;
+ }
+
+
+ @Override
+ public boolean isUserInRole(String role) {
+ return role.equals(event.getRequestContext().getIdentity().getPrincipal());
+ }
+
+ @Override
+ public boolean isSecure() {
+ return getAuthenticationScheme() != null;
+ }
+
+ @Override
+ public String getAuthenticationScheme() {
+ if (Objects.equals(getEvent().getRequestContext().getIdentity().getType(), AUTH_SCHEME_AWS_IAM)) {
+ return AUTH_SCHEME_AWS_IAM;
+ } else {
+ return null;
+ }
+ }
+}
diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpApiV2ProxyHttpServletRequest.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpApiV2ProxyHttpServletRequest.java
index 6fdb31f08..6d2486803 100644
--- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpApiV2ProxyHttpServletRequest.java
+++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpApiV2ProxyHttpServletRequest.java
@@ -47,7 +47,6 @@ public class AwsHttpApiV2ProxyHttpServletRequest extends AwsHttpServletRequest {
private MultiValuedTreeMap queryString;
private Headers headers;
private ContainerConfig config;
- private SecurityContext securityContext;
private AwsAsyncContext asyncContext;
/**
@@ -57,10 +56,9 @@ public class AwsHttpApiV2ProxyHttpServletRequest extends AwsHttpServletRequest {
* @param lambdaContext The Lambda function context. This object is used for utility methods such as log
*/
public AwsHttpApiV2ProxyHttpServletRequest(HttpApiV2ProxyRequest req, Context lambdaContext, SecurityContext sc, ContainerConfig cfg) {
- super(lambdaContext);
+ super(lambdaContext, sc);
request = req;
config = cfg;
- securityContext = sc;
queryString = parseRawQueryString(request.getRawQueryString());
headers = headersMapToMultiValue(request.getHeaders());
}
@@ -69,12 +67,6 @@ public HttpApiV2ProxyRequest getRequest() {
return request;
}
- @Override
- public String getAuthType() {
- // TODO
- return null;
- }
-
@Override
public Cookie[] getCookies() {
Cookie[] rhc;
@@ -108,56 +100,27 @@ public Cookie[] getCookies() {
@Override
public long getDateHeader(String s) {
- if (headers == null) {
- return -1L;
- }
- String dateString = headers.getFirst(s);
- if (dateString == null) {
- return -1L;
- }
- try {
- return Instant.from(ZonedDateTime.parse(dateString, dateFormatter)).toEpochMilli();
- } catch (DateTimeParseException e) {
- log.warn("Invalid date header in request: " + SecurityUtils.crlf(dateString));
- return -1L;
- }
+ return getDateHeader(s, headers);
}
@Override
public String getHeader(String s) {
- if (headers == null) {
- return null;
- }
- return headers.getFirst(s);
+ return getHeader(s, headers);
}
@Override
public Enumeration getHeaders(String s) {
- if (headers == null || !headers.containsKey(s)) {
- return Collections.emptyEnumeration();
- }
- return Collections.enumeration(headers.get(s));
+ return getHeaders(s, headers);
}
@Override
public Enumeration getHeaderNames() {
- if (headers == null) {
- return Collections.emptyEnumeration();
- }
- return Collections.enumeration(headers.keySet());
+ return getHeaderNames(headers);
}
@Override
public int getIntHeader(String s) {
- if (headers == null) {
- return -1;
- }
- String headerValue = headers.getFirst(s);
- if (headerValue == null || "".equals(headerValue)) {
- return -1;
- }
-
- return Integer.parseInt(headerValue);
+ return getIntHeader(s, headers);
}
@Override
@@ -187,28 +150,6 @@ public String getQueryString() {
return request.getRawQueryString();
}
- @Override
- public String getRemoteUser() {
- if (securityContext == null || securityContext.getUserPrincipal() == null) {
- return null;
- }
- return securityContext.getUserPrincipal().getName();
- }
-
- @Override
- public boolean isUserInRole(String s) {
- // TODO: Not supported
- return false;
- }
-
- @Override
- public Principal getUserPrincipal() {
- if (securityContext == null) {
- return null;
- }
- return securityContext.getUserPrincipal();
- }
-
@Override
public String getRequestURI() {
return cleanUri(getContextPath()) + cleanUri(request.getRawPath());
@@ -219,27 +160,6 @@ public StringBuffer getRequestURL() {
return generateRequestURL(request.getRawPath());
}
-
- @Override
- public boolean authenticate(HttpServletResponse httpServletResponse) throws IOException, ServletException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void login(String s, String s1) throws ServletException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void logout() throws ServletException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public T upgrade(Class aClass) throws IOException, ServletException {
- throw new UnsupportedOperationException();
- }
-
@Override
public String getCharacterEncoding() {
if (headers == null) {
@@ -250,30 +170,17 @@ public String getCharacterEncoding() {
@Override
public void setCharacterEncoding(String s) throws UnsupportedEncodingException {
- if (headers == null || !headers.containsKey(HttpHeaders.CONTENT_TYPE)) {
- log.debug("Called set character encoding to " + SecurityUtils.crlf(s) + " on a request without a content type. Character encoding will not be set");
- return;
- }
- String currentContentType = headers.getFirst(HttpHeaders.CONTENT_TYPE);
- headers.putSingle(HttpHeaders.CONTENT_TYPE, appendCharacterEncoding(currentContentType, s));
+ setCharacterEncoding(s, headers);
}
@Override
public int getContentLength() {
- String headerValue = headers.getFirst(HttpHeaders.CONTENT_LENGTH);
- if (headerValue == null) {
- return -1;
- }
- return Integer.parseInt(headerValue);
+ return getContentLength(headers);
}
@Override
public long getContentLengthLong() {
- String headerValue = headers.getFirst(HttpHeaders.CONTENT_LENGTH);
- if (headerValue == null) {
- return -1;
- }
- return Long.parseLong(headerValue);
+ return getContentLengthLong(headers);
}
@Override
@@ -286,17 +193,7 @@ public String getContentType() {
@Override
public String getParameter(String s) {
- String queryStringParameter = getFirstQueryParamValue(queryString, s, config.isQueryStringCaseSensitive());
- if (queryStringParameter != null) {
- return queryStringParameter;
- }
-
- String[] bodyParams = getFormBodyParameterCaseInsensitive(s);
- if (bodyParams.length == 0) {
- return null;
- } else {
- return bodyParams[0];
- }
+ return getParameter(queryString, s, config.isQueryStringCaseSensitive());
}
@Override
@@ -315,7 +212,7 @@ public String[] getParameterValues(String s) {
values.addAll(Arrays.asList(getFormBodyParameterCaseInsensitive(s)));
- if (values.size() == 0) {
+ if (values.isEmpty()) {
return null;
} else {
return values.toArray(new String[0]);
@@ -409,16 +306,6 @@ public Enumeration getLocales() {
return Collections.enumeration(locales);
}
- @Override
- public boolean isSecure() {
- return securityContext.isSecure();
- }
-
- @Override
- public RequestDispatcher getRequestDispatcher(String s) {
- return getServletContext().getRequestDispatcher(s);
- }
-
@Override
public int getRemotePort() {
return 0;
@@ -456,6 +343,8 @@ public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse se
return asyncContext;
}
+
+
@Override
public AsyncContext getAsyncContext() {
if (asyncContext == null) {
@@ -475,11 +364,6 @@ public String getProtocolRequestId() {
return "";
}
- @Override
- public ServletConnection getServletConnection() {
- return null;
- }
-
private MultiValuedTreeMap parseRawQueryString(String qs) {
if (qs == null || "".equals(qs.trim())) {
return new MultiValuedTreeMap<>();
@@ -505,7 +389,7 @@ private MultiValuedTreeMap parseRawQueryString(String qs) {
return qsMap;
}
- private Headers headersMapToMultiValue(Map headers) {
+ protected static Headers headersMapToMultiValue(Map headers) {
if (headers == null || headers.size() == 0) {
return new Headers();
}
diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletRequest.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletRequest.java
index b76fd216e..05bf1a792 100644
--- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletRequest.java
+++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletRequest.java
@@ -22,6 +22,8 @@
import com.amazonaws.serverless.proxy.model.MultiValuedTreeMap;
import com.amazonaws.services.lambda.runtime.Context;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import jakarta.ws.rs.core.HttpHeaders;
+import jakarta.ws.rs.core.SecurityContext;
import org.apache.commons.fileupload2.core.DiskFileItem;
import org.apache.commons.fileupload2.core.FileItem;
import org.apache.commons.fileupload2.core.FileUploadException;
@@ -43,7 +45,11 @@
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.Charset;
+import java.security.Principal;
+import java.time.Instant;
+import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -74,6 +80,8 @@ public abstract class AwsHttpServletRequest implements HttpServletRequest {
// information from anywhere else
static final String CF_PROTOCOL_HEADER_NAME = "CloudFront-Forwarded-Proto";
static final String PROTOCOL_HEADER_NAME = "X-Forwarded-Proto";
+ static final String CLIENT_IP_HEADER_NAME = "X-Forwarded-For";
+
static final String HOST_HEADER_NAME = "Host";
static final String PORT_HEADER_NAME = "X-Forwarded-Port";
static final String CLIENT_IP_HEADER = "X-Forwarded-For";
@@ -94,6 +102,7 @@ public abstract class AwsHttpServletRequest implements HttpServletRequest {
protected AwsHttpServletResponse response;
protected AwsLambdaServletContainerHandler containerHandler;
protected ServletInputStream requestInputStream;
+ private final SecurityContext securityContext;
private static Logger log = LoggerFactory.getLogger(AwsHttpServletRequest.class);
@@ -107,9 +116,11 @@ public abstract class AwsHttpServletRequest implements HttpServletRequest {
* Protected constructors for implementing classes. This should be called first with the context received from
* AWS Lambda
* @param lambdaContext The Lambda function context. This object is used for utility methods such as log
+ * @param securityContext The security context
*/
- protected AwsHttpServletRequest(Context lambdaContext) {
+ protected AwsHttpServletRequest(Context lambdaContext, SecurityContext securityContext) {
this.lambdaContext = lambdaContext;
+ this.securityContext = securityContext;
attributes = new HashMap<>();
setAttribute(DISPATCHER_TYPE_ATTRIBUTE, DispatcherType.REQUEST);
}
@@ -269,14 +280,74 @@ public DispatcherType getDispatcherType() {
return DispatcherType.REQUEST;
}
+ @Override
+ public RequestDispatcher getRequestDispatcher(String path) {
+ return getServletContext().getRequestDispatcher(path);
+ }
+
@Override
public String getServletPath() {
// we always work on the root path
return "";
}
+ @Override
+ public ServletConnection getServletConnection() {
+ return null;
+ }
- //-------------------------------------------------------------
+ @Override
+ public String getAuthType() {
+ return securityContext.getAuthenticationScheme();
+ }
+
+ @Override
+ public boolean isSecure() {
+ return securityContext.isSecure();
+ }
+
+ @Override
+ public Principal getUserPrincipal() {
+ if (securityContext == null) {
+ return null;
+ }
+ return securityContext.getUserPrincipal();
+ }
+
+ @Override
+ public String getRemoteUser() {
+ if (getUserPrincipal() == null) {
+ return null;
+ }
+ return getUserPrincipal().getName();
+ }
+
+ @Override
+ public boolean isUserInRole(String role) {
+ return securityContext.isUserInRole(role);
+ }
+
+ @Override
+ public boolean authenticate(HttpServletResponse httpServletResponse) throws IOException, ServletException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void login(String username, String password) throws ServletException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void logout() throws ServletException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public T upgrade(Class handlerClass) throws IOException, ServletException {
+ throw new UnsupportedOperationException();
+ }
+
+//-------------------------------------------------------------
// Methods - Getter/Setter
//-------------------------------------------------------------
@@ -603,6 +674,18 @@ protected List getQueryParamValuesAsList(MultiValuedTreeMap qs, String s, boolean isCaseSensitive) {
+ List values = new ArrayList<>(Arrays.asList(getQueryParamValues(qs, s, isCaseSensitive)));
+
+ values.addAll(Arrays.asList(getFormBodyParameterCaseInsensitive(s)));
+
+ if (values.isEmpty()) {
+ return null;
+ } else {
+ return values.toArray(new String[0]);
+ }
+ }
+
protected Map generateParameterMap(MultiValuedTreeMap qs, ContainerConfig config) {
Map output;
@@ -825,6 +908,99 @@ static String decodeValueIfEncoded(String value) {
}
+
+ /**
+ * These are helper methods meant to reduce duplicated code in servletRequest classes.
+ */
+
+ protected long getDateHeader(String s, Headers headers) {
+ if (headers == null) {
+ return -1L;
+ }
+ String dateString = headers.getFirst(s);
+ if (dateString == null) {
+ return -1L;
+ }
+ try {
+ return Instant.from(ZonedDateTime.parse(dateString, dateFormatter)).toEpochMilli();
+ } catch (DateTimeParseException e) {
+ log.warn("Invalid date header in request: " + SecurityUtils.crlf(dateString));
+ return -1L;
+ }
+ }
+
+ protected String getHeader(String s, Headers headers) {
+ if (headers == null) {
+ return null;
+ }
+ return headers.getFirst(s);
+ }
+
+ protected Enumeration getHeaders(String s, Headers headers) {
+ if (headers == null || !headers.containsKey(s)) {
+ return Collections.emptyEnumeration();
+ }
+ return Collections.enumeration(headers.get(s));
+ }
+
+ protected Enumeration getHeaderNames(Headers headers) {
+ if (headers == null) {
+ return Collections.emptyEnumeration();
+ }
+ return Collections.enumeration(headers.keySet());
+ }
+
+ protected int getIntHeader(String s, Headers headers) {
+ if (headers == null) {
+ return -1;
+ }
+ String headerValue = headers.getFirst(s);
+ if (headerValue == null || "".equals(headerValue)) {
+ return -1;
+ }
+
+ return Integer.parseInt(headerValue);
+ }
+
+ protected void setCharacterEncoding(String s, Headers headers) throws UnsupportedEncodingException {
+ if (headers == null || !headers.containsKey(HttpHeaders.CONTENT_TYPE)) {
+ log.debug("Called set character encoding to " + SecurityUtils.crlf(s) + " on a request without a content type. Character encoding will not be set");
+ return;
+ }
+ String currentContentType = headers.getFirst(HttpHeaders.CONTENT_TYPE);
+ headers.putSingle(HttpHeaders.CONTENT_TYPE, appendCharacterEncoding(currentContentType, s));
+ }
+
+ protected int getContentLength(Headers headers) {
+ String headerValue = headers.getFirst(HttpHeaders.CONTENT_LENGTH);
+ if (headerValue == null) {
+ return -1;
+ }
+ return Integer.parseInt(headerValue);
+ }
+
+ protected long getContentLengthLong(Headers headers) {
+ String headerValue = headers.getFirst(HttpHeaders.CONTENT_LENGTH);
+ if (headerValue == null) {
+ return -1;
+ }
+ return Long.parseLong(headerValue);
+ }
+
+ protected String getParameter(MultiValuedTreeMap qs, String s, boolean isCaseSensitive) {
+ String queryStringParameter = getFirstQueryParamValue(qs, s, isCaseSensitive);
+ if (queryStringParameter != null) {
+ return queryStringParameter;
+ }
+
+ String[] bodyParams = getFormBodyParameterCaseInsensitive(s);
+ if (bodyParams.length == 0) {
+ return null;
+ } else {
+ return bodyParams[0];
+ }
+ }
+
/**
* Class that represents a header value.
*/
diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequest.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequest.java
index fe514e65d..973ed9fc6 100644
--- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequest.java
+++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequest.java
@@ -55,7 +55,6 @@ public class AwsProxyHttpServletRequest extends AwsHttpServletRequest {
//-------------------------------------------------------------
private AwsProxyRequest request;
- private SecurityContext securityContext;
private AwsAsyncContext asyncContext;
private static Logger log = LoggerFactory.getLogger(AwsProxyHttpServletRequest.class);
private ContainerConfig config;
@@ -71,9 +70,8 @@ public AwsProxyHttpServletRequest(AwsProxyRequest awsProxyRequest, Context lambd
public AwsProxyHttpServletRequest(AwsProxyRequest awsProxyRequest, Context lambdaContext, SecurityContext awsSecurityContext, ContainerConfig config) {
- super(lambdaContext);
+ super(lambdaContext, awsSecurityContext);
this.request = awsProxyRequest;
- this.securityContext = awsSecurityContext;
this.config = config;
}
@@ -85,13 +83,6 @@ public AwsProxyRequest getAwsProxyRequest() {
// Implementation - HttpServletRequest
//-------------------------------------------------------------
-
- @Override
- public String getAuthType() {
- return securityContext.getAuthenticationScheme();
- }
-
-
@Override
public Cookie[] getCookies() {
if (request.getMultiValueHeaders() == null) {
@@ -107,22 +98,9 @@ public Cookie[] getCookies() {
@Override
public long getDateHeader(String s) {
- if (request.getMultiValueHeaders() == null) {
- return -1L;
- }
- String dateString = request.getMultiValueHeaders().getFirst(s);
- if (dateString == null) {
- return -1L;
- }
- try {
- return Instant.from(ZonedDateTime.parse(dateString, dateFormatter)).toEpochMilli();
- } catch (DateTimeParseException e) {
- log.warn("Invalid date header in request" + SecurityUtils.crlf(dateString));
- return -1L;
- }
+ return getDateHeader(s, request.getMultiValueHeaders());
}
-
@Override
public String getHeader(String s) {
List values = getHeaderValues(s);
@@ -135,33 +113,19 @@ public String getHeader(String s) {
@Override
public Enumeration getHeaders(String s) {
- if (request.getMultiValueHeaders() == null || request.getMultiValueHeaders().get(s) == null) {
- return Collections.emptyEnumeration();
- }
- return Collections.enumeration(request.getMultiValueHeaders().get(s));
+ return getHeaders(s, request.getMultiValueHeaders());
}
@Override
public Enumeration getHeaderNames() {
- if (request.getMultiValueHeaders() == null) {
- return Collections.emptyEnumeration();
- }
- return Collections.enumeration(request.getMultiValueHeaders().keySet());
+ return getHeaderNames(request.getMultiValueHeaders());
}
@Override
public int getIntHeader(String s) {
- if (request.getMultiValueHeaders() == null) {
- return -1;
- }
- String headerValue = request.getMultiValueHeaders().getFirst(s);
- if (headerValue == null) {
- return -1;
- }
-
- return Integer.parseInt(headerValue);
+ return getIntHeader(s, request.getMultiValueHeaders());
}
@@ -205,26 +169,6 @@ public String getQueryString() {
}
}
-
- @Override
- public String getRemoteUser() {
- return securityContext.getUserPrincipal().getName();
- }
-
-
- @Override
- public boolean isUserInRole(String s) {
- // TODO: Not supported?
- return false;
- }
-
-
- @Override
- public Principal getUserPrincipal() {
- return securityContext.getUserPrincipal();
- }
-
-
@Override
public String getRequestURI() {
return cleanUri(getContextPath()) + cleanUri(request.getPath());
@@ -236,33 +180,6 @@ public StringBuffer getRequestURL() {
return generateRequestURL(request.getPath());
}
-
- @Override
- public boolean authenticate(HttpServletResponse httpServletResponse)
- throws IOException, ServletException {
- throw new UnsupportedOperationException();
- }
-
-
- @Override
- public void login(String s, String s1)
- throws ServletException {
- throw new UnsupportedOperationException();
- }
-
-
- @Override
- public void logout()
- throws ServletException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public T upgrade(Class aClass)
- throws IOException, ServletException {
- throw new UnsupportedOperationException();
- }
-
//-------------------------------------------------------------
// Implementation - ServletRequest
//-------------------------------------------------------------
@@ -295,21 +212,13 @@ public void setCharacterEncoding(String s)
@Override
public int getContentLength() {
- String headerValue = request.getMultiValueHeaders().getFirst(HttpHeaders.CONTENT_LENGTH);
- if (headerValue == null) {
- return -1;
- }
- return Integer.parseInt(headerValue);
+ return getContentLength(request.getMultiValueHeaders());
}
@Override
public long getContentLengthLong() {
- String headerValue = request.getMultiValueHeaders().getFirst(HttpHeaders.CONTENT_LENGTH);
- if (headerValue == null) {
- return -1;
- }
- return Long.parseLong(headerValue);
+ return getContentLengthLong(request.getMultiValueHeaders());
}
@@ -325,20 +234,9 @@ public String getContentType() {
@Override
public String getParameter(String s) {
- String queryStringParameter = getFirstQueryParamValue(request.getMultiValueQueryStringParameters(), s, config.isQueryStringCaseSensitive());
- if (queryStringParameter != null) {
- return queryStringParameter;
- }
-
- String[] bodyParams = getFormBodyParameterCaseInsensitive(s);
- if (bodyParams.length == 0) {
- return null;
- } else {
- return bodyParams[0];
- }
+ return getParameter(request.getMultiValueQueryStringParameters(), s, config.isQueryStringCaseSensitive());
}
-
@Override
public Enumeration getParameterNames() {
Set formParameterNames = getFormUrlEncodedParametersMap().keySet();
@@ -473,18 +371,6 @@ public Enumeration getLocales() {
return Collections.enumeration(locales);
}
- @Override
- public boolean isSecure() {
- return securityContext.isSecure();
- }
-
-
- @Override
- public RequestDispatcher getRequestDispatcher(String s) {
- return getServletContext().getRequestDispatcher(s);
- }
-
-
@Override
public int getRemotePort() {
if (request.getRequestSource().equals(RequestSource.ALB)) {
@@ -555,11 +441,6 @@ public String getProtocolRequestId() {
return "";
}
- @Override
- public ServletConnection getServletConnection() {
- return null;
- }
-
//-------------------------------------------------------------
// Methods - Private
//-------------------------------------------------------------
diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsVpcLatticeV2HttpServletRequest.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsVpcLatticeV2HttpServletRequest.java
new file mode 100644
index 000000000..638132ab6
--- /dev/null
+++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsVpcLatticeV2HttpServletRequest.java
@@ -0,0 +1,343 @@
+package com.amazonaws.serverless.proxy.internal.servlet;
+
+import com.amazonaws.serverless.proxy.internal.LambdaContainerHandler;
+import com.amazonaws.serverless.proxy.internal.SecurityUtils;
+import com.amazonaws.serverless.proxy.model.*;
+import com.amazonaws.services.lambda.runtime.Context;
+import jakarta.servlet.*;
+import jakarta.servlet.http.Cookie;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.servlet.http.HttpUpgradeHandler;
+import jakarta.ws.rs.core.HttpHeaders;
+import jakarta.ws.rs.core.SecurityContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.StringReader;
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.security.Principal;
+import java.util.*;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * Implementation of the HttpServletRequest
interface that supports VPCLatticeV2RequestEvent
object.
+ * This object is initialized with an VPCLatticeV2RequestEvent
event and a SecurityContext
generated
+ * by an implementation of the SecurityContextWriter
.
+ */
+public class AwsVpcLatticeV2HttpServletRequest extends AwsHttpServletRequest {
+
+ //-------------------------------------------------------------
+ // Variables - Private
+ //-------------------------------------------------------------
+
+
+ private final VPCLatticeV2RequestEvent request;
+ private final MultiValuedTreeMap queryString;
+ private final Headers headers;
+ private AwsAsyncContext asyncContext;
+ private final Context lambdaContext;
+ private static final Logger log = LoggerFactory.getLogger(AwsVpcLatticeV2HttpServletRequest.class);
+ private final ContainerConfig config;
+
+ //-------------------------------------------------------------
+ // Constructors
+ //-------------------------------------------------------------
+
+
+ public AwsVpcLatticeV2HttpServletRequest(VPCLatticeV2RequestEvent vpcLatticeV2Request, Context lambdaContext, SecurityContext awsSecurityContext) {
+ this(vpcLatticeV2Request, lambdaContext, awsSecurityContext, LambdaContainerHandler.getContainerConfig());
+ }
+
+
+ public AwsVpcLatticeV2HttpServletRequest(VPCLatticeV2RequestEvent vpcLatticeV2Request, Context lambdaContext, SecurityContext awsSecurityContext, ContainerConfig config) {
+ super(lambdaContext, awsSecurityContext);
+ this.request = vpcLatticeV2Request;
+ this.lambdaContext = lambdaContext;
+ this.config = config;
+ headers = request.getHeaders();
+ queryString = queryStringToMultiValue(request.getQueryStringParameters());
+ }
+
+ @Override
+ public Cookie[] getCookies() {
+ if (headers == null || !headers.containsKey(HttpHeaders.COOKIE)) {
+ return new Cookie[0];
+ } else {
+ return parseCookieHeaderValue(headers.getFirst(HttpHeaders.COOKIE));
+ }
+ }
+
+ @Override
+ public long getDateHeader(String s) {
+ return getDateHeader(s, headers);
+ }
+
+ @Override
+ public String getHeader(String s) {
+ return getHeader(s, headers);
+ }
+
+ @Override
+ public Enumeration getHeaders(String s) {
+ return getHeaders(s, headers);
+ }
+
+ @Override
+ public Enumeration getHeaderNames() {
+ return getHeaderNames(headers);
+ }
+
+ @Override
+ public int getIntHeader(String s) {
+ return getIntHeader(s, headers);
+ }
+
+ @Override
+ public String getMethod() {
+ return request.getMethod();
+ }
+
+ @Override
+ public String getPathInfo() {
+ String pathInfo = cleanUri(request.getPath());
+ return decodeRequestPath(pathInfo, LambdaContainerHandler.getContainerConfig());
+ }
+
+ @Override
+ public String getPathTranslated() {
+ return null;
+ }
+
+ @Override
+ public String getContextPath() {
+ return generateContextPath(config, null);
+ }
+
+ @Override
+ public String getQueryString() {
+
+ if (Objects.isNull(queryString))
+ return null;
+
+ try {
+ StringBuilder queryStringBuilder = new StringBuilder();
+
+ try {
+ for (String key : queryString.keySet()) {
+ String val = queryString.getFirst(key);
+ queryStringBuilder.append("&");
+ queryStringBuilder.append(URLEncoder.encode(key, config.getUriEncoding()));
+ queryStringBuilder.append("=");
+ if (val != null) {
+ queryStringBuilder.append(URLEncoder.encode(val, config.getUriEncoding()));
+ }
+ }
+ } catch (UnsupportedEncodingException e) {
+ throw new ServletException("Invalid charset passed for query string encoding", e);
+ }
+
+ return queryStringBuilder.substring(1); // remove the first & - faster to do it here than adding logic in the Lambda
+
+ } catch (ServletException e) {
+ log.error("Could not generate query string", e);
+ return null;
+ }
+ }
+
+ @Override
+ public String getRequestURI() {
+ return cleanUri(getContextPath()) + cleanUri(request.getPath());
+ }
+
+ @Override
+ public StringBuffer getRequestURL() {
+ return generateRequestURL(request.getPath());
+ }
+
+ @Override
+ public String getCharacterEncoding() {
+ if (headers == null) {
+ return config.getDefaultContentCharset();
+ }
+ return parseCharacterEncoding(headers.getFirst(HttpHeaders.CONTENT_TYPE));
+ }
+
+ @Override
+ public void setCharacterEncoding(String s) throws UnsupportedEncodingException {
+ setCharacterEncoding(s, headers);
+ }
+
+ @Override
+ public int getContentLength() {
+ return getContentLength(headers);
+ }
+
+ @Override
+ public long getContentLengthLong() {
+ return getContentLengthLong(headers);
+ }
+
+ @Override
+ public String getContentType() {
+ return headers.getFirst(HttpHeaders.CONTENT_TYPE);
+ }
+
+ @Override
+ public ServletInputStream getInputStream() throws IOException {
+ if (requestInputStream == null) {
+ requestInputStream = new AwsServletInputStream(bodyStringToInputStream(request.getBody(), Boolean.TRUE.equals(request.getIsBase64Encoded())));
+ }
+ return requestInputStream;
+ }
+
+ @Override
+ public String getParameter(String s) {
+ return getParameter(queryString, s, config.isQueryStringCaseSensitive());
+ }
+
+ @Override
+ public Enumeration getParameterNames() {
+ Set formParameterNames = getFormUrlEncodedParametersMap().keySet();
+ if (queryString == null) {
+ return Collections.enumeration(formParameterNames);
+ }
+ return Collections.enumeration(Stream.concat(formParameterNames.stream(),
+ queryString.keySet().stream()).collect(Collectors.toSet()));
+ }
+
+ @Override
+ public String[] getParameterValues(String s) {
+ return getParameterValues(queryString, s, config.isQueryStringCaseSensitive());
+ }
+
+ @Override
+ public Map getParameterMap() {
+ return generateParameterMap(queryString, config);
+ }
+
+ @Override
+ public String getProtocol() {
+ // No protocol on the request payload. Defaulting to "HTTP/1.1". Should we return UnsupportedOperationException instead?
+ return "HTTP/1.1";
+ }
+
+ @Override
+ public String getScheme() {
+ return getSchemeFromHeader(request.getHeaders());
+ }
+
+ @Override
+ public BufferedReader getReader() throws IOException {
+ return new BufferedReader(new StringReader(request.getBody()));
+ }
+
+ @Override
+ public String getRemoteAddr() {
+ return request.getHeaders().getFirst(CLIENT_IP_HEADER_NAME);
+ }
+
+ @Override
+ public String getRemoteHost() {
+ return request.getHeaders().getFirst(HttpHeaders.HOST);
+ }
+
+ @Override
+ public Locale getLocale() {
+ List locales = parseAcceptLanguageHeader(headers.getFirst(HttpHeaders.ACCEPT_LANGUAGE));
+ return locales.isEmpty() ? Locale.getDefault() : locales.get(0);
+ }
+
+ @Override
+ public Enumeration getLocales() {
+ List locales = parseAcceptLanguageHeader(headers.getFirst(HttpHeaders.ACCEPT_LANGUAGE));
+ return Collections.enumeration(locales);
+ }
+
+ @Override
+ public int getRemotePort() {
+ return 0;
+ }
+
+ @Override
+ public AsyncContext startAsync() throws IllegalStateException {
+ asyncContext = new AwsAsyncContext(this, response);
+ setAttribute(DISPATCHER_TYPE_ATTRIBUTE, DispatcherType.ASYNC);
+ log.debug("Starting async context for request: " + SecurityUtils.crlf(lambdaContext.getAwsRequestId()));
+ return asyncContext;
+ }
+
+ @Override
+ public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse) throws IllegalStateException {
+ asyncContext = new AwsAsyncContext((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse);
+ setAttribute(DISPATCHER_TYPE_ATTRIBUTE, DispatcherType.ASYNC);
+ log.debug("Starting async context for request: " + SecurityUtils.crlf(lambdaContext.getAwsRequestId()));
+ return asyncContext;
+ }
+
+ @Override
+ public boolean isAsyncSupported() {
+ return true;
+ }
+
+ @Override
+ public boolean isAsyncStarted() {
+ if (asyncContext == null) {
+ return false;
+ }
+ return !asyncContext.isCompleted() && !asyncContext.isDispatched();
+ }
+
+ @Override
+ public AsyncContext getAsyncContext() {
+ if (asyncContext == null) {
+ throw new IllegalStateException("Request " + SecurityUtils.crlf(lambdaContext.getAwsRequestId())
+ + " is not in asynchronous mode. Call startAsync before attempting to get the async context.");
+ }
+ return asyncContext;
+ }
+
+ @Override
+ public String getRequestId() {
+ return lambdaContext.getAwsRequestId();
+ }
+
+ @Override
+ public String getProtocolRequestId() {
+ return "";
+ }
+
+ @Override
+ public int getServerPort() {
+ if (request.getHeaders() == null) {
+ return 443;
+ }
+ String port = request.getHeaders().getFirst(PORT_HEADER_NAME);
+ if (SecurityUtils.isValidPort(port)) {
+ return Integer.parseInt(port);
+ } else {
+ return 443; // default port
+ }
+ }
+
+ @Override
+ public String getServerName() {
+ return request.getHeaders().getFirst(HOST_HEADER_NAME);
+ }
+
+ protected static MultiValuedTreeMap queryStringToMultiValue(Map qs) {
+ if (qs == null || qs.isEmpty()) {
+ return null;
+ }
+ MultiValuedTreeMap qsMap = new MultiValuedTreeMap<>();
+ for (Map.Entry kv : qs.entrySet()) {
+ qsMap.add(kv.getKey(), kv.getValue());
+ }
+ return qsMap;
+ }
+
+}
diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsVpcLatticeV2HttpServletRequestReader.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsVpcLatticeV2HttpServletRequestReader.java
new file mode 100644
index 000000000..722e8fbd3
--- /dev/null
+++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsVpcLatticeV2HttpServletRequestReader.java
@@ -0,0 +1,36 @@
+package com.amazonaws.serverless.proxy.internal.servlet;
+
+import com.amazonaws.serverless.exceptions.InvalidRequestEventException;
+import com.amazonaws.serverless.proxy.RequestReader;
+import com.amazonaws.serverless.proxy.model.ContainerConfig;
+import com.amazonaws.serverless.proxy.model.VPCLatticeV2RequestEvent;
+import com.amazonaws.services.lambda.runtime.Context;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.ws.rs.core.SecurityContext;
+
+public class AwsVpcLatticeV2HttpServletRequestReader extends RequestReader {
+
+ static final String INVALID_REQUEST_ERROR = "The incoming event is not a valid request from Amazon API Gateway or an Application Load Balancer";
+ @Override
+ public AwsVpcLatticeV2HttpServletRequest readRequest(VPCLatticeV2RequestEvent request, SecurityContext securityContext, Context lambdaContext, ContainerConfig config) throws InvalidRequestEventException {
+ if ( request.getMethod() == null || request.getMethod().isEmpty() || request.getRequestContext() == null) {
+ throw new InvalidRequestEventException(INVALID_REQUEST_ERROR);
+ }
+
+ // clean out the request path based on the container config
+ request.setPath(stripBasePath(request.getPath(), config));
+
+ AwsVpcLatticeV2HttpServletRequest servletRequest = new AwsVpcLatticeV2HttpServletRequest(request, lambdaContext, securityContext, config);
+ servletRequest.setAttribute(VPC_LATTICE_V2_CONTEXT_PROPERTY, request.getRequestContext());
+ servletRequest.setAttribute(VPC_LATTICE_V2_EVENT_PROPERTY, request);
+ servletRequest.setAttribute(LAMBDA_CONTEXT_PROPERTY, lambdaContext);
+ servletRequest.setAttribute(JAX_SECURITY_CONTEXT_PROPERTY, securityContext);
+
+ return servletRequest;
+ }
+
+ @Override
+ protected Class extends VPCLatticeV2RequestEvent> getRequestClass() {
+ return VPCLatticeV2RequestEvent.class;
+ }
+}
diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/ServletLambdaContainerHandlerBuilder.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/ServletLambdaContainerHandlerBuilder.java
index a2c1c73ff..25fa09c73 100644
--- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/ServletLambdaContainerHandlerBuilder.java
+++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/ServletLambdaContainerHandlerBuilder.java
@@ -18,6 +18,7 @@
import com.amazonaws.serverless.proxy.model.AwsProxyResponse;
import com.amazonaws.serverless.proxy.model.HttpApiV2ProxyRequest;
+import com.amazonaws.serverless.proxy.model.VPCLatticeV2RequestEvent;
import jakarta.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.List;
@@ -119,6 +120,17 @@ public Builder defaultHttpApiV2Proxy() {
}
+ public Builder defaultVpcLatticeV2Proxy() {
+ initializationWrapper(new AsyncInitializationWrapper())
+ .requestReader((RequestReader) new AwsVpcLatticeV2HttpServletRequestReader())
+ .responseWriter((ResponseWriter) new AwsProxyHttpServletResponseWriter(true))
+ .securityContextWriter((SecurityContextWriter) new AwsVPCLatticeV2SecurityContextWriter())
+ .exceptionHandler(defaultExceptionHandler())
+ .requestTypeClass((Class) VPCLatticeV2RequestEvent.class)
+ .responseTypeClass((Class) AwsProxyResponse.class);
+ return self();
+ }
+
protected ExceptionHandler defaultExceptionHandler() {
return (ExceptionHandler) new AwsProxyExceptionHandler();
}
diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/RequestSource.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/RequestSource.java
index c819fdcc0..7bf292452 100644
--- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/RequestSource.java
+++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/RequestSource.java
@@ -14,5 +14,7 @@
public enum RequestSource {
ALB,
- API_GATEWAY
+ API_GATEWAY,
+ VPC_LATTICE_V2,
+ VPC_LATTICE_V1
}
\ No newline at end of file
diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/VPCLatticeV2RequestEvent.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/VPCLatticeV2RequestEvent.java
new file mode 100644
index 000000000..cc99c4b75
--- /dev/null
+++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/VPCLatticeV2RequestEvent.java
@@ -0,0 +1,249 @@
+package com.amazonaws.serverless.proxy.model;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import org.jetbrains.annotations.Nullable;
+
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneOffset;
+import java.util.Map;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class VPCLatticeV2RequestEvent {
+ private String version;
+ private String path;
+ private String method;
+ private Headers headers;
+ @Nullable
+ private Map queryStringParameters;
+ private RequestContext requestContext;
+ private String body;
+
+ /***
+ * isBase64Encoded is set if the body is a base64 encoded String.
+ */
+ @Nullable
+ private Boolean isBase64Encoded;
+
+ @JsonIgnore
+ public RequestSource getRequestSource() {
+ return RequestSource.VPC_LATTICE_V2;
+ }
+
+ public RequestContext getRequestContext() {
+ return requestContext;
+ }
+
+ public String getVersion() {
+ return version;
+ }
+
+ public String getPath() {
+ return path;
+ }
+
+ public String getMethod() {
+ return method;
+ }
+
+ public String getBody() {
+ return body;
+ }
+
+ public Headers getHeaders() {
+ return headers;
+ }
+
+ public @Nullable Boolean getIsBase64Encoded() {
+ return isBase64Encoded;
+ }
+
+ public @Nullable Map getQueryStringParameters() {
+ return queryStringParameters;
+ }
+
+ public void setPath(String s) {
+ this.path = s;
+ }
+
+ public void setVersion(String version) {
+ this.version = version;
+ }
+
+ public void setMethod(String method) {
+ this.method = method;
+ }
+
+ public void setHeaders(Headers headers) {
+ this.headers = headers;
+ }
+
+ public void setQueryStringParameters(@Nullable Map queryStringParameters) {
+ this.queryStringParameters = queryStringParameters;
+ }
+
+ public void setBody(String body) {
+ this.body = body;
+ }
+
+ public void setBase64Encoded(Boolean base64Encoded) {
+ isBase64Encoded = base64Encoded;
+ }
+
+ public void setRequestContext(RequestContext requestContext) {
+ this.requestContext = requestContext;
+ }
+
+ public static class RequestContext {
+ private String serviceNetworkArn;
+ private String serviceArn;
+ private String targetGroupArn;
+ private Identity identity;
+ private String region;
+ /**
+ * Number of microseconds from the epoch
+ */
+ private String timeEpoch;
+
+ @JsonIgnore
+ public LocalDateTime getLocalDateTime() {
+ long epochMicroseconds = Long.parseLong(timeEpoch);
+ long epochMilliseconds = epochMicroseconds / 1000;
+
+ return Instant.ofEpochMilli(epochMilliseconds).atZone(ZoneOffset.UTC).toLocalDateTime();
+ }
+
+ public Identity getIdentity() {
+ return identity;
+ }
+
+ public String getTimeEpoch() {
+ return timeEpoch;
+ }
+
+ public String getRegion() {
+ return region;
+ }
+
+ public String getServiceNetworkArn() {
+ return serviceNetworkArn;
+ }
+
+ public String getServiceArn() {
+ return serviceArn;
+ }
+
+ public String getTargetGroupArn() {
+ return targetGroupArn;
+ }
+
+ public void setServiceNetworkArn(String serviceNetworkArn) {
+ this.serviceNetworkArn = serviceNetworkArn;
+ }
+
+ public void setServiceArn(String serviceArn) {
+ this.serviceArn = serviceArn;
+ }
+
+ public void setTargetGroupArn(String targetGroupArn) {
+ this.targetGroupArn = targetGroupArn;
+ }
+
+ public void setIdentity(Identity identity) {
+ this.identity = identity;
+ }
+
+ public void setRegion(String region) {
+ this.region = region;
+ }
+
+ public void setTimeEpoch(String timeEpoch) {
+ this.timeEpoch = timeEpoch;
+ }
+ }
+
+ public static class Identity {
+ private String sourceVpcArn;
+ private String type;
+ private String principal;
+ private String sessionName;
+ private String x509SanDns;
+ private String x509SanNameCn;
+ private String x509SubjectCn;
+ private String x509IssuerOu;
+ private String x509SanUri;
+
+ public String getPrincipal() {
+ return principal;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public String getSessionName() {
+ return sessionName;
+ }
+
+ public String getX509IssuerOu() {
+ return x509IssuerOu;
+ }
+
+ public String getX509SanDns() {
+ return x509SanDns;
+ }
+
+ public String getX509SanNameCn() {
+ return x509SanNameCn;
+ }
+
+ public String getX509SanUri() {
+ return x509SanUri;
+ }
+
+ public String getX509SubjectCn() {
+ return x509SubjectCn;
+ }
+
+ public String getSourceVpcArn() {
+ return sourceVpcArn;
+ }
+
+ public void setSourceVpcArn(String sourceVpcArn) {
+ this.sourceVpcArn = sourceVpcArn;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ public void setPrincipal(String principal) {
+ this.principal = principal;
+ }
+
+ public void setSessionName(String sessionName) {
+ this.sessionName = sessionName;
+ }
+
+ public void setX509SanDns(String x509SanDns) {
+ this.x509SanDns = x509SanDns;
+ }
+
+ public void setX509SanNameCn(String x509SanNameCn) {
+ this.x509SanNameCn = x509SanNameCn;
+ }
+
+ public void setX509SubjectCn(String x509SubjectCn) {
+ this.x509SubjectCn = x509SubjectCn;
+ }
+
+ public void setX509IssuerOu(String x509IssuerOu) {
+ this.x509IssuerOu = x509IssuerOu;
+ }
+
+ public void setX509SanUri(String x509SanUri) {
+ this.x509SanUri = x509SanUri;
+ }
+ }
+}
diff --git a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/jaxrs/AwsVpcLatticeV2SecurityContextTest.java b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/jaxrs/AwsVpcLatticeV2SecurityContextTest.java
new file mode 100644
index 000000000..31a4e9be4
--- /dev/null
+++ b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/jaxrs/AwsVpcLatticeV2SecurityContextTest.java
@@ -0,0 +1,48 @@
+package com.amazonaws.serverless.proxy.internal.jaxrs;
+
+import com.amazonaws.serverless.proxy.AwsVPCLatticeV2SecurityContextWriter;
+import com.amazonaws.serverless.proxy.internal.LambdaContainerHandler;
+import com.amazonaws.serverless.proxy.internal.testutils.AwsProxyRequestBuilder;
+import com.amazonaws.serverless.proxy.model.VPCLatticeV2RequestEvent;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import jakarta.ws.rs.core.SecurityContext;
+import org.junit.jupiter.api.Test;
+
+import static com.amazonaws.serverless.proxy.model.VPCLatticeV2RequestEventTest.BASE_V2_EVENT_AUTH_IAM;
+import static org.junit.jupiter.api.Assertions.*;
+
+public class AwsVpcLatticeV2SecurityContextTest {
+
+
+ AwsVPCLatticeV2SecurityContextWriter contextWriter = new AwsVPCLatticeV2SecurityContextWriter();
+ VPCLatticeV2RequestEvent NONE_AUTH = new AwsProxyRequestBuilder("/", "GET").toVPCLatticeV2Request();
+
+
+
+ private VPCLatticeV2RequestEvent getAuthenticatedEvent() {
+ try {
+ return LambdaContainerHandler.getObjectMapper().readValue(BASE_V2_EVENT_AUTH_IAM, VPCLatticeV2RequestEvent.class);
+ } catch (JsonProcessingException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+
+
+ @Test
+ void getAuthenticationScheme_noAuth_nullAuthType() {
+ SecurityContext ctx = contextWriter.writeSecurityContext(NONE_AUTH, null);
+ assertNull(ctx.getAuthenticationScheme());
+ assertNull(ctx.getUserPrincipal());
+ assertFalse(ctx.isSecure());
+ }
+
+ @Test
+ void getAuthenticationScheme_iamAuth_AwsIamAuthType() {
+ VPCLatticeV2RequestEvent req = getAuthenticatedEvent();
+ SecurityContext ctx = contextWriter.writeSecurityContext(req, null);
+ assertNotNull(ctx.getAuthenticationScheme());
+ assertEquals(ctx.getUserPrincipal().getName(), "arn:aws:iam::123456789012:assumed-role/my-role/my-session");
+ assertTrue(ctx.isSecure());
+ }
+}
diff --git a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequestTest.java b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequestTest.java
index 4edcf5241..14af92a92 100644
--- a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequestTest.java
+++ b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequestTest.java
@@ -83,7 +83,7 @@ public void initAwsProxyHttpServletRequestTest(String type) {
}
public static Collection