From 15d4b607619b3e13520a74cfeda46c98b6cab699 Mon Sep 17 00:00:00 2001 From: Mia Alarcon Chong Date: Tue, 5 Sep 2023 13:42:31 -0400 Subject: [PATCH] Changes to serverless pattern --- cloudtrail-lambda-dynamo-cdk/README.md | 39 ++++--- .../lib/lambda/object_tag_checker/Event.py | 12 ++ .../lib/lambda/object_tag_checker/index.py | 93 +++++++++++++++ .../lambda/object_tag_checker/objects_db.py | 30 +++++ .../src/lib/lambda/populate_dynamo/index.py | 108 +++++++----------- .../lib/lambda/resource_tag_checker/Event.py | 15 --- .../lib/lambda/resource_tag_checker/index.py | 85 -------------- .../resource_tag_checker/resources_db.py | 38 ------ .../src/lib/tag-compliance-stack.ts | 82 ++++++------- .../src/test/src.test.ts | 17 --- .../src/test/test_file.txt | 1 + 11 files changed, 242 insertions(+), 278 deletions(-) create mode 100644 cloudtrail-lambda-dynamo-cdk/src/lib/lambda/object_tag_checker/Event.py create mode 100644 cloudtrail-lambda-dynamo-cdk/src/lib/lambda/object_tag_checker/index.py create mode 100644 cloudtrail-lambda-dynamo-cdk/src/lib/lambda/object_tag_checker/objects_db.py delete mode 100644 cloudtrail-lambda-dynamo-cdk/src/lib/lambda/resource_tag_checker/Event.py delete mode 100644 cloudtrail-lambda-dynamo-cdk/src/lib/lambda/resource_tag_checker/index.py delete mode 100644 cloudtrail-lambda-dynamo-cdk/src/lib/lambda/resource_tag_checker/resources_db.py delete mode 100644 cloudtrail-lambda-dynamo-cdk/src/test/src.test.ts create mode 100644 cloudtrail-lambda-dynamo-cdk/src/test/test_file.txt diff --git a/cloudtrail-lambda-dynamo-cdk/README.md b/cloudtrail-lambda-dynamo-cdk/README.md index dbe6f8b02..125e90829 100644 --- a/cloudtrail-lambda-dynamo-cdk/README.md +++ b/cloudtrail-lambda-dynamo-cdk/README.md @@ -1,15 +1,14 @@ -# Check Resource Tag Compliance Using CloudTrail, DynamoDB, and Lambda +# Check S3 Object Tag Compliance Using CloudTrail, DynamoDB, and Lambda -This pattern shows how to leverage CloudTrail resource creation API calls to check for required tags and determine compliance. The resources used in this pattern include CloudTrail, S3, Lambda, and DynamoDB which are all deployed via CDK. From the CloudTrail logs stored in S3, the relevant resource creation events are populated into a DynamoDB table via Lambda. The items written into the table are then checked for the required tags to determine compliance. +This pattern shows how to leverage CloudTrail resource creation API calls to check for required S3 object tags and determine compliance. The resources used in this pattern include CloudTrail, S3, Lambda, and DynamoDB which are all deployed via CDK. From the CloudTrail logs stored in S3, PutObject events are populated into a DynamoDB table via Lambda. The items written into the table are then checked for the required tags to determine compliance. -Learn more about this pattern at Serverless Land Patterns:[Create an AWS account](https://serverlessland.com/patterns/cloudtrail-lambda-dynamodb-cdk) +Learn more about this pattern at Serverless Land Patterns: https://serverlessland.com/patterns/cloudtrail-lambda-dynamodb-cdk Important: this application uses various AWS services and there are costs associated with these services after the Free Tier usage - please see the [AWS Pricing page](https://aws.amazon.com/pricing/) for details. You are responsible for any AWS costs incurred. No warranty is implied in this example. ## Requirements -* [Create an AWS account](https://portal.aws.amazon.com/gp/aws/developer/registration/index.html) if you do not already have one and log in. The IAM user that you use must have sufficient permissions to make necessary AWS service calls and manage AWS -s. +* [Create an AWS account](https://portal.aws.amazon.com/gp/aws/developer/registration/index.html) if you do not already have one and log in. The IAM user that you use must have sufficient permissions to make necessary AWS service calls and manage AWS resources. * [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html) installed and configured * [Git Installed](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) * [Node.js installed](https://nodejs.org/en/download) @@ -29,6 +28,10 @@ s. ``` npm install ``` +1. Open the lambda function cloudtrail-lambda-dynamo-cdk/src/lib/lambda/object_tag_checker/index.py, and on line 17 replace the example keys with your required keys: + ``` + required_keys = {"Key1", "Key2", "Key3", "Key4"} + ``` 1. Use the following command to generate the AWS CloudFormation template for your CDK application: ``` cdk synth @@ -40,25 +43,31 @@ s. ## Testing Once the CDK stack has deployed successfully, you can take the following steps to ensure the pattern is working appropriately: -1. Using the AWS CLI, create a bucket using the following command: +1. Using the AWS CLI, upload the test_file.txt found in the src folder to the S3 bucket of your choosing using the following command: ``` - aws s3api create-bucket --bucket {bucket-name} --region {your AWS region} + aws s3 cp test/test_file.txt s3:///est/test_file1.txt ``` - If the S3 bucket creation was successful, you should receive the following response: + If the file upload was successful, you should receive the following response: ``` - { - "Location": "/{bucket-name}" - } + upload test/test_file.txt to s3:///test_file1.txt ``` - You can also open the AWS Management Console, navigate to S3, and confirm the created bucket is listed there. + You can also open the AWS Management Console, navigate to S3, and confirm the uploaded file is found in the S3 bucket you specified. + +1. Using the AWS CLI, upload and tag the test_file.txt found in the src folder to the S3 bucket of your choosing using the following command: + ``` + aws s3 cp test/test_file.txt s3:///test_file2.txt + aws s3api put-object-tagging \ + --bucket \ + --key test_file2.txt \ + --tagging '{"TagSet": [{ "Key": "Key1", "Value": "key1_value" },{ "Key": "Key2", "Value": "key2_value" },{ "Key": "Key3", "Value": "key3_value" },{ "Key": "Key4", "Value": "key4_value" }]}' -1. Navigate to DynamoDB + ``` -1. Select the table created for this pattern +1. In DynamoDB console, Navigate to DynamoDB, "s3-objects-table" table created for this pattern 1. Click 'Explore table items' -1. Within about 5 minutes or less, you should see a new item populated in the DynamoDB table specifying the ARN of the newly created bucket. The 'is_compliant' column should be set to 'false' since the bucket was created with no tags. +1. Within a couple of minutes, you should see two new items populated into the DynamoDB table specifying the ARN of the uploaded objects. The ‘is_compliant’ column should be set to ‘false’ for test_file1.txt since the object was uploaded with no tags and set to ‘true’ for test_file2.txt. ## Cleanup diff --git a/cloudtrail-lambda-dynamo-cdk/src/lib/lambda/object_tag_checker/Event.py b/cloudtrail-lambda-dynamo-cdk/src/lib/lambda/object_tag_checker/Event.py new file mode 100644 index 000000000..a8370655c --- /dev/null +++ b/cloudtrail-lambda-dynamo-cdk/src/lib/lambda/object_tag_checker/Event.py @@ -0,0 +1,12 @@ +class Event: + def __init__(self, object_arn, bucket_name, is_compliant, object_key): + self.object_arn = object_arn + self.bucket_name = bucket_name + self.is_compliant = is_compliant + self.object_key = object_key + self.tags = None + + def validate_compliance(self, required_keys): + result = self.tags.difference(required_keys) + if result: + return result \ No newline at end of file diff --git a/cloudtrail-lambda-dynamo-cdk/src/lib/lambda/object_tag_checker/index.py b/cloudtrail-lambda-dynamo-cdk/src/lib/lambda/object_tag_checker/index.py new file mode 100644 index 000000000..877141dee --- /dev/null +++ b/cloudtrail-lambda-dynamo-cdk/src/lib/lambda/object_tag_checker/index.py @@ -0,0 +1,93 @@ +import boto3 +import os +from objects_db import Objects +from Event import Event +from botocore.exceptions import ClientError +from boto3.dynamodb.types import TypeDeserializer + +deserializer = TypeDeserializer() +dynamodb = boto3.resource("dynamodb") +s3_client = boto3.client("s3") +table_name = os.environ["TABLE_NAME"] + +REQUIRED_KEYS = { + "Key1", + "Key2", + "Key3", + "Key4", +} # replace with required resource keys + + +def pass_items(event): + objects_to_check = [] + + for record in event["Records"]: + new_image = record["dynamodb"].get("NewImage") + python_data = new_image + print(python_data) + for key, value in python_data.items(): + if "S" in value: + python_data[key] = value["S"] + elif "BOOL" in value: + python_data[key] = value["BOOL"] + + python_data = Event(**python_data) + + objects_to_check.append(python_data) + + return objects_to_check + + +def add_to_set(tags: list): + tags_set = set() + for value in tags: + tags_set.add(value["Key"].strip()) + return tags_set + + +def update_keys(item): + resource_instance = Objects(dynamodb, table_name) + attributes = {"is_compliant": item.is_compliant} + resource_instance.add_key(item, attributes) + + +def check_compliance(objects): + for i in objects: + try: + tags_set = set() + print(i.bucket_name) + + response = s3_client.get_object_tagging( + Bucket=i.bucket_name, Key=i.object_key + ) + + tags = response["TagSet"] + + if tags: + tags_set = add_to_set(tags) + + i.tags = tags_set + is_not_compliant = i.validate_compliance(required_keys=REQUIRED_KEYS) + + if is_not_compliant or not i.tags: + # further action may be taken here if not compliant (EX: notify admins using SNS) + + i.is_compliant = False + print(i.object_key, "is not compliant") + else: + i.is_compliant = True + update_keys(i) + print(i.object_key, "is now compliant") + + except ClientError as err: + if err.response["Error"]["Code"] == "NoSuchTagSet": + print(i.resource_name, "has no tags or does not exist") + else: + print(err) + except Exception as err: + print(err) + + +def lambda_handler(event, context): + objects = pass_items(event) + return check_compliance(objects) \ No newline at end of file diff --git a/cloudtrail-lambda-dynamo-cdk/src/lib/lambda/object_tag_checker/objects_db.py b/cloudtrail-lambda-dynamo-cdk/src/lib/lambda/object_tag_checker/objects_db.py new file mode 100644 index 000000000..242f485a9 --- /dev/null +++ b/cloudtrail-lambda-dynamo-cdk/src/lib/lambda/object_tag_checker/objects_db.py @@ -0,0 +1,30 @@ +from botocore.exceptions import ClientError + + +class Objects: + def __init__(self, db_client, table_name): + self.dyn_client = db_client + self.table = self.dyn_client.Table(table_name) + + def add_key(self, item, attributes): + update_expression = "SET {}".format( + ",".join(f"#{key}=:{key}" for key in attributes) + ) + attribute_values = {f":{key}": value for key, value in attributes.items()} + attribute_names = {f"#{key}": key for key in attributes} + print(attribute_values) + + try: + response = self.table.update_item( + Key={ + "object_arn": item.object_arn, + }, + UpdateExpression=update_expression, + ExpressionAttributeValues=attribute_values, + ExpressionAttributeNames=attribute_names, + ) + return response + + except ClientError as err: + print(err) + raise \ No newline at end of file diff --git a/cloudtrail-lambda-dynamo-cdk/src/lib/lambda/populate_dynamo/index.py b/cloudtrail-lambda-dynamo-cdk/src/lib/lambda/populate_dynamo/index.py index 49c0f52c3..e02ecb2eb 100644 --- a/cloudtrail-lambda-dynamo-cdk/src/lib/lambda/populate_dynamo/index.py +++ b/cloudtrail-lambda-dynamo-cdk/src/lib/lambda/populate_dynamo/index.py @@ -1,87 +1,59 @@ import boto3 -import os -import json -import gzip from botocore.exceptions import ClientError +import gzip +import json +import os -dynamodb = boto3.resource('dynamodb') -table_name = os.environ['TABLE_NAME'] +dynamodb = boto3.resource("dynamodb") +table_name = os.environ["TABLE_NAME"] table = dynamodb.Table(table_name) -s3_client = boto3.client('s3') +s3_client = boto3.client("s3") + def lambda_handler(event, context): + print(event) + items_to_add = [] - bucket_name = os.environ['BUCKET_NAME'] - - for record in event['Records']: + cloudtrail_bucket = os.environ["CLOUDTRAIL_BUCKET_NAME"] + + for record in event["Records"]: try: - if record['s3']['bucket']['name'] != bucket_name: + bucket_name = record["s3"]["bucket"]["name"] + + if bucket_name != cloudtrail_bucket: continue - - object_key = record['s3']['object']['key'] - - response = s3_client.get_object(Bucket=bucket_name, Key=object_key) - log_data = gzip.decompress(response['Body'].read()).decode('utf-8') - cloudtrail_logs = json.loads(log_data)['Records'] - + + object_key = record["s3"]["object"]["key"] + response = s3_client.get_object(Bucket=cloudtrail_bucket, Key=object_key) + log_data = gzip.decompress(response["Body"].read()).decode("utf-8") + cloudtrail_logs = json.loads(log_data)["Records"] + for log in cloudtrail_logs: - event_name = log['eventName'] - event_id = log['eventID'] - event_source = log['eventSource'] - - if event_name == 'CreateBucket': - bucket_name = log['requestParameters']['bucketName'] - bucket_arn = f'arn:aws:s3:::{bucket_name}' - - item = { - 'resource_arn': bucket_arn, - 'event_id': event_id, - 'event_source': event_source, - 'resource_name': bucket_name, - 'is_compliant': False - } - items_to_add.append(item) - - if event_name == 'CreateCluster': - cluster_arn = log['responseElements']['cluster']['clusterArn'] - cluster_name = log['requestParameters']['clusterName'] - + event_name = log["eventName"] + user_identity = log["userIdentity"] + + if ( + user_identity.get("type") == "AWSService" + and user_identity.get("invokedBy") == "cloudtrail.amazonaws.com" + ): + continue + + if event_name == "PutObject": + bucket_name = log["requestParameters"]["bucketName"] + key = log["requestParameters"]["key"] + object_arn = f"arn:aws:s3:::{bucket_name}/{key}" item = { - 'resource_arn': cluster_arn, - 'event_id': event_id, - 'event_source': event_source, - 'resource_name': cluster_name, - 'is_compliant': False + "object_arn": object_arn, + "object_key": key, + "bucket_name": bucket_name, + "is_compliant": False, } items_to_add.append(item) - - if event_name.startswith('CreateFunction'): - function_name = log['requestParameters']['functionName'] - region = log['awsRegion'] - account_id = log['userIdentity']['accountId'] - function_arn = f'arn:aws:lambda:{region}:{account_id}:function:{function_name}' - - - item = { - 'resource_arn': function_arn, - 'event_id': event_id, - 'event_source': event_source, - 'resource_name': function_name, - 'is_compliant': False - } - - if not any(i['resource_arn'] == function_arn for i in items_to_add): - items_to_add.append(item) - except ClientError as err: print(err) - + if items_to_add: with table.batch_writer() as batch: for item in items_to_add: batch.put_item(Item=item) - - return { - 'statusCode': 200, - 'body': json.dumps('Success') - } \ No newline at end of file + return {"statusCode": 200, "body": json.dumps("Success")} \ No newline at end of file diff --git a/cloudtrail-lambda-dynamo-cdk/src/lib/lambda/resource_tag_checker/Event.py b/cloudtrail-lambda-dynamo-cdk/src/lib/lambda/resource_tag_checker/Event.py deleted file mode 100644 index 8b6c5b379..000000000 --- a/cloudtrail-lambda-dynamo-cdk/src/lib/lambda/resource_tag_checker/Event.py +++ /dev/null @@ -1,15 +0,0 @@ -class Event: - def __init__(self, resource_arn, event_id, event_source, resource_name, is_compliant): - self.resource_arn = resource_arn - self.event_id = event_id - self.event_source = event_source - self.resource_name = resource_name - self.is_compliant = is_compliant - self.service = self.event_source.split(".")[0] - self.tags = None - - - def validate_compliance(self, required_keys): - result = self.tags.difference(required_keys) - if result: - return result \ No newline at end of file diff --git a/cloudtrail-lambda-dynamo-cdk/src/lib/lambda/resource_tag_checker/index.py b/cloudtrail-lambda-dynamo-cdk/src/lib/lambda/resource_tag_checker/index.py deleted file mode 100644 index ed33a884f..000000000 --- a/cloudtrail-lambda-dynamo-cdk/src/lib/lambda/resource_tag_checker/index.py +++ /dev/null @@ -1,85 +0,0 @@ -import boto3 -import os -from resources_db import Resources -from Event import Event -from botocore.exceptions import ClientError - -dynamodb = boto3.resource('dynamodb') -s3_client = boto3.client('s3') -dax_client = boto3.client('dax') -lambda_client = boto3.client('lambda') -table_name = os.environ['TABLE_NAME'] - -def resources_items(): - db_instance = Resources(dynamodb, table_name) - response = db_instance.scan_resources() - - return response - -def pass_items(items): - resources_to_check = [] - for resource in items['Items']: - resource = Event(**resource) - print(resource) - resources_to_check.append(resource) - - return resources_to_check - -def add_toSet(tags: list): - tags_set = set() - for value in tags: - tags_set.add(value["Key"].strip()) - return tags_set - -def update_keys(item): - resource_instance = Resources(dynamodb, table_name) - attributes = {'is_compliant': item.is_compliant} - resource_instance.add_key(item, attributes) - -def check_compliance(objects): - required_keys = {"Key1", "Key2", "Key3", "Key4"} # replace with required resource keys - for i in objects: - try: - tags_set = set() - if i.service == 's3': - response = s3_client.get_bucket_tagging(Bucket=i.resource_name) - tags = response['TagSet'] - if tags: - tags_set = add_toSet(tags) - elif i.service == 'dax': - response = dax_client.list_tags(ResourceName=i.resource_arn) - tags = response['Tags'] - if tags: - tags_set = add_toSet(tags) - elif i.service == 'lambda': - response = lambda_client.list_tags(Resource=i.resource_arn) - tags = response['Tags'] - if tags: - for value in tags: - tags_set.add(value.strip()) - i.tags = tags_set - is_not_compliant = i.validate_compliance(required_keys = required_keys) - if is_not_compliant or not i.tags: - # further action may be taken here if not compliant (EX: notify admins using SNS) - i.is_compliant = False - print(i.resource_name, 'is not compliant') - else: - i.is_compliant = True - update_keys(i) - print(i.resource_name, 'is now compliant') - - except ClientError as err: - if err.response['Error']['Code'] == 'NoSuchTagSet': - print(i.resource_name,'has no tags or does not exist') - else: - print(err) - except Exception as err: - print(err) - -def lambda_handler(event=None, context=None): - items = resources_items() - objects = pass_items(items) - return check_compliance(objects) - - -print(lambda_handler()) \ No newline at end of file diff --git a/cloudtrail-lambda-dynamo-cdk/src/lib/lambda/resource_tag_checker/resources_db.py b/cloudtrail-lambda-dynamo-cdk/src/lib/lambda/resource_tag_checker/resources_db.py deleted file mode 100644 index f49c5ecec..000000000 --- a/cloudtrail-lambda-dynamo-cdk/src/lib/lambda/resource_tag_checker/resources_db.py +++ /dev/null @@ -1,38 +0,0 @@ -from botocore.exceptions import ClientError -from boto3.dynamodb.conditions import Attr -import logging -logger = logging.getLogger('dynamodb_resource') -class Resources: - def __init__(self, db_client, table_name): - self.dyn_client = db_client - self.table = self.dyn_client.Table(table_name) - - def scan_resources(self): - try: - response = self.table.scan(FilterExpression=Attr('is_compliant').eq(False)) - - return response - - except ClientError as err: - print(err) - raise - - def add_key(self, item, attributes): - update_expression = 'SET {}'.format(','.join(f'#{key}=:{key}' for key in attributes)) - attribute_values = {f':{key}': value for key, value in attributes.items()} - attribute_names = {f'#{key}': key for key in attributes} - - try: - response = self.table.update_item( - Key = { - "resource_arn": item.resource_arn, - }, - UpdateExpression = update_expression, - ExpressionAttributeValues = attribute_values, - ExpressionAttributeNames = attribute_names, - ) - return response - - except ClientError as err: - print(err) - raise \ No newline at end of file diff --git a/cloudtrail-lambda-dynamo-cdk/src/lib/tag-compliance-stack.ts b/cloudtrail-lambda-dynamo-cdk/src/lib/tag-compliance-stack.ts index 8dfd1ad84..7f44c405f 100644 --- a/cloudtrail-lambda-dynamo-cdk/src/lib/tag-compliance-stack.ts +++ b/cloudtrail-lambda-dynamo-cdk/src/lib/tag-compliance-stack.ts @@ -1,5 +1,6 @@ import * as cdk from 'aws-cdk-lib'; import { Construct } from 'constructs'; +import { Duration } from 'aws-cdk-lib'; import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; import * as s3 from 'aws-cdk-lib/aws-s3'; import * as cloudtrail from 'aws-cdk-lib/aws-cloudtrail'; @@ -11,24 +12,21 @@ export class TagComplianceStack extends cdk.Stack { constructor(scope: Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); - const region = cdk.Aws.REGION; - const accountId = cdk.Aws.ACCOUNT_ID; - - const table = new dynamodb.Table(this, 'resourceCreationTable', { - tableName: 'resource-creation-table', + const table = new dynamodb.Table(this, 's3ObjectsTable', { + tableName: 's3-objects-table', partitionKey: { - name: 'resource_arn', // name may be changed + name: 'object_arn', type: dynamodb.AttributeType.STRING }, stream: dynamodb.StreamViewType.NEW_IMAGE, - removalPolicy: cdk.RemovalPolicy.DESTROY // can be adjusted as deemed necessary + removalPolicy: cdk.RemovalPolicy.DESTROY }); // random generated number appended to bucket name, to create unique name - const generatedNum = Math.ceil(Math.random() * 10); + const generatedNum = Math.ceil(Math.random() * 100); - const bucket = new s3.Bucket(this, 'resourceCreationLogs', { - bucketName: `resource-creation-logs-${generatedNum}`, // name may be changed, need to make sure bucket name already doesn't exist + const cloudtrail_bucket = new s3.Bucket(this, 'objectCreationLogs', { + bucketName: `object-creation-logs-${generatedNum}`, // name may be changed, need to make sure bucket name doesn't already exist blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, encryption: s3.BucketEncryption.S3_MANAGED, removalPolicy: cdk.RemovalPolicy.DESTROY, @@ -43,13 +41,13 @@ export class TagComplianceStack extends cdk.Stack { effect: iam.Effect.ALLOW, principals: [cloudtrailPrincipal], actions:["s3:GetBucketAcl"], - resources:[bucket.bucketArn], + resources:[cloudtrail_bucket.bucketArn], }), new iam.PolicyStatement({ effect: iam.Effect.ALLOW, principals:[cloudtrailPrincipal], actions:["s3:PutObject"], - resources: [bucket.arnForObjects(`AWSLogs/*`)], + resources: [cloudtrail_bucket.arnForObjects(`AWSLogs/*`)], conditions: { "StringEquals": { "s3:x-amz-acl": "bucket-owner-full-control" @@ -60,17 +58,26 @@ export class TagComplianceStack extends cdk.Stack { // adding the IAM statements to the bucket policy for(const statement of bucket_policy_statements) { - bucket.addToResourcePolicy(statement); + cloudtrail_bucket.addToResourcePolicy(statement); } - new cloudtrail.Trail(this, 'resourceCreationTrail', { - trailName: 'resource-creation-trail', - bucket: bucket, + const trail = new cloudtrail.Trail(this, 'objectCreationTrail', { + trailName: 'object-creation-trail', + bucket: cloudtrail_bucket, isMultiRegionTrail: true, - includeGlobalServiceEvents: true, - managementEvents: cloudtrail.ReadWriteType.WRITE_ONLY + includeGlobalServiceEvents: true }); + + trail.addEventSelector( + cloudtrail.DataResourceType.S3_OBJECT, + ['arn:aws:s3:::'], + { + readWriteType: cloudtrail.ReadWriteType.WRITE_ONLY, + includeManagementEvents: false + } + ) + // function that gets cloudtrail events from the s3 bucket and populates to dynamo table const populateDynamoFn = new lambda.Function(this, 'populateDynamoFunction', { functionName: 'populate-dynamo', @@ -79,10 +86,10 @@ export class TagComplianceStack extends cdk.Stack { code: lambda.Code.fromAsset('lib/lambda/populate_dynamo'), environment: { 'TABLE_NAME': table.tableName, - 'BUCKET_NAME': bucket.bucketName + 'CLOUDTRAIL_BUCKET_NAME': cloudtrail_bucket.bucketName }, events: [ - new eventsources.S3EventSource(bucket, { + new eventsources.S3EventSource(cloudtrail_bucket, { events: [s3.EventType.OBJECT_CREATED] }) ] @@ -103,8 +110,8 @@ export class TagComplianceStack extends cdk.Stack { "s3:ListBucket" ], resources: [ - bucket.bucketArn, - bucket.bucketArn + "/*" + cloudtrail_bucket.bucketArn, + cloudtrail_bucket.bucketArn + "/*" ] }); @@ -113,51 +120,46 @@ export class TagComplianceStack extends cdk.Stack { populateDynamoFn.addToRolePolicy(lambdaS3Read); // function that checks the resources put into Dynamo for the tags specified in this function (go to function code to edit) - const resourceTagCheckerFn = new lambda.Function(this, 'resourceTagCheckerFunction', { - functionName: 'resource-tag-checker', + const objectTagCheckerFn = new lambda.Function(this, 'objectTagCheckerFunction', { + functionName: 'object-tag-checker', runtime: lambda.Runtime.PYTHON_3_10, handler: 'index.lambda_handler', - code: lambda.Code.fromAsset('lib/lambda/resource_tag_checker'), + code: lambda.Code.fromAsset('lib/lambda/object_tag_checker'), environment: { 'TABLE_NAME': table.tableName }, events: [ new eventsources.DynamoEventSource(table, { startingPosition: lambda.StartingPosition.LATEST, - batchSize: 100 + batchSize: 100, + retryAttempts: 1, + maxRecordAge: Duration.seconds(300) }) ] }); // allows lambda to get the tags for specified resources - const lambdaGetTags = new iam.PolicyStatement({ + const lambdaGetObjectTags = new iam.PolicyStatement({ effect: iam.Effect.ALLOW, actions: [ - "s3:GetBucketTagging", - "lambda:ListTags", - "dynamodb:ListTagsOfResource", - "dax:ListTags" + "s3:GetObjectTagging", ], resources: [ - `arn:aws:dynamodb:${region}:${accountId}:table/*`, 'arn:aws:s3:::*', - `arn:aws:lambda:${region}:${accountId}:function:*`, - `arn:aws:dax:${region}:${accountId}:cache/*` ] }); // allows lambda to scan and update the dynamodb table - const lambdaScanUpdateDynamo = new iam.PolicyStatement({ + const lambdaUpdateDynamo = new iam.PolicyStatement({ effect: iam.Effect.ALLOW, actions: [ - "dynamodb:Scan", "dynamodb:UpdateItem" ], resources: [table.tableArn] }); - resourceTagCheckerFn.addToRolePolicy(lambdaGetTags); - resourceTagCheckerFn.addToRolePolicy(lambdaScanUpdateDynamo); - resourceTagCheckerFn.role?.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaDynamoDBExecutionRole')); + objectTagCheckerFn.addToRolePolicy(lambdaGetObjectTags); + objectTagCheckerFn.addToRolePolicy(lambdaUpdateDynamo); + objectTagCheckerFn.role?.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaDynamoDBExecutionRole')); } -} +} \ No newline at end of file diff --git a/cloudtrail-lambda-dynamo-cdk/src/test/src.test.ts b/cloudtrail-lambda-dynamo-cdk/src/test/src.test.ts deleted file mode 100644 index 7d90b8da5..000000000 --- a/cloudtrail-lambda-dynamo-cdk/src/test/src.test.ts +++ /dev/null @@ -1,17 +0,0 @@ -// import * as cdk from 'aws-cdk-lib'; -// import { Template } from 'aws-cdk-lib/assertions'; -// import * as Src from '../lib/src-stack'; - -// example test. To run these tests, uncomment this file along with the -// example resource in lib/src-stack.ts -test('SQS Queue Created', () => { -// const app = new cdk.App(); -// // WHEN -// const stack = new Src.SrcStack(app, 'MyTestStack'); -// // THEN -// const template = Template.fromStack(stack); - -// template.hasResourceProperties('AWS::SQS::Queue', { -// VisibilityTimeout: 300 -// }); -}); diff --git a/cloudtrail-lambda-dynamo-cdk/src/test/test_file.txt b/cloudtrail-lambda-dynamo-cdk/src/test/test_file.txt new file mode 100644 index 000000000..7212ad5eb --- /dev/null +++ b/cloudtrail-lambda-dynamo-cdk/src/test/test_file.txt @@ -0,0 +1 @@ +Test file for S3 upload \ No newline at end of file