Create CloudWatch Alarm to notify about setting a S3 object to public - amazon-web-services

I want to create on CloudWatch a metric filter and an alarm based on it to notify me about S3 events, specially when a file or a bucket is set to public. This is the metric filter I used to create the metric:
{ ($.eventSource = s3.amazonaws.com) && (($.eventName = PutBucketAcl)
|| ($.eventName = PutObjectAcl)) &&
(($.requestParameters.AccessControlPolicy.AccessControlList.Grant.Grantee.type
= Group ))}
I tested this pattern by putting the following Custom log data :
{
"Records": [
{
"eventVersion": "1.03",
"userIdentity": {
"type": "IAMUser",
"principalId": "111122223333",
"arn": "arn:aws:iam::111122223333:user/myUserName",
"accountId": "111122223333",
"accessKeyId": "AKIAIOSFODNN7EXAMPLE",
"userName": "myUserName"
},
"eventTime": "2015-08-26T20:46:31Z",
"eventSource": "s3.amazonaws.com",
"eventName": "DeleteBucketPolicy",
"awsRegion": "us-west-2",
"sourceIPAddress": "127.0.0.1",
"userAgent": "[]",
"requestParameters": {
"bucketName": "myawsbucket"
},
"responseElements": null,
"requestID": "47B8E8D397DCE7A6",
"eventID": "cdc4b7ed-e171-4cef-975a-ad829d4123e8",
"eventType": "AwsApiCall",
"recipientAccountId": "111122223333"
},
{
"eventVersion": "1.03",
"userIdentity": {
"type": "IAMUser",
"principalId": "111122223333",
"arn": "arn:aws:iam::111122223333:user/myUserName",
"accountId": "111122223333",
"accessKeyId": "AKIAIOSFODNN7EXAMPLE",
"userName": "myUserName"
},
"eventTime": "2015-08-26T20:46:31Z",
"eventSource": "s3.amazonaws.com",
"eventName": "PutBucketAcl",
"awsRegion": "us-west-2",
"sourceIPAddress": "",
"userAgent": "[]",
"requestParameters": {
"bucketName": "",
"AccessControlPolicy": {
"AccessControlList": {
"Grant": {
"Grantee": {
"xsi:type": "Group",
"xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance",
"ID": "d25639fbe9c19cd30a4c0f43fbf00e2d3f96400a9aa8dabfbbebe1906Example"
},
"Permission": "FULL_CONTROL"
}
},
"xmlns": "http://s3.amazonaws.com/doc/2006-03-01/",
"Owner": {
"ID": "d25639fbe9c19cd30a4c0f43fbf00e2d3f96400a9aa8dabfbbebe1906Example"
}
}
},
"responseElements": null,
"requestID": "BD8798EACDD16751",
"eventID": "607b9532-1423-41c7-b048-ec2641693c47",
"eventType": "AwsApiCall",
"recipientAccountId": "111122223333"
},
{
"eventVersion": "1.03",
"userIdentity": {
"type": "IAMUser",
"principalId": "111122223333",
"arn": "arn:aws:iam::111122223333:user/myUserName",
"accountId": "111122223333",
"accessKeyId": "AKIAIOSFODNN7EXAMPLE",
"userName": "myUserName"
},
"eventTime": "2015-08-26T20:46:31Z",
"eventSource": "s3.amazonaws.com",
"eventName": "GetBucketVersioning",
"awsRegion": "us-west-2",
"sourceIPAddress": "",
"userAgent": "[]",
"requestParameters": {
"bucketName": "myawsbucket"
},
"responseElements": null,
"requestID": "07D681279BD94AED",
"eventID": "f2b287f3-0df1-4961-a2f4-c4bdfed47657",
"eventType": "AwsApiCall",
"recipientAccountId": "111122223333"
}
]
}
I clicked Test Pattern and I get this message:
Results Found 0 matches out of 50 event(s) in the sample log.
Is the metric filter proper and correct ? I'm supposed to have one result but it is not coming up.

