From f01b22cb20fae76b87a11390b17663d37b60df89 Mon Sep 17 00:00:00 2001 From: Perry Huang Date: Fri, 25 Sep 2026 23:29:15 -0700 Subject: [PATCH 1/2] Add VPC Lattice event types and identity fields --- events/testdata/vpclattice-response.json | 10 ++++ events/testdata/vpclattice-v1-request.json | 16 +++++++ events/testdata/vpclattice-v2-request.json | 36 ++++++++++++++ events/vpclattice.go | 56 ++++++++++++++++++++++ events/vpclattice_test.go | 46 ++++++++++++++++++ 5 files changed, 164 insertions(+) create mode 100644 events/testdata/vpclattice-response.json create mode 100644 events/testdata/vpclattice-v1-request.json create mode 100644 events/testdata/vpclattice-v2-request.json create mode 100644 events/vpclattice.go create mode 100644 events/vpclattice_test.go diff --git a/events/testdata/vpclattice-response.json b/events/testdata/vpclattice-response.json new file mode 100644 index 00000000..9e211f14 --- /dev/null +++ b/events/testdata/vpclattice-response.json @@ -0,0 +1,10 @@ +{ + "isBase64Encoded": true, + "statusCode": 200, + "statusDescription": "200 OK", + "headers": { + "set-cookie": "cookies", + "content-type": "application/json" + }, + "body": "BODY" +} diff --git a/events/testdata/vpclattice-v1-request.json b/events/testdata/vpclattice-v1-request.json new file mode 100644 index 00000000..0d640eea --- /dev/null +++ b/events/testdata/vpclattice-v1-request.json @@ -0,0 +1,16 @@ +{ + "raw_path": "/?query1=value1&query1=value2", + "method": "POST", + "headers": { + "accept-encoding": "gzip, br, deflate", + "accept": "application/json", + "content-length": "4", + "content-type": "application/json", + "host": "some-host.vpc-lattice-svcs.us-east-1.on.aws", + "x-forwarded-for": "1.2.3.4,2.3.4.5", + "x-forwarded-port": "443" + }, + "query_string_parameters": {"query1": "value2"}, + "body": "BODY", + "is_base64_encoded": true +} diff --git a/events/testdata/vpclattice-v2-request.json b/events/testdata/vpclattice-v2-request.json new file mode 100644 index 00000000..6de74845 --- /dev/null +++ b/events/testdata/vpclattice-v2-request.json @@ -0,0 +1,36 @@ +{ + "version": "2.0", + "path": "/?query1=value1&query1=value2", + "method": "POST", + "headers": { + "accept": ["application/json"], + "x-forwarded-port": ["443"], + "x-forwarded-for": ["1.2.3.4,2.3.4.5"], + "host": ["some-host.vpc-lattice-svcs.us-east-1.on.aws"], + "content-type": ["application/json"], + "content-length": ["4"], + "accept-encoding": ["gzip, br, deflate"] + }, + "queryStringParameters": {"query1": ["value1", "value2"]}, + "body": "BODY", + "isBase64Encoded": true, + "requestContext": { + "serviceNetworkArn": "arn:aws:vpc-lattice:us-east-1:12346789012:servicenetwork/sn-0db8ae434454b4422", + "serviceArn": "arn:aws:vpc-lattice:us-east-1:12346789012:service/svc-0db8ae434454b4422", + "targetGroupArn": "arn:aws:vpc-lattice:us-east-1:12346789012:targetgroup/tg-0db8ae434454b4422", + "identity": { + "sourceVpcArn": "arn:aws:ec2:us-east-1:12346789012:vpc/vpc-0db8ae434454b4422", + "type": "AWS_IAM", + "principal": "arn:aws:sts::12346789012:assumed-role/IAM_ROLE/SESSION_NAME", + "principalOrgID": "o-50dc6c495c0c9188", + "sessionName": "SESSION_NAME", + "x509IssuerOu": "issuer OU", + "x509SanDns": "example.com", + "x509SanNameCn": "issuer name", + "x509SanUri": "spiffe://example.com/service", + "x509SubjectCn": "subject name" + }, + "region": "us-east-1", + "timeEpoch": "1695799509392227" + } +} diff --git a/events/vpclattice.go b/events/vpclattice.go new file mode 100644 index 00000000..30d22a69 --- /dev/null +++ b/events/vpclattice.go @@ -0,0 +1,56 @@ +package events + +// VPCLatticeRequestV1 contains a V1 request from AWS VPC Lattice. +type VPCLatticeRequestV1 struct { + RawPath string `json:"raw_path"` + Method string `json:"method"` + Headers map[string]string `json:"headers"` + QueryStringParameters map[string]string `json:"query_string_parameters"` + Body string `json:"body"` + IsBase64Encoded bool `json:"is_base64_encoded,omitempty"` +} + +// VPCLatticeRequestV2 contains a V2 request from AWS VPC Lattice. +type VPCLatticeRequestV2 struct { + Version string `json:"version"` + Path string `json:"path"` + Method string `json:"method"` + Headers map[string][]string `json:"headers"` + QueryStringParameters map[string][]string `json:"queryStringParameters,omitempty"` + Body string `json:"body"` + RequestContext VPCLatticeRequestContext `json:"requestContext"` + IsBase64Encoded bool `json:"isBase64Encoded,omitempty"` +} + +// VPCLatticeRequestContext contains metadata about the incoming request. +type VPCLatticeRequestContext struct { + ServiceNetworkARN string `json:"serviceNetworkArn"` + ServiceARN string `json:"serviceArn"` + TargetGroupARN string `json:"targetGroupArn"` + Identity *VPCLatticeRequestIdentity `json:"identity,omitempty"` + Region string `json:"region"` + TimeEpoch string `json:"timeEpoch"` +} + +// VPCLatticeRequestIdentity contains information about the caller. +type VPCLatticeRequestIdentity struct { + SourceVPCARN string `json:"sourceVpcArn,omitempty"` + Type string `json:"type,omitempty"` + Principal string `json:"principal,omitempty"` + PrincipalOrgID string `json:"principalOrgID,omitempty"` + SessionName string `json:"sessionName,omitempty"` + X509IssuerOU string `json:"x509IssuerOu,omitempty"` + X509SanDNS string `json:"x509SanDns,omitempty"` + X509SanNameCN string `json:"x509SanNameCn,omitempty"` + X509SanURI string `json:"x509SanUri,omitempty"` + X509SubjectCN string `json:"x509SubjectCn,omitempty"` +} + +// VPCLatticeResponse contains the response to be returned to VPC Lattice. +type VPCLatticeResponse struct { + IsBase64Encoded bool `json:"isBase64Encoded"` + StatusCode int `json:"statusCode"` + StatusDescription string `json:"statusDescription,omitempty"` + Headers map[string]string `json:"headers"` + Body string `json:"body,omitempty"` +} diff --git a/events/vpclattice_test.go b/events/vpclattice_test.go new file mode 100644 index 00000000..25fb4d5a --- /dev/null +++ b/events/vpclattice_test.go @@ -0,0 +1,46 @@ +package events + +import ( + "encoding/json" + "testing" + + "github.com/aws/aws-lambda-go/events/test" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestVPCLatticeRequestV1Marshalling(t *testing.T) { + test.AssertJsonFile(t, "./testdata/vpclattice-v1-request.json", &VPCLatticeRequestV1{}) +} + +func TestVPCLatticeRequestV1MalformedJson(t *testing.T) { + test.TestMalformedJson(t, &VPCLatticeRequestV1{}) +} + +func TestVPCLatticeRequestV2Marshalling(t *testing.T) { + test.AssertJsonFile(t, "./testdata/vpclattice-v2-request.json", &VPCLatticeRequestV2{}) +} + +func TestVPCLatticeRequestV2MalformedJson(t *testing.T) { + test.TestMalformedJson(t, &VPCLatticeRequestV2{}) +} + +func TestVPCLatticeRequestV2OptionalIdentityFields(t *testing.T) { + const input = `{"version":"2.0","path":"/","method":"GET","headers":{},"body":"","requestContext":{"serviceNetworkArn":"network","serviceArn":"service","targetGroupArn":"target","identity":{"type":"AWS_IAM"},"region":"us-east-1","timeEpoch":"1695799509392227"}}` + var request VPCLatticeRequestV2 + require.NoError(t, json.Unmarshal([]byte(input), &request)) + output, err := json.Marshal(request) + require.NoError(t, err) + assert.JSONEq(t, input, string(output)) +} + +func TestVPCLatticeResponse(t *testing.T) { + test.AssertJsonFile(t, "./testdata/vpclattice-response.json", &VPCLatticeResponse{}) +} + +func TestVPCLatticeResponseWithoutBody(t *testing.T) { + response := VPCLatticeResponse{StatusCode: 204, Headers: map[string]string{}} + output, err := json.Marshal(response) + require.NoError(t, err) + assert.JSONEq(t, `{"isBase64Encoded":false,"statusCode":204,"headers":{}}`, string(output)) +} From 6d88270aff51e80035aa4634126a38091e93c1a4 Mon Sep 17 00:00:00 2001 From: Perry Huang Date: Fri, 25 Sep 2026 23:46:49 -0700 Subject: [PATCH 2/2] Preserve V1 base64 flag and V2 omission --- events/vpclattice.go | 2 +- events/vpclattice_test.go | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/events/vpclattice.go b/events/vpclattice.go index 30d22a69..121a1492 100644 --- a/events/vpclattice.go +++ b/events/vpclattice.go @@ -7,7 +7,7 @@ type VPCLatticeRequestV1 struct { Headers map[string]string `json:"headers"` QueryStringParameters map[string]string `json:"query_string_parameters"` Body string `json:"body"` - IsBase64Encoded bool `json:"is_base64_encoded,omitempty"` + IsBase64Encoded bool `json:"is_base64_encoded"` } // VPCLatticeRequestV2 contains a V2 request from AWS VPC Lattice. diff --git a/events/vpclattice_test.go b/events/vpclattice_test.go index 25fb4d5a..44a31f55 100644 --- a/events/vpclattice_test.go +++ b/events/vpclattice_test.go @@ -25,6 +25,16 @@ func TestVPCLatticeRequestV2MalformedJson(t *testing.T) { test.TestMalformedJson(t, &VPCLatticeRequestV2{}) } +func TestVPCLatticeRequestV1IncludesFalseBase64Flag(t *testing.T) { + var request VPCLatticeRequestV1 + require.NoError(t, json.Unmarshal([]byte(`{"is_base64_encoded":false}`), &request)) + output, err := json.Marshal(request) + require.NoError(t, err) + var fields map[string]json.RawMessage + require.NoError(t, json.Unmarshal(output, &fields)) + assert.Equal(t, json.RawMessage("false"), fields["is_base64_encoded"]) +} + func TestVPCLatticeRequestV2OptionalIdentityFields(t *testing.T) { const input = `{"version":"2.0","path":"/","method":"GET","headers":{},"body":"","requestContext":{"serviceNetworkArn":"network","serviceArn":"service","targetGroupArn":"target","identity":{"type":"AWS_IAM"},"region":"us-east-1","timeEpoch":"1695799509392227"}}` var request VPCLatticeRequestV2