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..121a1492 --- /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"` +} + +// 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..44a31f55 --- /dev/null +++ b/events/vpclattice_test.go @@ -0,0 +1,56 @@ +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 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 + 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)) +}