Calculating whether a policy is providing open access is quite complex, due to the many ways that rules can be specified in the Bucket Policy (for example, wildcards can provide access).
An easier approach would be to use the Amazon S3 Bucket Permissions check in Trusted Advisor:
Checks buckets in Amazon Simple Storage Service (Amazon S3) that have open access permissions or allow access to any authenticated AWS user.
You can then Monitor Trusted Advisor Check Results with Amazon CloudWatch Events.
However, that particular check is not included in the Free Tier for Trusted Advisor. You would need to be on a Support Plan for that check to operate.
The Amazon S3 console was also recently updated -- it now clearly shows any buckets with public permissions.

Related

How to switch roles on AWS Console while requiring sts:RoleSessionName?

I have two AWS accounts, A and B. I authenticate to account A using SAML, then to access account B I switch roles. The setup worked well until I tried to enforce that, when switching roles the users need to provide their AWS username as the RoleSessionName. When I require that, then switching roles using the AWS cli works fine, but switching roles in the AWS Console stops working.
Here's the role trust policy that works on both cli and console:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::84...10:root"
},
"Action": "sts:AssumeRole"
}
]
}
Enforcing that the RoleSessionName be the AWS username means to change the policy like this:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::84...10:root"
},
"Action": "sts:AssumeRole",
"Condition": {
"StringLike": {
"sts:RoleSessionName": "${aws:username}"
}
}
}
]
}
Note: if instead of "sts:RoleSessionName": "${aws:username}" I do "sts:RoleSessionName": "*", then I can switch roles on the AWS console, but I can't find a way to figure out a pattern that includes the user name and works. I tried *${aws:username}, ${aws:username}*, and *${aws:username}*.
Once I enforce the sts:RoleSessionName condition on the role trust policy, I can no longer switch roles on the AWS Console. What I see in the CloudTrail events (only visible on us-east-1 region), is that switching the role in the AWS Console is recorded as 2 events: a SwitchRole followed by a AssumeRole.
When I switch roles in the AWS console with the first policy, I see the two events in CloudTrail.
Successful SwitchRole event:
{
"eventVersion": "1.08",
"userIdentity": {
"type": "AssumedRole",
"principalId": "ARO...BYP:me#email.com",
"arn": "arn:aws:sts::84...10:assumed-role/my-saml-role/me#email.com",
"accountId": "84...10"
},
"eventTime": "2022-12-21T19:34:55Z",
"eventSource": "signin.amazonaws.com",
"eventName": "SwitchRole",
"awsRegion": "us-east-1",
"sourceIPAddress": "4...0",
"userAgent": "Mozilla/5.0 ...",
"requestParameters": null,
"responseElements": {
"SwitchRole": "Success"
},
"additionalEventData": {
"RedirectTo": "https://us-east-1.console.aws.amazon.com/cloudtrail/home?region=us-east-1#/events?ReadOnly=false",
"SwitchTo": "arn:aws:iam::13...20:role/my-role"
},
"eventID": "ef759d28-37cb-4ece-af7d-3d7c5326691d",
"readOnly": false,
"eventType": "AwsConsoleSignIn",
"managementEvent": true,
"recipientAccountId": "84...10",
"eventCategory": "Management",
"tlsDetails": {
"tlsVersion": "TLSv1.2",
"cipherSuite": "ECDHE-RSA-AES128-GCM-SHA256",
"clientProvidedHostHeader": "signin.aws.amazon.com"
}
}
Successful AssumeRole event:
{
"eventVersion": "1.08",
"userIdentity": {
"type": "AssumedRole",
"principalId": "ARO...BYP:me#email.com",
"arn": "arn:aws:sts::84...10:assumed-role/my-saml-role/me#email.com",
"accountId": "84...10",
"accessKeyId": "ASIA...OA",
"sessionContext": {
"sessionIssuer": {
"type": "Role",
"principalId": "AR...BYP",
"arn": "arn:aws:sts::84...10:assumed-role/my-saml-role",
"accountId": "84...10",
"userName": "my-saml-role"
},
"webIdFederationData": {},
"attributes": {
"creationDate": "2022-12-21T19:10:25Z",
"mfaAuthenticated": "false"
}
}
},
"eventTime": "2022-12-21T19:34:55Z",
"eventSource": "sts.amazonaws.com",
"eventName": "AssumeRole",
"awsRegion": "us-east-1",
"sourceIPAddress": "4...0",
"userAgent": "AWS Signin, aws-internal/3 aws-sdk-java/1.12.339 Linux/5.4.215-mr.86.metal1.x86_64 OpenJDK_64-Bit_Server_VM/25.352-b09 java/1.8.0_352 kotlin/1.3.72 vendor/Oracle_Corporation cfg/retry-mode/standard",
"requestParameters": {
"roleArn": "arn:aws:iam::13...20:role/my-role",
"roleSessionName": "me#email.com"
},
"responseElements": {
"credentials": {
"accessKeyId": "AS...FJ",
"sessionToken": "IQ...VMRCMyvsQ==",
"expiration": "Dec 21, 2022, 8:34:55 PM"
},
"assumedRoleUser": {
"assumedRoleId": "AR...PX:me#email.com",
"arn": "arn:aws:sts::13...20:assumed-role/my-role/me#email.com"
}
},
"requestID": "6d9b977a-e489-4261-97b5-48c0167d82ea",
"eventID": "1f7178bd-046b-4e20-b264-dd86b8753a45",
"readOnly": true,
"resources": [
{
"accountId": "13...20",
"type": "AWS::IAM::Role",
"ARN": "arn:aws:iam::13...20:role/my-role"
}
],
"eventType": "AwsApiCall",
"managementEvent": true,
"recipientAccountId": "84...10",
"sharedEventID": "e49297fc-883e-4160-86bc-d157bc58a3ee",
"eventCategory": "Management",
"tlsDetails": {
"tlsVersion": "TLSv1.2",
"cipherSuite": "ECDHE-RSA-AES128-GCM-SHA256",
"clientProvidedHostHeader": "sts.us-east-1.amazonaws.com"
}
}
When I enforce the sts:RoleSessionName requirement, then the SwitchRole event fails with error switchrole.error.invalidparams.
Failed SwitchRole event
{
"eventVersion": "1.08",
"userIdentity": {
"type": "AssumedRole",
"principalId": "AR..YP:me#email.com",
"arn": "arn:aws:sts::84...10:assumed-role/my-saml-role/me#email.com",
"accountId": "84...10"
},
"eventTime": "2022-12-21T19:25:23Z",
"eventSource": "signin.amazonaws.com",
"eventName": "SwitchRole",
"awsRegion": "us-east-1",
"sourceIPAddress": "4...0",
"userAgent": "Mozilla/5.0 ...",
"errorMessage": "switchrole.error.invalidparams",
"requestParameters": null,
"responseElements": {
"SwitchRole": "Failure"
},
"additionalEventData": {
"RedirectTo": "https://eu-central-1.console.aws.amazon.com/cloudtrail/home?region=eu-central-1#/events?ReadOnly=false",
"SwitchTo": "arn:aws:iam::13..20:role/my-role"
},
"eventID": "76de870e-7e75-43a9-a908-d34616022553",
"readOnly": false,
"eventType": "AwsConsoleSignIn",
"managementEvent": true,
"recipientAccountId": "84...10",
"eventCategory": "Management",
"tlsDetails": {
"tlsVersion": "TLSv1.2",
"cipherSuite": "ECDHE-RSA-AES128-GCM-SHA256",
"clientProvidedHostHeader": "signin.aws.amazon.com"
}
}
The AWS docs mentions that sts:RoleSessionName is available when assuming a role in the AWS Console.
sts:RoleSessionName Works with string operators.
Use this key to compare the session name that a principal specifies
when assuming a role with the value that is specified in the policy.
Availability – This key is present in the request when the principal
assumes the role using the AWS Management Console, any assume-role CLI
command, or any AWS STS AssumeRole API operation.
Src: https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_iam-condition-keys.html#condition-keys-sts

