From 30d7c7eaf4349770050b8b4f524070bafcc091b0 Mon Sep 17 00:00:00 2001 From: Bryan Moffatt Date: Tue, 19 Dec 2023 12:54:58 -0800 Subject: [PATCH 1/9] update linter version (#539) --- .github/workflows/lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 5fe27573..3c948d56 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -18,6 +18,6 @@ jobs: - name: golangci-golint run: | - curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s v1.52.1 + curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s v1.55.2 ./bin/golangci-lint run -v ./... From 326401a86d34ca38d254a3188248fbbad5e7fc08 Mon Sep 17 00:00:00 2001 From: "Brandon C (Amazon)" Date: Tue, 19 Dec 2023 16:37:50 -0800 Subject: [PATCH 2/9] Added support for Cognito pre token generation with access token customization (#538) * Fixed Cognito service name and added event structure for Cognito pre token generation event V2 * gofmt -s -w . * Add sample data and serde test * Tweak the test data to avoid having to break response marshaling compatability of the GroupConfiguration struct --------- Co-authored-by: Bryan Moffatt Co-authored-by: Bryan Moffatt --- events/cognito.go | 72 +++++++++++++++---- events/cognito_test.go | 22 ++++++ ...ognito-event-userpools-pretokengen-v2.json | 71 ++++++++++++++++++ 3 files changed, 150 insertions(+), 15 deletions(-) create mode 100644 events/testdata/cognito-event-userpools-pretokengen-v2.json diff --git a/events/cognito.go b/events/cognito.go index c24a3e3e..f02619da 100644 --- a/events/cognito.go +++ b/events/cognito.go @@ -2,7 +2,7 @@ package events -// CognitoEvent contains data from an event sent from AWS Cognito Sync +// CognitoEvent contains data from an event sent from Amazon Cognito Sync type CognitoEvent struct { DatasetName string `json:"datasetName"` DatasetRecords map[string]CognitoDatasetRecord `json:"datasetRecords"` @@ -13,14 +13,14 @@ type CognitoEvent struct { Version int `json:"version"` } -// CognitoDatasetRecord represents a record from an AWS Cognito Sync event +// CognitoDatasetRecord represents a record from an Amazon Cognito Sync event type CognitoDatasetRecord struct { NewValue string `json:"newValue"` OldValue string `json:"oldValue"` Op string `json:"op"` } -// CognitoEventUserPoolsPreSignup is sent by AWS Cognito User Pools when a user attempts to register +// CognitoEventUserPoolsPreSignup is sent by Amazon Cognito User Pools when a user attempts to register // (sign up), allowing a Lambda to perform custom validation to accept or deny the registration request type CognitoEventUserPoolsPreSignup struct { CognitoEventUserPoolsHeader @@ -28,7 +28,7 @@ type CognitoEventUserPoolsPreSignup struct { Response CognitoEventUserPoolsPreSignupResponse `json:"response"` } -// CognitoEventUserPoolsPreAuthentication is sent by AWS Cognito User Pools when a user submits their information +// CognitoEventUserPoolsPreAuthentication is sent by Amazon Cognito User Pools when a user submits their information // to be authenticated, allowing you to perform custom validations to accept or deny the sign in request. type CognitoEventUserPoolsPreAuthentication struct { CognitoEventUserPoolsHeader @@ -36,7 +36,7 @@ type CognitoEventUserPoolsPreAuthentication struct { Response CognitoEventUserPoolsPreAuthenticationResponse `json:"response"` } -// CognitoEventUserPoolsPostConfirmation is sent by AWS Cognito User Pools after a user is confirmed, +// CognitoEventUserPoolsPostConfirmation is sent by Amazon Cognito User Pools after a user is confirmed, // allowing the Lambda to send custom messages or add custom logic. type CognitoEventUserPoolsPostConfirmation struct { CognitoEventUserPoolsHeader @@ -44,7 +44,7 @@ type CognitoEventUserPoolsPostConfirmation struct { Response CognitoEventUserPoolsPostConfirmationResponse `json:"response"` } -// CognitoEventUserPoolsPreTokenGen is sent by AWS Cognito User Pools when a user attempts to retrieve +// CognitoEventUserPoolsPreTokenGen is sent by Amazon Cognito User Pools when a user attempts to retrieve // credentials, allowing a Lambda to perform insert, suppress or override claims type CognitoEventUserPoolsPreTokenGen struct { CognitoEventUserPoolsHeader @@ -52,7 +52,15 @@ type CognitoEventUserPoolsPreTokenGen struct { Response CognitoEventUserPoolsPreTokenGenResponse `json:"response"` } -// CognitoEventUserPoolsPostAuthentication is sent by AWS Cognito User Pools after a user is authenticated, +// CognitoEventUserPoolsPreTokenGenV2 is sent by Amazon Cognito User Pools when a user attempts to retrieve +// credentials, allowing a Lambda to perform insert, suppress or override claims and scopes +type CognitoEventUserPoolsPreTokenGenV2 struct { + CognitoEventUserPoolsHeader + Request CognitoEventUserPoolsPreTokenGenV2Request `json:"request"` + Response CognitoEventUserPoolsPreTokenGenV2Response `json:"response"` +} + +// CognitoEventUserPoolsPostAuthentication is sent by Amazon Cognito User Pools after a user is authenticated, // allowing the Lambda to add custom logic. type CognitoEventUserPoolsPostAuthentication struct { CognitoEventUserPoolsHeader @@ -60,7 +68,7 @@ type CognitoEventUserPoolsPostAuthentication struct { Response CognitoEventUserPoolsPostAuthenticationResponse `json:"response"` } -// CognitoEventUserPoolsMigrateUser is sent by AWS Cognito User Pools when a user does not exist in the +// CognitoEventUserPoolsMigrateUser is sent by Amazon Cognito User Pools when a user does not exist in the // user pool at the time of sign-in with a password, or in the forgot-password flow. type CognitoEventUserPoolsMigrateUser struct { CognitoEventUserPoolsHeader @@ -74,7 +82,7 @@ type CognitoEventUserPoolsCallerContext struct { ClientID string `json:"clientId"` } -// CognitoEventUserPoolsHeader contains common data from events sent by AWS Cognito User Pools +// CognitoEventUserPoolsHeader contains common data from events sent by Amazon Cognito User Pools type CognitoEventUserPoolsHeader struct { Version string `json:"version"` TriggerSource string `json:"triggerSource"` @@ -125,11 +133,24 @@ type CognitoEventUserPoolsPreTokenGenRequest struct { ClientMetadata map[string]string `json:"clientMetadata"` } -// CognitoEventUserPoolsPreTokenGenResponse containst the response portion of a PreTokenGen event +// CognitoEventUserPoolsPreTokenGenV2Request contains request portion of V2 PreTokenGen event +type CognitoEventUserPoolsPreTokenGenV2Request struct { + UserAttributes map[string]string `json:"userAttributes"` + GroupConfiguration GroupConfiguration `json:"groupConfiguration"` + ClientMetadata map[string]string `json:"clientMetadata,omitempty"` + Scopes []string `json:"scopes"` +} + +// CognitoEventUserPoolsPreTokenGenResponse contains the response portion of a PreTokenGen event type CognitoEventUserPoolsPreTokenGenResponse struct { ClaimsOverrideDetails ClaimsOverrideDetails `json:"claimsOverrideDetails"` } +// CognitoEventUserPoolsPreTokenGenV2Response contains the response portion of a V2 PreTokenGen event +type CognitoEventUserPoolsPreTokenGenV2Response struct { + ClaimsAndScopeOverrideDetails ClaimsAndScopeOverrideDetails `json:"claimsAndScopeOverrideDetails"` +} + // CognitoEventUserPoolsPostAuthenticationRequest contains the request portion of a PostAuthentication event type CognitoEventUserPoolsPostAuthenticationRequest struct { NewDeviceUsed bool `json:"newDeviceUsed"` @@ -157,6 +178,27 @@ type CognitoEventUserPoolsMigrateUserResponse struct { ForceAliasCreation bool `json:"forceAliasCreation"` } +// ClaimsAndScopeOverrideDetails allows lambda to add, suppress or override V2 claims and scopes in the token +type ClaimsAndScopeOverrideDetails struct { + IDTokenGeneration IDTokenGeneration `json:"idTokenGeneration"` + AccessTokenGeneration AccessTokenGeneration `json:"accessTokenGeneration"` + GroupOverrideDetails GroupConfiguration `json:"groupOverrideDetails"` +} + +// IDTokenGeneration allows lambda to modify the ID token +type IDTokenGeneration struct { + ClaimsToAddOrOverride map[string]string `json:"claimsToAddOrOverride"` + ClaimsToSuppress []string `json:"claimsToSuppress"` +} + +// AccessTokenGeneration allows lambda to modify the access token +type AccessTokenGeneration struct { + ClaimsToAddOrOverride map[string]string `json:"claimsToAddOrOverride"` + ClaimsToSuppress []string `json:"claimsToSuppress"` + ScopesToAdd []string `json:"scopesToAdd"` + ScopesToSuppress []string `json:"scopesToSuppress"` +} + // ClaimsOverrideDetails allows lambda to add, suppress or override claims in the token type ClaimsOverrideDetails struct { GroupOverrideDetails GroupConfiguration `json:"groupOverrideDetails"` @@ -164,7 +206,7 @@ type ClaimsOverrideDetails struct { ClaimsToSuppress []string `json:"claimsToSuppress"` } -// GroupConfiguration allows lambda to override groups, roles and set a perferred role +// GroupConfiguration allows lambda to override groups, roles and set a preferred role type GroupConfiguration struct { GroupsToOverride []string `json:"groupsToOverride"` IAMRolesToOverride []string `json:"iamRolesToOverride"` @@ -194,7 +236,7 @@ type CognitoEventUserPoolsDefineAuthChallengeResponse struct { FailAuthentication bool `json:"failAuthentication"` } -// CognitoEventUserPoolsDefineAuthChallenge sent by AWS Cognito User Pools to initiate custom authentication flow +// CognitoEventUserPoolsDefineAuthChallenge sent by Amazon Cognito User Pools to initiate custom authentication flow type CognitoEventUserPoolsDefineAuthChallenge struct { CognitoEventUserPoolsHeader Request CognitoEventUserPoolsDefineAuthChallengeRequest `json:"request"` @@ -216,7 +258,7 @@ type CognitoEventUserPoolsCreateAuthChallengeResponse struct { ChallengeMetadata string `json:"challengeMetadata"` } -// CognitoEventUserPoolsCreateAuthChallenge sent by AWS Cognito User Pools to create a challenge to present to the user +// CognitoEventUserPoolsCreateAuthChallenge sent by Amazon Cognito User Pools to create a challenge to present to the user type CognitoEventUserPoolsCreateAuthChallenge struct { CognitoEventUserPoolsHeader Request CognitoEventUserPoolsCreateAuthChallengeRequest `json:"request"` @@ -236,7 +278,7 @@ type CognitoEventUserPoolsVerifyAuthChallengeResponse struct { AnswerCorrect bool `json:"answerCorrect"` } -// CognitoEventUserPoolsVerifyAuthChallenge sent by AWS Cognito User Pools to verify if the response from the end user +// CognitoEventUserPoolsVerifyAuthChallenge sent by Amazon Cognito User Pools to verify if the response from the end user // for a custom Auth Challenge is valid or not type CognitoEventUserPoolsVerifyAuthChallenge struct { CognitoEventUserPoolsHeader @@ -244,7 +286,7 @@ type CognitoEventUserPoolsVerifyAuthChallenge struct { Response CognitoEventUserPoolsVerifyAuthChallengeResponse `json:"response"` } -// CognitoEventUserPoolsCustomMessage is sent by AWS Cognito User Pools before a verification or MFA message is sent, +// CognitoEventUserPoolsCustomMessage is sent by Amazon Cognito User Pools before a verification or MFA message is sent, // allowing a user to customize the message dynamically. type CognitoEventUserPoolsCustomMessage struct { CognitoEventUserPoolsHeader diff --git a/events/cognito_test.go b/events/cognito_test.go index 88cb3121..12cd1654 100644 --- a/events/cognito_test.go +++ b/events/cognito_test.go @@ -140,6 +140,28 @@ func TestCognitoEventUserPoolsPreTokenGenMarshaling(t *testing.T) { test.AssertJsonsEqual(t, inputJSON, outputJSON) } +func TestCognitoEventUserPoolsPreTokenGenV2Marshaling(t *testing.T) { + // read json from file + inputJSON, err := ioutil.ReadFile("./testdata/cognito-event-userpools-pretokengen-v2.json") + if err != nil { + t.Errorf("could not open test file. details: %v", err) + } + + // de-serialize into CognitoEvent + var inputEvent CognitoEventUserPoolsPreTokenGenV2 + if err := json.Unmarshal(inputJSON, &inputEvent); err != nil { + t.Errorf("could not unmarshal event. details: %v", err) + } + + // serialize to json + outputJSON, err := json.Marshal(inputEvent) + if err != nil { + t.Errorf("could not marshal event. details: %v", err) + } + + test.AssertJsonsEqual(t, inputJSON, outputJSON) +} + func TestCognitoEventUserPoolsDefineAuthChallengeMarshaling(t *testing.T) { var inputEvent CognitoEventUserPoolsDefineAuthChallenge test.AssertJsonFile(t, "./testdata/cognito-event-userpools-define-auth-challenge.json", &inputEvent) diff --git a/events/testdata/cognito-event-userpools-pretokengen-v2.json b/events/testdata/cognito-event-userpools-pretokengen-v2.json new file mode 100644 index 00000000..6feeb184 --- /dev/null +++ b/events/testdata/cognito-event-userpools-pretokengen-v2.json @@ -0,0 +1,71 @@ +{ + "version": "2", + "triggerSource": "TokenGeneration_Authentication", + "region": "us-west-2", + "userPoolId": "us-east-1_EXAMPLE", + "userName": "brcotter", + "callerContext": { + "awsSdkVersion": "aws-sdk-unknown-unknown", + "clientId": "1example23456789" + }, + "request": { + "userAttributes": { + "sub": "a36036a8-9061-424d-a737-56d57dae7bc6", + "cognito:email_alias": "testuser@example.com", + "cognito:user_status": "CONFIRMED", + "email_verified": "true", + "email": "testuser@example.com" + }, + "groupConfiguration": { + "groupsToOverride": [], + "iamRolesToOverride": [], + "preferredRole": null + }, + "scopes": [ + "aws.cognito.signin.user.admin" + ] + }, + "response": { + "claimsAndScopeOverrideDetails": { + "idTokenGeneration": { + "claimsToAddOrOverride": { + "family_name": "xyz" + }, + "claimsToSuppress": [ + "email", + "birthdate" + ] + }, + "accessTokenGeneration": { + "claimsToAddOrOverride": { + "family_name": "xyz" + }, + "claimsToSuppress": [ + "email", + "birthdate" + ], + "scopesToAdd": [ + "scope1", + "scope2", + "scopeLomond" + ], + "scopesToSuppress": [ + "phone_number" + ] + }, + "groupOverrideDetails": { + "groupsToOverride": [ + "group-A", + "group-B", + "group-C" + ], + "iamRolesToOverride": [ + "arn:aws:iam::123456789012:role/sns_callerA", + "arn:aws:iam::123456789012:role/sns_callerB", + "arn:aws:iam::123456789012:role/sns_callerC" + ], + "preferredRole": "arn:aws:iam::123456789012:role/sns_caller" + } + } + } +} From c42f056212f972e36a1266130278ce5c4ce02b28 Mon Sep 17 00:00:00 2001 From: "Brandon C (Amazon)" Date: Tue, 19 Dec 2023 16:52:37 -0800 Subject: [PATCH 3/9] fixed test event for Cognito pretokenV2 (#540) --- events/testdata/cognito-event-userpools-pretokengen-v2.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/events/testdata/cognito-event-userpools-pretokengen-v2.json b/events/testdata/cognito-event-userpools-pretokengen-v2.json index 6feeb184..8f963d9b 100644 --- a/events/testdata/cognito-event-userpools-pretokengen-v2.json +++ b/events/testdata/cognito-event-userpools-pretokengen-v2.json @@ -1,9 +1,9 @@ { "version": "2", "triggerSource": "TokenGeneration_Authentication", - "region": "us-west-2", + "region": "us-east-1", "userPoolId": "us-east-1_EXAMPLE", - "userName": "brcotter", + "userName": "testuser", "callerContext": { "awsSdkVersion": "aws-sdk-unknown-unknown", "clientId": "1example23456789" From f0792716d3cb62d45772c5523d7abcd2117ce8aa Mon Sep 17 00:00:00 2001 From: Hirotaka Tagawa / wafuwafu13 Date: Fri, 22 Dec 2023 00:07:33 +0000 Subject: [PATCH 4/9] refactor: use assert.PanicsWithValue (#541) --- events/attributevalue_test.go | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/events/attributevalue_test.go b/events/attributevalue_test.go index 551c9d1c..b1b68233 100644 --- a/events/attributevalue_test.go +++ b/events/attributevalue_test.go @@ -215,25 +215,10 @@ func TestAccessWithWrongTypePanics(t *testing.T) { var av DynamoDBAttributeValue err := json.Unmarshal([]byte(testCase.input), &av) assert.Nil(t, err) - // may use PanicsWithValue(expectedError) when it is available - assertPanicsWithValue(t, testCase.expectedError, func() { testCase.accessor(av) }) + assert.PanicsWithValue(t, testCase.expectedError, func() { testCase.accessor(av) }) } } -func assertPanicsWithValue(t *testing.T, expected error, action func()) { - defer func() { - r := recover() - if r == nil { - t.Errorf("Should have panicked") - } - if r != expected { - t.Errorf("should have panicked with value %v but panicked with value %v", expected, r) - } - }() - - action() -} - func TestMarshalAndUnmarshalString(t *testing.T) { const inputString = "INPUT STRING" inputValue := NewStringAttribute(inputString) From 50dbfb12d5bfc88c681a4b8d4fbea871f0b449e6 Mon Sep 17 00:00:00 2001 From: Mateus Marquezini Date: Thu, 4 Jan 2024 20:26:54 -0300 Subject: [PATCH 5/9] [479] CloudWatchEvent renamed to EventBridgeEvent (#537) * #479 CloudWatchEvent renamed to EventBridgeEvent * #479 CloudWatchEvent renamed to EventBridgeEvent * #479 code improvements after code review * #479 small fixes after code review * #479 small fix after code review --- ...DME_CloudWatch_Events.md => README_EventBridge_Events.md} | 4 ++-- events/cloudwatch_events.go | 5 +++-- events/codedeploy.go | 2 -- events/codepipeline_cloudwatch.go | 4 ++-- events/codepipeline_cloudwatch_test.go | 3 ++- 5 files changed, 9 insertions(+), 9 deletions(-) rename events/{README_CloudWatch_Events.md => README_EventBridge_Events.md} (67%) diff --git a/events/README_CloudWatch_Events.md b/events/README_EventBridge_Events.md similarity index 67% rename from events/README_CloudWatch_Events.md rename to events/README_EventBridge_Events.md index b1d16dc7..52ea90c2 100644 --- a/events/README_CloudWatch_Events.md +++ b/events/README_EventBridge_Events.md @@ -1,7 +1,7 @@ # Sample Function -The following is a Lambda function that receives Amazon CloudWatch event record data as input and writes event detail to Lambda's CloudWatch Logs. Note that by default anything written to Console will be logged as CloudWatch Logs events. +The following is a Lambda function that receives Amazon EventBridge event record data as input and writes event detail to Lambda's CloudWatch Logs. Note that by default anything written to Console will be logged as CloudWatch Logs events. ```go import ( @@ -11,7 +11,7 @@ import ( "github.com/aws/aws-lambda-go/events" ) -func handler(ctx context.Context, event events.CloudWatchEvent) { +func handler(ctx context.Context, event events.EventBridgeEvent) { fmt.Printf("Detail = %s\n", event.Detail) } ``` diff --git a/events/cloudwatch_events.go b/events/cloudwatch_events.go index e3201fdb..0b109ba3 100644 --- a/events/cloudwatch_events.go +++ b/events/cloudwatch_events.go @@ -5,8 +5,7 @@ import ( "time" ) -// CloudWatchEvent is the outer structure of an event sent via CloudWatch Events. -// For examples of events that come via CloudWatch Events, see https://docs.aws.amazon.com/AmazonCloudWatch/latest/events/EventTypes.html +// CloudWatchEvent is the outer structure of an event sent via EventBridge serverless service. type CloudWatchEvent struct { Version string `json:"version"` ID string `json:"id"` @@ -18,3 +17,5 @@ type CloudWatchEvent struct { Resources []string `json:"resources"` Detail json.RawMessage `json:"detail"` } + +type EventBridgeEvent = CloudWatchEvent diff --git a/events/codedeploy.go b/events/codedeploy.go index e54eeded..af239bbf 100644 --- a/events/codedeploy.go +++ b/events/codedeploy.go @@ -20,8 +20,6 @@ const ( CodeDeployDeploymentStateSuccess CodeDeployDeploymentState = "SUCCESS" ) -// CodeDeployEvent is documented at: -// https://docs.aws.amazon.com/AmazonCloudWatch/latest/events/EventTypes.html#acd_event_types type CodeDeployEvent struct { // AccountID is the id of the AWS account from which the event originated. AccountID string `json:"account"` diff --git a/events/codepipeline_cloudwatch.go b/events/codepipeline_cloudwatch.go index 0beea862..41340c4e 100644 --- a/events/codepipeline_cloudwatch.go +++ b/events/codepipeline_cloudwatch.go @@ -41,8 +41,6 @@ const ( CodePipelineActionStateCanceled CodePipelineActionState = "CANCELED" ) -// CodePipelineEvent is documented at: -// https://docs.aws.amazon.com/AmazonCloudWatch/latest/events/EventTypes.html#codepipeline_event_type type CodePipelineCloudWatchEvent struct { // Version is the version of the event's schema. Version string `json:"version"` @@ -76,6 +74,8 @@ type CodePipelineCloudWatchEvent struct { Detail CodePipelineEventDetail `json:"detail"` } +type CodePipelineEventBridgeEvent = CodePipelineCloudWatchEvent + type CodePipelineEventDetail struct { Pipeline string `json:"pipeline"` diff --git a/events/codepipeline_cloudwatch_test.go b/events/codepipeline_cloudwatch_test.go index e1e21748..13321980 100644 --- a/events/codepipeline_cloudwatch_test.go +++ b/events/codepipeline_cloudwatch_test.go @@ -2,10 +2,11 @@ package events import ( "encoding/json" - "github.com/stretchr/testify/require" "io/ioutil" //nolint: staticcheck "testing" "time" + + "github.com/stretchr/testify/require" ) func TestUnmarshalCodePipelineEvent(t *testing.T) { From 501c2f378153015a4a72f0b1a8e89dcb0bd3762a Mon Sep 17 00:00:00 2001 From: KIDANI Akito Date: Sat, 6 Jan 2024 09:30:01 +0900 Subject: [PATCH 6/9] Add S3ObjectLambdaEvent (#536) * Add S3ObjectLambdaEvent * Update S3 Object Lambda sample code * Avoid the generic name of Configuration * Follow stylecheck * Add S3ObjectLambda prefix * fix Url to URL --------- Co-authored-by: Bryan Moffatt --- events/README_S3_Object_Lambda.md | 83 +++++++++++++++++++ events/s3_object_lambda.go | 64 ++++++++++++++ events/s3_object_lambda_test.go | 44 ++++++++++ ...-lambda-event-get-object-assumed-role.json | 42 ++++++++++ ...s3-object-lambda-event-get-object-iam.json | 29 +++++++ ...3-object-lambda-event-head-object-iam.json | 27 ++++++ ...-object-lambda-event-list-objects-iam.json | 27 ++++++ ...ject-lambda-event-list-objects-v2-iam.json | 27 ++++++ 8 files changed, 343 insertions(+) create mode 100644 events/README_S3_Object_Lambda.md create mode 100644 events/s3_object_lambda.go create mode 100644 events/s3_object_lambda_test.go create mode 100644 events/testdata/s3-object-lambda-event-get-object-assumed-role.json create mode 100644 events/testdata/s3-object-lambda-event-get-object-iam.json create mode 100644 events/testdata/s3-object-lambda-event-head-object-iam.json create mode 100644 events/testdata/s3-object-lambda-event-list-objects-iam.json create mode 100644 events/testdata/s3-object-lambda-event-list-objects-v2-iam.json diff --git a/events/README_S3_Object_Lambda.md b/events/README_S3_Object_Lambda.md new file mode 100644 index 00000000..2166cb91 --- /dev/null +++ b/events/README_S3_Object_Lambda.md @@ -0,0 +1,83 @@ +# Sample Function + +The following is a sample class and Lambda function that receives Amazon S3 Object Lambda event record data as an input and returns an object metadata output. + +```go + +// main.go +package main + +import ( + "context" + "crypto/md5" + "encoding/hex" + "encoding/json" + "io/ioutil" + "net/http" + "github.com/aws/aws-lambda-go/lambda" + "github.com/aws/aws-lambda-go/events" + "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/service/s3" +) + +func handler(ctx context.Context, event events.S3ObjectLambdaEvent) error { + url := event.GetObjectContext.InputS3Url + resp, err := http.Get(url) + if err != nil { + return err + } + defer resp.Body.Close() + bodyBytes, err := ioutil.ReadAll(resp.Body) + if err != nil { + return err + } + transformedObject := TransformedObject{ + Metadata: Metadata{ + Length: len(bodyBytes), + Md5: toMd5(bodyBytes), + }, + } + jsonData, err := json.Marshal(transformedObject) + if err != nil { + return err + } + cfg, err := config.LoadDefaultConfig(context.TODO()) + if err != nil { + return err + } + svc := s3.NewFromConfig(cfg) + input := &s3.WriteGetObjectResponseInput{ + RequestRoute: &event.GetObjectContext.OutputRoute, + RequestToken: &event.GetObjectContext.OutputToken, + Body: strings.NewReader(string(jsonData)), + } + res, err := svc.WriteGetObjectResponse(ctx, input) + if err != nil { + return err + } + fmt.Printf("%v", res) + return nil +} + +func toMd5(data []byte) string { + hasher := md5.New() + hasher.Write(data) + hash := hasher.Sum(nil) + + return hex.EncodeToString(hash) +} + +type TransformedObject struct { + Metadata Metadata `json:"metadata"` +} + +type Metadata struct { + Length int `json:"length"` + Md5 string `json:"md5"` +} + +func main() { + lambda.Start(handler) +} + +``` diff --git a/events/s3_object_lambda.go b/events/s3_object_lambda.go new file mode 100644 index 00000000..47a28b04 --- /dev/null +++ b/events/s3_object_lambda.go @@ -0,0 +1,64 @@ +package events + +type S3ObjectLambdaEvent struct { + XAmzRequestID string `json:"xAmzRequestId"` + GetObjectContext *S3ObjectLambdaGetObjectContext `json:"getObjectContext,omitempty"` + ListObjectsContext *S3ObjectLambdaListObjectsContext `json:"listObjectsContext,omitempty"` + ListObjectsV2Context *S3ObjectLambdaListObjectsV2Context `json:"listObjectsV2Context,omitempty"` + HeadObjectContext *S3ObjectLambdaHeadObjectContext `json:"headObjectContext,omitempty"` + Configuration S3ObjectLambdaConfiguration `json:"configuration"` + UserRequest S3ObjectLambdaUserRequest `json:"userRequest"` + UserIdentity S3ObjectLambdaUserIdentity `json:"userIdentity"` + ProtocolVersion string `json:"protocolVersion"` +} + +type S3ObjectLambdaGetObjectContext struct { + InputS3URL string `json:"inputS3Url"` + OutputRoute string `json:"outputRoute"` + OutputToken string `json:"outputToken"` +} + +type S3ObjectLambdaListObjectsContext struct { + InputS3URL string `json:"inputS3Url"` +} + +type S3ObjectLambdaListObjectsV2Context struct { + InputS3URL string `json:"inputS3Url"` +} + +type S3ObjectLambdaHeadObjectContext struct { + InputS3URL string `json:"inputS3Url"` +} + +type S3ObjectLambdaConfiguration struct { + AccessPointARN string `json:"accessPointArn"` + SupportingAccessPointARN string `json:"supportingAccessPointArn"` + Payload string `json:"payload"` +} + +type S3ObjectLambdaUserRequest struct { + URL string `json:"url"` + Headers map[string]string `json:"headers"` +} + +type S3ObjectLambdaUserIdentity struct { + Type string `json:"type"` + PrincipalID string `json:"principalId"` + ARN string `json:"arn"` + AccountID string `json:"accountId"` + AccessKeyID string `json:"accessKeyId"` + SessionContext *S3ObjectLambdaSessionContext `json:"sessionContext,omitempty"` +} + +type S3ObjectLambdaSessionContext struct { + Attributes map[string]string `json:"attributes"` + SessionIssuer *S3ObjectLambdaSessionIssuer `json:"sessionIssuer,omitempty"` +} + +type S3ObjectLambdaSessionIssuer struct { + Type string `json:"type"` + PrincipalID string `json:"principalId"` + ARN string `json:"arn"` + AccountID string `json:"accountId"` + UserName string `json:"userName"` +} diff --git a/events/s3_object_lambda_test.go b/events/s3_object_lambda_test.go new file mode 100644 index 00000000..66e40ac7 --- /dev/null +++ b/events/s3_object_lambda_test.go @@ -0,0 +1,44 @@ +package events + +import ( + "encoding/json" + "testing" + + "github.com/aws/aws-lambda-go/events/test" + "github.com/stretchr/testify/assert" +) + +func TestS3ObjectLambdaEventMarshaling(t *testing.T) { + tests := []struct { + file string + }{ + {"./testdata/s3-object-lambda-event-get-object-iam.json"}, + {"./testdata/s3-object-lambda-event-get-object-assumed-role.json"}, + {"./testdata/s3-object-lambda-event-head-object-iam.json"}, + {"./testdata/s3-object-lambda-event-list-objects-iam.json"}, + {"./testdata/s3-object-lambda-event-list-objects-v2-iam.json"}, + } + + for _, tc := range tests { + tc := tc + t.Run(tc.file, func(t *testing.T) { + inputJSON := test.ReadJSONFromFile(t, tc.file) + + var inputEvent S3ObjectLambdaEvent + if err := json.Unmarshal(inputJSON, &inputEvent); err != nil { + t.Errorf("could not unmarshal event. details: %v", err) + } + + outputJSON, err := json.Marshal(inputEvent) + if err != nil { + t.Errorf("could not marshal event. details: %v", err) + } + + assert.JSONEq(t, string(inputJSON), string(outputJSON)) + }) + } +} + +func TestS3ObjectLambdaMarshalingMalformedJson(t *testing.T) { + test.TestMalformedJson(t, S3ObjectLambdaEvent{}) +} diff --git a/events/testdata/s3-object-lambda-event-get-object-assumed-role.json b/events/testdata/s3-object-lambda-event-get-object-assumed-role.json new file mode 100644 index 00000000..34aa55b1 --- /dev/null +++ b/events/testdata/s3-object-lambda-event-get-object-assumed-role.json @@ -0,0 +1,42 @@ +{ + "xAmzRequestId": "requestId", + "getObjectContext": { + "inputS3Url": "https://my-s3-ap-111122223333.s3-accesspoint.us-east-1.amazonaws.com/example?X-Amz-Security-Token=", + "outputRoute": "io-use1-001", + "outputToken": "OutputToken" + }, + "configuration": { + "accessPointArn": "arn:aws:s3-object-lambda:us-east-1:111122223333:accesspoint/example-object-lambda-ap", + "supportingAccessPointArn": "arn:aws:s3:us-east-1:111122223333:accesspoint/example-ap", + "payload": "{}" + }, + "userRequest": { + "url": "https://object-lambda-111122223333.s3-object-lambda.us-east-1.amazonaws.com/example", + "headers": { + "Host": "object-lambda-111122223333.s3-object-lambda.us-east-1.amazonaws.com", + "Accept-Encoding": "identity", + "X-Amz-Content-SHA256": "e3b0c44298fc1example" + } + }, + "userIdentity": { + "type": "AssumedRole", + "principalId": "principalId", + "arn": "arn:aws:sts::111122223333:assumed-role/Admin/example", + "accountId": "111122223333", + "accessKeyId": "accessKeyId", + "sessionContext": { + "attributes": { + "mfaAuthenticated": "false", + "creationDate": "Wed Mar 10 23:41:52 UTC 2021" + }, + "sessionIssuer": { + "type": "Role", + "principalId": "principalId", + "arn": "arn:aws:iam::111122223333:role/Admin", + "accountId": "111122223333", + "userName": "Admin" + } + } + }, + "protocolVersion": "1.00" +} \ No newline at end of file diff --git a/events/testdata/s3-object-lambda-event-get-object-iam.json b/events/testdata/s3-object-lambda-event-get-object-iam.json new file mode 100644 index 00000000..5f415a2e --- /dev/null +++ b/events/testdata/s3-object-lambda-event-get-object-iam.json @@ -0,0 +1,29 @@ +{ + "xAmzRequestId": "requestId", + "getObjectContext": { + "inputS3Url": "https://my-s3-ap-111122223333.s3-accesspoint.us-east-1.amazonaws.com/example?X-Amz-Security-Token=", + "outputRoute": "io-use1-001", + "outputToken": "OutputToken" + }, + "configuration": { + "accessPointArn": "arn:aws:s3-object-lambda:us-east-1:111122223333:accesspoint/example-object-lambda-ap", + "supportingAccessPointArn": "arn:aws:s3:us-east-1:111122223333:accesspoint/example-ap", + "payload": "{}" + }, + "userRequest": { + "url": "https://object-lambda-111122223333.s3-object-lambda.us-east-1.amazonaws.com/example", + "headers": { + "Host": "object-lambda-111122223333.s3-object-lambda.us-east-1.amazonaws.com", + "Accept-Encoding": "identity", + "X-Amz-Content-SHA256": "e3b0c44298fc1example" + } + }, + "userIdentity": { + "type": "IAMUser", + "principalId": "principalId", + "arn": "arn:aws:iam::111122223333:user/username", + "accountId": "111122223333", + "accessKeyId": "accessKeyId" + }, + "protocolVersion": "1.00" +} \ No newline at end of file diff --git a/events/testdata/s3-object-lambda-event-head-object-iam.json b/events/testdata/s3-object-lambda-event-head-object-iam.json new file mode 100644 index 00000000..85822dc7 --- /dev/null +++ b/events/testdata/s3-object-lambda-event-head-object-iam.json @@ -0,0 +1,27 @@ +{ + "xAmzRequestId": "requestId", + "headObjectContext": { + "inputS3Url": "https://my-s3-ap-111122223333.s3-accesspoint.us-east-1.amazonaws.com/example?X-Amz-Security-Token=" + }, + "configuration": { + "accessPointArn": "arn:aws:s3-object-lambda:us-east-1:111122223333:accesspoint/example-object-lambda-ap", + "supportingAccessPointArn": "arn:aws:s3:us-east-1:111122223333:accesspoint/example-ap", + "payload": "{}" + }, + "userRequest": { + "url": "https://object-lambda-111122223333.s3-object-lambda.us-east-1.amazonaws.com/example", + "headers": { + "Host": "object-lambda-111122223333.s3-object-lambda.us-east-1.amazonaws.com", + "Accept-Encoding": "identity", + "X-Amz-Content-SHA256": "e3b0c44298fc1example" + } + }, + "userIdentity": { + "type": "IAMUser", + "principalId": "principalId", + "arn": "arn:aws:iam::111122223333:user/username", + "accountId": "111122223333", + "accessKeyId": "accessKeyId" + }, + "protocolVersion": "1.01" +} \ No newline at end of file diff --git a/events/testdata/s3-object-lambda-event-list-objects-iam.json b/events/testdata/s3-object-lambda-event-list-objects-iam.json new file mode 100644 index 00000000..2a08f998 --- /dev/null +++ b/events/testdata/s3-object-lambda-event-list-objects-iam.json @@ -0,0 +1,27 @@ +{ + "xAmzRequestId": "requestId", + "listObjectsContext": { + "inputS3Url": "https://my-s3-ap-111122223333.s3-accesspoint.us-east-1.amazonaws.com/example?X-Amz-Security-Token=" + }, + "configuration": { + "accessPointArn": "arn:aws:s3-object-lambda:us-east-1:111122223333:accesspoint/example-object-lambda-ap", + "supportingAccessPointArn": "arn:aws:s3:us-east-1:111122223333:accesspoint/example-ap", + "payload": "{}" + }, + "userRequest": { + "url": "https://object-lambda-111122223333.s3-object-lambda.us-east-1.amazonaws.com/example", + "headers": { + "Host": "object-lambda-111122223333.s3-object-lambda.us-east-1.amazonaws.com", + "Accept-Encoding": "identity", + "X-Amz-Content-SHA256": "e3b0c44298fc1example" + } + }, + "userIdentity": { + "type": "IAMUser", + "principalId": "principalId", + "arn": "arn:aws:iam::111122223333:user/username", + "accountId": "111122223333", + "accessKeyId": "accessKeyId" + }, + "protocolVersion": "1.01" +} \ No newline at end of file diff --git a/events/testdata/s3-object-lambda-event-list-objects-v2-iam.json b/events/testdata/s3-object-lambda-event-list-objects-v2-iam.json new file mode 100644 index 00000000..41d95d09 --- /dev/null +++ b/events/testdata/s3-object-lambda-event-list-objects-v2-iam.json @@ -0,0 +1,27 @@ +{ + "xAmzRequestId": "requestId", + "listObjectsV2Context": { + "inputS3Url": "https://my-s3-ap-111122223333.s3-accesspoint.us-east-1.amazonaws.com/example?X-Amz-Security-Token=" + }, + "configuration": { + "accessPointArn": "arn:aws:s3-object-lambda:us-east-1:111122223333:accesspoint/example-object-lambda-ap", + "supportingAccessPointArn": "arn:aws:s3:us-east-1:111122223333:accesspoint/example-ap", + "payload": "{}" + }, + "userRequest": { + "url": "https://object-lambda-111122223333.s3-object-lambda.us-east-1.amazonaws.com/example", + "headers": { + "Host": "object-lambda-111122223333.s3-object-lambda.us-east-1.amazonaws.com", + "Accept-Encoding": "identity", + "X-Amz-Content-SHA256": "e3b0c44298fc1example" + } + }, + "userIdentity": { + "type": "IAMUser", + "principalId": "principalId", + "arn": "arn:aws:iam::111122223333:user/username", + "accountId": "111122223333", + "accessKeyId": "accessKeyId" + }, + "protocolVersion": "1.01" +} \ No newline at end of file From c5ef6f102099d5609798e2e40fa38dd2c787e32a Mon Sep 17 00:00:00 2001 From: Bryan Moffatt Date: Fri, 5 Jan 2024 16:37:56 -0800 Subject: [PATCH 7/9] 'Building your function' README updates (#543) * README updates * Update README.md --- README.md | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 23619e5e..18e9509c 100644 --- a/README.md +++ b/README.md @@ -43,16 +43,26 @@ func main() { # Building your function -Preparing a binary to deploy to AWS Lambda requires that it is compiled for Linux and placed into a .zip file. +Preparing a binary to deploy to AWS Lambda requires that it is compiled for Linux and placed into a .zip file. When using the `provided`, `provided.al2`, or `provided.al2023` runtime, the executable within the .zip file should be named `bootstrap`. Lambda's default architecture is `x86_64`, so when cross compiling from a non-x86 environment, the executable should be built with `GOARCH=amd64`. Likewise, if the Lambda function will be [configured to use ARM](https://docs.aws.amazon.com/lambda/latest/dg/foundation-arch.html), the executable should built with `GOARCH=arm64`. -## For developers on Linux and macOS ``` shell -# Remember to build your handler executable for Linux! -# When using the `provided.al2` runtime, the handler executable should be named `bootstrap` GOOS=linux GOARCH=amd64 go build -o bootstrap main.go zip lambda-handler.zip bootstrap ``` +## For developers on Linux + +On Linux, the Go compiler's default behavior is to link the output executable to the system libc for some standard library functionality (for example, DNS lookups). If the build environment is using a Linux distribution with a GNU libc version newer than the deployment environment, the application when deployed to Lambda may fail with an error like ``/lib64/libc.so.6: version `GLIBC_X.YZ' not found``. + +Most Go applications do not require linking to the system libc. This behavior can be disabled by using the `CGO_ENABLED` environment variable. + +``` +CGO_ENABLED=0 go build -o bootstrap main.go +zip lambda-handler.zip bootstrap +``` + +See [Using CGO](#using-cgo) + ## For developers on Windows Windows developers may have trouble producing a zip file that marks the binary as executable on Linux. To create a .zip that will work on AWS Lambda, the `build-lambda-zip` tool may be helpful. @@ -81,6 +91,20 @@ $env:CGO_ENABLED = "0" go build -o bootstrap main.go ~\Go\Bin\build-lambda-zip.exe -o lambda-handler.zip bootstrap ``` + +## Using CGO + +For applications that require CGO, the build environment must be using a GNU libc version installed compatible with the target Lambda runtime. Otherwise, execution may fail with errors like ``/lib64/libc.so.6: version `GLIBC_X.YZ' not found``. + +| Lambda runtime | GLIBC version +| ----- | --- +| `provided.al2023` | 2.34 +| `provided.al2` | 2.26 +| `provided` and `go1.x` | 2.17 + + +Alternatively, Lambda supports container images as a deployment package alternative to .zip files. For more information, refer to the official documentation for [working with with container images](https://docs.aws.amazon.com/lambda/latest/dg/images-create.html). + # Deploying your functions To deploy your function, refer to the official documentation for [deploying using the AWS CLI, AWS Cloudformation, and AWS SAM](https://docs.aws.amazon.com/lambda/latest/dg/deploying-lambda-apps.html). From cce08455c22b1075be4476b32b4e9ee785c38abc Mon Sep 17 00:00:00 2001 From: Bryan Moffatt Date: Fri, 5 Jan 2024 16:42:17 -0800 Subject: [PATCH 8/9] linter config: reformat the lists (#545) --- .golangci.yml | 52 +++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 2 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index dc8402d5..56449af7 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -19,5 +19,53 @@ run: linters-settings: stylecheck: go: "1.20" - checks: ["all"] - initialisms: ["AWS", "ARN", "IAM", "MQTT", "ACL", "API", "ASCII", "CPU", "CSS", "DNS", "EOF", "GUID", "HTML", "HTTP", "HTTPS", "ID", "IP", "JSON", "QPS", "RAM", "RPC", "SLA", "SMTP", "SQL", "SSH", "TCP", "TLS", "TTL", "UDP", "UI", "GID", "UID", "UUID", "URI", "URL", "UTF8", "VM", "XML", "XMPP", "XSRF", "XSS", "SIP", "RTP", "AMQP", "DB", "TS", "VPC"] + checks: + - "all" + initialisms: + - "ACL" + - "AMQP" + - "API" + - "ARN" + - "ASCII" + - "AWS" + - "CPU" + - "CSS" + - "DB" + - "DNS" + - "EOF" + - "GID" + - "GUID" + - "HTML" + - "HTTP" + - "HTTPS" + - "IAM" + - "ID" + - "IP" + - "JSON" + - "MQTT" + - "QPS" + - "RAM" + - "RPC" + - "RTP" + - "SIP" + - "SLA" + - "SMTP" + - "SQL" + - "SSH" + - "TCP" + - "TLS" + - "TS" + - "TTL" + - "UDP" + - "UI" + - "UID" + - "URI" + - "URL" + - "UTF8" + - "UUID" + - "VM" + - "VPC" + - "XML" + - "XMPP" + - "XSRF" + - "XSS" From 3a93ed1e265b5e368f0d20c3a2b8395d2c9f787d Mon Sep 17 00:00:00 2001 From: Luke Young <91491244+lyoung-confluent@users.noreply.github.com> Date: Thu, 11 Jan 2024 10:27:34 -0800 Subject: [PATCH 9/9] lambdaurl: Populate RemoteAddr with SourceIP (#546) --- lambdaurl/http_handler.go | 1 + 1 file changed, 1 insertion(+) diff --git a/lambdaurl/http_handler.go b/lambdaurl/http_handler.go index 79a63e18..53c5ba54 100644 --- a/lambdaurl/http_handler.go +++ b/lambdaurl/http_handler.go @@ -118,6 +118,7 @@ func Wrap(handler http.Handler) func(context.Context, *events.LambdaFunctionURLR if err != nil { return nil, err } + httpRequest.RemoteAddr = request.RequestContext.HTTP.SourceIP for k, v := range request.Headers { httpRequest.Header.Add(k, v) }