|
| 1 | +import json |
| 2 | +import logging |
| 3 | +import os |
| 4 | +import uuid |
| 5 | + |
| 6 | +import boto3 |
| 7 | +import cfnresponse |
| 8 | + |
| 9 | +RESOURCE_PROPERTIES = "ResourceProperties" |
| 10 | +SOURCE_BUCKET_NAME = "SOURCE_BUCKET_NAME" |
| 11 | +LOGGING_BUCKET_NAME = "LOGGING_BUCKET_NAME" |
| 12 | +PHYSICAL_RESOURCE_ID = "PhysicalResourceId" |
| 13 | +POLICY_SID = "customingestionAccessLogsPolicy" |
| 14 | +STATEMENT = "Statement" |
| 15 | + |
| 16 | +DEFAULT_LEVEL = "DEBUG" |
| 17 | + |
| 18 | +s3 = boto3.client("s3") |
| 19 | +logger = logging.getLogger(__name__) |
| 20 | +logger.setLevel(os.environ.get("LOG_LEVEL", DEFAULT_LEVEL)) |
| 21 | + |
| 22 | + |
| 23 | +def handler(event, context): |
| 24 | + logger.debug(f"The event is {event}") |
| 25 | + physical_resource_id = None |
| 26 | + |
| 27 | + try: |
| 28 | + physical_resource_id = event.get(PHYSICAL_RESOURCE_ID, uuid.uuid4().hex[:8]) |
| 29 | + |
| 30 | + if event["RequestType"] == "Create" or event["RequestType"] == "Update": |
| 31 | + source_bucket_name = event[RESOURCE_PROPERTIES][SOURCE_BUCKET_NAME] |
| 32 | + logging_bucket_name = event[RESOURCE_PROPERTIES][LOGGING_BUCKET_NAME] |
| 33 | + account_id = context.invoked_function_arn.split(":")[4] |
| 34 | + aws_partition = context.invoked_function_arn.split(":")[1] |
| 35 | + |
| 36 | + s3.put_public_access_block( |
| 37 | + Bucket=logging_bucket_name, |
| 38 | + PublicAccessBlockConfiguration={ |
| 39 | + "BlockPublicAcls": True, |
| 40 | + "IgnorePublicAcls": True, |
| 41 | + "BlockPublicPolicy": False, |
| 42 | + "RestrictPublicBuckets": True, |
| 43 | + }, |
| 44 | + ExpectedBucketOwner=account_id, |
| 45 | + ) |
| 46 | + |
| 47 | + policy_string = s3.get_bucket_policy(Bucket=logging_bucket_name)["Policy"] |
| 48 | + bucket_policy = json.loads(policy_string) |
| 49 | + |
| 50 | + update_policy = True |
| 51 | + |
| 52 | + for statement in bucket_policy[STATEMENT]: |
| 53 | + logger.debug(f"Statement is {json.dumps(statement)}") |
| 54 | + if "Sid" in statement and statement["Sid"] == POLICY_SID: |
| 55 | + if ( |
| 56 | + f"arn:{aws_partition}:s3:::{source_bucket_name}" |
| 57 | + not in statement["Condition"]["ArnLike"]["aws:SourceArn"] |
| 58 | + ): |
| 59 | + logger.info("Bucket policy exists but with a different bucket, hence removing it") |
| 60 | + bucket_policy[STATEMENT].remove(statement) |
| 61 | + break |
| 62 | + else: |
| 63 | + logger.info("Bucket policy exists, hence update not required") |
| 64 | + update_policy = false |
| 65 | + break |
| 66 | + |
| 67 | + if update_policy: |
| 68 | + bucket_policy[STATEMENT].append( |
| 69 | + { |
| 70 | + "Action": "s3:PutObject", |
| 71 | + "Condition": { |
| 72 | + "ArnLike": {"aws:SourceArn": [f"arn:{aws_partition}:s3:::{source_bucket_name}"]}, |
| 73 | + "StringEquals": {"aws:SourceAccount": account_id}, |
| 74 | + }, |
| 75 | + "Effect": "Allow", |
| 76 | + "Principal": {"Service": "logging.s3.amazonaws.com"}, |
| 77 | + "Resource": f"arn:{aws_partition}:s3:::{logging_bucket_name}/customingestion*", |
| 78 | + "Sid": POLICY_SID, |
| 79 | + } |
| 80 | + ) |
| 81 | + |
| 82 | + s3.put_bucket_policy(Bucket=logging_bucket_name, Policy=json.dumps(bucket_policy)) |
| 83 | + |
| 84 | + s3.put_public_access_block( |
| 85 | + Bucket=logging_bucket_name, |
| 86 | + PublicAccessBlockConfiguration={ |
| 87 | + "BlockPublicAcls": True, |
| 88 | + "IgnorePublicAcls": True, |
| 89 | + "BlockPublicPolicy": True, |
| 90 | + "RestrictPublicBuckets": True, |
| 91 | + }, |
| 92 | + ExpectedBucketOwner=account_id, |
| 93 | + ) |
| 94 | + logger.info("Policy Updated successfully. Sending success response to CloudFormation") |
| 95 | + |
| 96 | + cfnresponse.send(event, context, cfnresponse.SUCCESS, {}, physical_resource_id) |
| 97 | + except Exception as ex: |
| 98 | + logger.error(f"Failed to update policy, error is {str(ex)}") |
| 99 | + cfnresponse.send( |
| 100 | + event, context, cfnresponse.FAILED, {}, physicalResourceId=physical_resource_id, reason=str(ex) |
| 101 | + ) |
0 commit comments