Determine the exact problem point of calling AWS Backup to AWS KMS in order to decrypt S3 bucket

Here is a log in AWS CloudTrail presumably indicating that
the AWS Backup service ("userAgent": "backup.amazonaws.com",)
tries to call the AWS KMS service("eventSource": "kms.amazonaws.com",)
in order to decrypt("eventName": "Decrypt",)
some S3 bucket("principalId": "AROAY6JAXY37VKF726QCA:AWS_BACKUP_S3_71C3048C",) and fails.
But there are no clues as to which particular bucket or KMS key caused the problem, I'd appreciate any advice on how to determine the exact problem point, thanks.
BTW the account contains hundreds of keys and buckets that are being back-up, so to determine the problem point in some manual way like, like researching each bucket/key, is very undesirable:)
{
"eventVersion": "1.08",
"userIdentity": {
"type": "AssumedRole",
"principalId": "AROAY6JAXY37VKF726QCA:AWS_BACKUP_S3_71C3048C",
"arn": "arn:aws:sts::****:assumed-role/AWSBackupDefaultServiceRole/AWS_BACKUP_S3_71C3048C",
"accountId": "****",
"accessKeyId": "ASIAY6JAXY37WHC7VFWN",
"sessionContext": {
"sessionIssuer": {
"type": "Role",
"principalId": "AROAY6JAXY37VKF726QCA",
"arn": "arn:aws:iam::****:role/service-role/AWSBackupDefaultServiceRole",
"accountId": "****",
"userName": "AWSBackupDefaultServiceRole"
},
"webIdFederationData": {},
"attributes": {
"creationDate": "2022-12-18T05:54:35Z",
"mfaAuthenticated": "false"
}
},
"invokedBy": "backup.amazonaws.com"
},
"eventTime": "2022-12-18T05:54:35Z",
"eventSource": "kms.amazonaws.com",
"eventName": "Decrypt",
"awsRegion": "eu-central-1",
"sourceIPAddress": "backup.amazonaws.com",
"userAgent": "backup.amazonaws.com",
"errorCode": "AccessDenied",
"errorMessage": "The ciphertext refers to a customer master key that does not exist, does not exist in this region, or you are not allowed to access.",
"requestParameters": null,
"responseElements": null,
"requestID": "660b1b90-66d9-4a2b-9ab1-4af4e195b71f",
"eventID": "ef6fe50b-67f9-46c6-bbf5-057de88c3c1c",
"readOnly": true,
"eventType": "AwsApiCall",
"managementEvent": true,
"recipientAccountId": "****",
"eventCategory": "Management"
}

Securely upload secrets to Secret Manager/Parameter Store

I caught a misstake I have made in the way I have been uploading secrets to Secrets Manager. Through using cloudformation I have been sending in the secret as a plain text parameter into the template. The secret itself never gets exposed in the cloudformation yaml file. However, the secret is exposed as a parameter in cloudformation. Hence, being able to read/describe the stack is enough to get the secret.
Did some digging and found this. They suggest creating the parameter store/secret manager using cdk or cloudformation and after which you upload the secret using SDK/CLI.
To my question: does the SDK and CLI give traces themselves? Meaning, have I just moved the problem. Shifted from exposing the secret in cloudformation to exposing it to cloudtrail or any other monitoring in AWS.
How can I securely upload my own secrets in combination with IaC, without manually using the AWS console. Is there a way to turn of logging for certain SDK/CLI calls?
Depending on your use case there are different options:
If you set up new resources and need to create a new secret, you can have the SecretsManager generate the secret for you. See CloudFormation docs for the Secret resource.
If you want to store an existing secret, the option with the separate API-call is a good suggestion. The only place this could in principle be recorded is CloudTrail, which records any API-Call, but I have confirmed, that the secret value is not stored in the PutSecretValue event record.
A CreateSecret event from CloudTrail:
{
"eventVersion": "1.08",
"userIdentity": {
"type": "IAMUser",
"principalId": "AIDA2BFBC5RB4SDFSDQDI",
"arn": "arn:aws:iam::123456789123:user/myself",
"accountId": "123456789123",
"accessKeyId": "ASIA2BFSDFSD5RBR4L2JB7T",
"userName": "myself",
"sessionContext": {
"sessionIssuer": {},
"webIdFederationData": {},
"attributes": {
"mfaAuthenticated": "true",
"creationDate": "2021-07-05T11:38:38Z"
}
}
},
"eventTime": "2021-07-05T11:39:46Z",
"eventSource": "secretsmanager.amazonaws.com",
"eventName": "CreateSecret",
"awsRegion": "eu-central-1",
"sourceIPAddress": "95.48.10.191",
"userAgent": "aws-internal/3 aws-sdk-java/1.11.1030 Linux/5.4.109-57.183.amzn2int.x86_64 OpenJDK_64-Bit_Server_VM/25.292-b10 java/1.8.0_292 vendor/Oracle_Corporation cfg/retry-mode/legacy",
"requestParameters": {
"name": "/demo",
"clientRequestToken": "5c59462b-d05c-4cfa-a224-a8d60f3edeff"
},
"responseElements": null,
"requestID": "6e61267a-ed8a-4383-8729-c33b8c217990",
"eventID": "23facc03-032c-4b24-bc36-d8f4e330445e",
"readOnly": false,
"eventType": "AwsApiCall",
"managementEvent": true,
"eventCategory": "Management",
"recipientAccountId": "123456789123",
"sessionCredentialFromConsole": "true"
}
A PutSecretValue event in CloudTrail:
{
"eventVersion": "1.08",
"userIdentity": {
"type": "IAMUser",
"principalId": "AIDA2BFSASB4SXNVRQDI",
"arn": "arn:aws:iam::123456789123:user/myself",
"accountId": "123456789123",
"accessKeyId": "ASIA2BFBSAWR4L2JB7T",
"userName": "myself",
"sessionContext": {
"sessionIssuer": {},
"webIdFederationData": {},
"attributes": {
"mfaAuthenticated": "true",
"creationDate": "2021-07-05T11:38:38Z"
}
}
},
"eventTime": "2021-07-05T11:40:09Z",
"eventSource": "secretsmanager.amazonaws.com",
"eventName": "PutSecretValue",
"awsRegion": "eu-central-1",
"sourceIPAddress": "11.11.190.191",
"userAgent": "aws-internal/3 aws-sdk-java/1.11.1030 Linux/5.4.109-57.183.amzn2int.x86_64 OpenJDK_64-Bit_Server_VM/25.292-b10 java/1.8.0_292 vendor/Oracle_Corporation cfg/retry-mode/legacy",
"requestParameters": {
"clientRequestToken": "61297703-b519-4e9e-8984-aacd40db826b",
"secretId": "/demo"
},
"responseElements": null,
"requestID": "97693f1b-f586-4641-af4c-b46d66fd27c1",
"eventID": "192f8959-3c51-40f5-8ca6-88f9075dc2a3",
"readOnly": false,
"eventType": "AwsApiCall",
"managementEvent": true,
"eventCategory": "Management",
"recipientAccountId": "123456789123",
"sessionCredentialFromConsole": "true"
}

How to fix AWS Config generating AccessDenied error?

I am trying to allow AWS Config to write to a non-public S3 bucket.
Based on the official documentation, I should have two policies assigned to the AWS role. However, It is not possible to add any policy to the service-linked role, neither to create a custom new service-linked role for AWS config.
As such, how can I stop receiving the S3 AccessDenied error without making the bucket public?
edit: here is the error log:
{
"eventVersion": "1.07",
"userIdentity": {
"type": "AssumedRole",
"principalId": "xxxxxxxxxxxxxxxxxxxxx:AWSConfig-BucketConfigCheck",
"arn": "arn:aws:sts::xxxxxxxxxxxx:assumed-role/AWSServiceRoleForConfig/AWSConfig-BucketConfigCheck",
"accountId": "xxxxxxxxxxxx",
"accessKeyId": "xxxxxxxxxxxxxxxxxxxx",
"sessionContext": {
"sessionIssuer": {
"type": "Role",
"principalId": "xxxxxxxxxxxxxxxxxxxxx",
"arn": "arn:aws:iam::xxxxxxxxxxxx:role/aws-service-role/config.amazonaws.com/AWSServiceRoleForConfig",
"accountId": "xxxxxxxxxxxx",
"userName": "AWSServiceRoleForConfig"
},
"attributes": {
"creationDate": "2020-04-30T00:43:57Z",
"mfaAuthenticated": "false"
}
},
"invokedBy": "AWS Internal"
},
"eventTime": "2020-04-30T00:43:57Z",
"eventSource": "s3.amazonaws.com",
"eventName": "PutObject",
"awsRegion": "eu-west-1",
"sourceIPAddress": "xxx.xxx.xxx.xxx",
"userAgent": "[AWSConfig]",
"errorCode": "AccessDenied",
"errorMessage": "Access Denied",
"requestParameters": {
"bucketName": "aws-config-bucket-xxxxxxxxxxxx",
"Host": "aws-config-bucket-xxxxxxxxxxxx.s3.eu-west-1.amazonaws.com",
"x-amz-acl": "bucket-owner-full-control",
"x-amz-server-side-encryption": "AES256",
"key": "AWSLogs/xxxxxxxxxxxx/Config/ConfigWritabilityCheckFile"
},
"responseElements": null,
"additionalEventData": {
"SignatureVersion": "SigV4",
"CipherSuite": "ECDHE-RSA-AES128-SHA",
"bytesTransferredIn": 0,
"AuthenticationMethod": "AuthHeader",
"x-amz-id-2": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=",
"bytesTransferredOut": 243
},
"requestID": "xxxxxxxxxxxxxxxx",
"eventID": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"readOnly": false,
"resources": [
{
"type": "AWS::S3::Object",
"ARN": "arn:aws:s3:::aws-config-bucket-xxxxxxxxxxxx/AWSLogs/xxxxxxxxxxxx/Config/ConfigWritabilityCheckFile"
},
{
"accountId": "xxxxxxxxxxxx",
"type": "AWS::S3::Bucket",
"ARN": "arn:aws:s3:::aws-config-bucket-xxxxxxxxxxxx"
}
],
"eventType": "AwsApiCall",
"managementEvent": false,
"recipientAccountId": "xxxxxxxxxxxx",
"vpcEndpointId": "vpce-xxxxxxxx",
"eventCategory": "Data"
}
I found the answer here: https://forums.aws.amazon.com/thread.jspa?threadID=314156
When AWS Config sends configuration information to an Amazon S3
bucket in another account, it first attempts to use the IAM role, but
this attempt fails if the access policy for the bucket does not grant
WRITE access to the IAM role. In this event, AWS Config sends the
information again, this time as the AWS Config service principal.
I checked my logs and there was an AWS Config service principal log, the same second as the AccessDenied, that was being accepted. Therefore, the error can be safely ignored. I have updated my Cloudwatch alarm to ignore it:
{($.errorCode="*UnauthorizedOperation") || (($.errorCode="AccessDenied*") && (($.userIdentity.type!="AssumedRole") || ($.userAgent!="[AWSConfig]")))}

AWS S3 automatic object key name normalization to lower case

AFAIK, object names on AWS S3 are always case sensitive and it is impossible to configure AWS S3 to be case insensitive.
So, is it possible to configure something like AWS Lambda in order to normalize uploaded file names to lower case? Or what is the best practice to perform this task with AWS S3?
Yes, this is easily done by having a Lambda function subscribe to your S3 PUT Event.
{
"Records": [
{
"eventVersion": "2.0",
"eventTime": "1970-01-01T00:00:00.000Z",
"requestParameters": {
"sourceIPAddress": "127.0.0.1"
},
"s3": {
"configurationId": "testConfigRule",
"object": {
"eTag": "0123456789abcdef0123456789abcdef",
"sequencer": "0A1B2C3D4E5F678901",
"key": "HappyFace.jpg",
"size": 1024
},
"bucket": {
"arn": bucketarn,
"name": "sourcebucket",
"ownerIdentity": {
"principalId": "EXAMPLE"
}
},
"s3SchemaVersion": "1.0"
},
"responseElements": {
"x-amz-id-2": "EXAMPLE123/5678abcdefghijklambdaisawesome/mnopqrstuvwxyzABCDEFGH",
"x-amz-request-id": "EXAMPLE123456789"
},
"awsRegion": "us-east-1",
"eventName": "ObjectCreated:Put",
"userIdentity": {
"principalId": "EXAMPLE"
},
"eventSource": "aws:s3"
}
]
}
You can then grab event.Records[0].s3.bucket.name and event.Records[0].s3.object.key to make a copyObject request to AWS
Once your file has been copied successfully, you can then delete the original file.
Just make sure your Lambda is configured for PUT events only, because if you set it to ALL events, both COPY and DELETE will also trigger your function, making you enter in an infinite recursion.