{
  "AWSTemplateFormatVersion": "2010-09-09",
  "Description": "AWS CloudFormation template to configure AWS Config",
  "Parameters": {
    "DeliveryChannelExists": {
		"Type": "String",
		"Description": "Do you have an existing AWS Config delivery channel?",
		"Default": "false",
		"AllowedValues": [
			"false",
			"true"
		]
	},
    "ResourceName": {
      "Type": "String",
      "Default": "ConfigS3PublicAccessTracker",
      "AllowedValues": [
        "ConfigS3PublicAccessTracker"
      ],
      "Description": "Name of Resources created."
    },
    "SNSTopic": {
      "Description": "SNS topic to send notifications for errant bucket policies",
      "Type": "String"
    },
    "EmailAddress": {
      "Description": "Email Address for sending SNS notifications for Config Events",
      "Type": "String"
    },
    "ConfigRuleNameRead": {
      "Type": "String",
      "Default": "s3-bucket-public-read-prohibited",
      "Description": "The name that you assign to the AWS Config rule.",
      "MinLength": "1",
      "ConstraintDescription": "This parameter is required."
    },
    "ConfigRuleNameWrite": {
      "Type": "String",
      "Default": "s3-bucket-public-write-prohibited",
      "Description": "The name that you assign to the AWS Config rule.",
      "MinLength": "1",
      "ConstraintDescription": "This parameter is required."
    }
  },
	"Conditions": {
		"CreateDeliveryChannel": {
			"Fn::Equals": [{
					"Ref": "DeliveryChannelExists"
				},
				"false"
			]
		}
	},
  "Resources": {
    "ConfigRecorder": {
		"Type": "AWS::Config::ConfigurationRecorder",
		"Properties": {
			"Name": "default",
			"RecordingGroup": {
				"ResourceTypes": [
					"AWS::S3::Bucket"
				]
			},
			"RoleARN": {
				"Fn::GetAtt": [
					"ConfigRoleRecorder",
					"Arn"
				]
			}
		}
	},
	"DeliveryChannel": {
		"Condition": "CreateDeliveryChannel",
		"Type": "AWS::Config::DeliveryChannel",
		"Properties": {
			"ConfigSnapshotDeliveryProperties": {
				"DeliveryFrequency": "Six_Hours"
			},
			"S3BucketName": {
				"Ref": "ConfigBucket"
			},
			"SnsTopicARN": {
				"Ref": "ConfigTopic"
			}
		}
	},
	"ConfigBucket": {
		"Type": "AWS::S3::Bucket"
	},
    "AWSConfigRuleForPublicReadBuckets": {
      "Type": "AWS::Config::ConfigRule",
      "Properties": {
        "ConfigRuleName": {
          "Ref": "ConfigRuleNameRead"
        },
        "Description": "Checks that your S3 buckets do not allow public read access. If an S3 bucket policy or bucket ACL allows public read access, the bucket is noncompliant.",
        "InputParameters": {},
        "Scope": {
          "ComplianceResourceTypes": [
            "AWS::S3::Bucket"
          ]
        },
        "Source": {
          "Owner": "AWS",
          "SourceIdentifier": "S3_BUCKET_PUBLIC_READ_PROHIBITED"
        }
      },
	  "DependsOn": [
		"ConfigRecorder"
		]
    },
    "AWSConfigRuleForPublicWriteBuckets": {
      "Type": "AWS::Config::ConfigRule",
      "Properties": {
        "ConfigRuleName": {
          "Ref": "ConfigRuleNameWrite"
        },
        "Description": "Checks that your S3 buckets do not allow public write access. If an S3 bucket policy or bucket ACL allows public write access, the bucket is noncompliant.",
        "InputParameters": {},
        "Scope": {
          "ComplianceResourceTypes": [
            "AWS::S3::Bucket"
          ]
        },
        "Source": {
          "Owner": "AWS",
          "SourceIdentifier": "S3_BUCKET_PUBLIC_WRITE_PROHIBITED"
        }
      },
	  "DependsOn": [
		"ConfigRecorder"
		]
    },
    "configRole": {
      "Type": "AWS::IAM::Role",
      "Properties": {
        "AssumeRolePolicyDocument": {
          "Version": "2012-10-17",
          "Statement": [{
            "Effect": "Allow",
            "Principal": {
              "Service": [
                "lambda.amazonaws.com"
              ]
            },
            "Action": [
              "sts:AssumeRole"
            ]
          }]
        },
        "Path": "/",
        "Policies": [{
          "PolicyName": "LambdaRoleConfigPolicy",
          "PolicyDocument": {
            "Version": "2012-10-17",
            "Statement": [{
                "Sid": "SNSPublish",
                "Effect": "Allow",
                "Action": [
                  "sns:Publish"
                ],
                "Resource": "*"
              },
              {
                "Sid": "S3GetBucketACLandPolicy",
                "Effect": "Allow",
                "Action": [
                  "s3:GetBucketAcl",
                  "s3:GetBucketPolicy",
                  "s3:GetObject"
                ],
                "Resource": "*"
              },
              {
                "Sid": "S3PutBucketACLAccess",
                "Effect": "Allow",
                "Action": "s3:PutBucketAcl",
                "Resource": "arn:aws:s3:::*"
              },
              {
                "Sid": "LambdaBasicExecutionAccess",
                "Effect": "Allow",
                "Action": [
                  "logs:CreateLogGroup",
                  "logs:CreateLogStream",
                  "logs:PutLogEvents"
                ],
                "Resource": "*"
              }
            ]
          }
        }]
      }
    },
    "ConfigOpenBucketEvent": {
      "Type": "AWS::Events::Rule",
      "Properties": {
        "Description": "S3BucketPublicReadWriteProhibited Event",
        "EventPattern": {
          "source": [
            "aws.config"
          ],
          "detail": {
            "requestParameters": {
              "evaluations": {
                "complianceType": [
                  "NON_COMPLIANT"
                ]
              }
            },
            "additionalEventData": {
              "managedRuleIdentifier": [
                "S3_BUCKET_PUBLIC_READ_PROHIBITED",
                "S3_BUCKET_PUBLIC_WRITE_PROHIBITED"
              ]
            }
          }
        },
        "State": "ENABLED",
        "Targets": [{
            "Arn": {
              "Fn::GetAtt": [
                "AWSConfigOpenAccessResponder",
                "Arn"
              ]
            },
            "Id": "AWSConfigOpenAccessResponder"
          },
          {
            "Arn": {
              "Ref": "OpenBucketAlertSNSTopic"
            },
            "Id": "OpenBucketAlertSNSTopic"
          }
        ]
      }
    },
	"ConfigTopic": {
		"Type": "AWS::SNS::Topic",
		"Properties": {
			"Subscription": [{
				"Endpoint": {
					"Ref": "EmailAddress"
				},
				"Protocol": "email"
			}]
		}
	},
	"ConfigTopicPolicy": {
		"Type": "AWS::SNS::TopicPolicy",
		"Properties": {
			"PolicyDocument": {
				"Id": "ConfigTopicPolicy",
				"Version": "2012-10-17",
				"Statement": [{
					"Effect": "Allow",
					"Principal": {
						"Service": "config.amazonaws.com"
					},
					"Action": "SNS:Publish",
					"Resource": "*"
				}]
			},
			"Topics": [{
					"Ref": "ConfigTopic"
				}
			]
		}
	},
    "OpenBucketAlertSNSTopic": {
      "Type": "AWS::SNS::Topic",
      "Properties": {
        "Subscription": [{
          "Endpoint": {
            "Ref": "EmailAddress"
          },
          "Protocol": "email"
        }]
      }
    },
    "OpenBucketAlertSNSTopicPolicy": {
      "Type": "AWS::SNS::TopicPolicy",
      "Properties": {
        "PolicyDocument": {
          "Id": "Id-Open-Bucket-Notifications",
          "Version": "2012-10-17",
          "Statement": [{
            "Sid": "Sid-Open-Bucket-Notifications",
            "Effect": "Allow",
            "Principal": {
              "Service": "events.amazonaws.com"
            },
            "Action": "sns:Publish",
            "Resource": "*"
          }]
        },
        "Topics": [{
          "Ref": "OpenBucketAlertSNSTopic"
        }]
      }
    },
    "AWSConfigOpenAccessResponder": {
      "Type": "AWS::Lambda::Function",
      "Properties": {
        "Handler": "index.lambda_handler",
        "Role": {
          "Fn::GetAtt": [
            "configRole",
            "Arn"
          ]
        },
        "Code": {
          "ZipFile": {"Fn::Join": ["\n", [
            "import boto3",
            "from botocore.exceptions import ClientError",
            "import json",
            "import os",
            "ACL_RD_WARNING = \"The S3 bucket ACL allows public read access.\"",
            "PLCY_RD_WARNING = \"The S3 bucket policy allows public read access.\"",
            "ACL_WRT_WARNING = \"The S3 bucket ACL allows public write access.\"",
            "PLCY_WRT_WARNING = \"The S3 bucket policy allows public write access.\"",
            "RD_COMBO_WARNING = ACL_RD_WARNING + PLCY_RD_WARNING",
            "WRT_COMBO_WARNING = ACL_WRT_WARNING + PLCY_WRT_WARNING",
            "def policyNotifier(bucketName, s3client):",
            "    try:",
            "        bucketPolicy = s3client.get_bucket_policy(Bucket = bucketName)",
            "        # notify that the bucket policy may need to be reviewed due to security concerns",
            "        sns = boto3.client('sns')",
            "        subject = \"Potential compliance violation in \" + bucketName + \" bucket policy\"",
            "        message = \"Potential bucket policy compliance violation. Please review: \" + json.dumps(bucketPolicy['Policy'])",
            "        # send SNS message with warning and bucket policy",
            "        response = sns.publish(",
            "            TopicArn = os.environ['TOPIC_ARN'],",
            "            Subject = subject,",
            "            Message = message",
            "        )",
            "    except ClientError as e:",
            "        # error caught due to no bucket policy",
            "        print(\"No bucket policy found; no alert sent.\")",
            "def lambda_handler(event, context):",
            "    # instantiate Amazon S3 client",
            "    s3 = boto3.client('s3')",
            "    resource = list(event['detail']['requestParameters']['evaluations'])[0]",
            "    bucketName = resource['complianceResourceId']",
            "    complianceFailure = event['detail']['requestParameters']['evaluations'][0]['annotation']",
            "    if(complianceFailure == ACL_RD_WARNING or complianceFailure == ACL_WRT_WARNING):",
            "        s3.put_bucket_acl(Bucket = bucketName, ACL = 'private')",
            "    elif(complianceFailure == PLCY_RD_WARNING or complianceFailure == PLCY_WRT_WARNING):",
            "        policyNotifier(bucketName, s3)",
            "    elif(complianceFailure == RD_COMBO_WARNING or complianceFailure == WRT_COMBO_WARNING):",
            "        s3.put_bucket_acl(Bucket = bucketName, ACL = 'private')",
            "        policyNotifier(bucketName, s3)",
            "    return 0  # done"
            ]]}
        },
        "Environment": {
          "Variables": {
            "TOPIC_ARN": {
              "Ref": "OpenBucketAlertSNSTopic"
            }
          }
        },
        "Description": "Securing Open S3 bucket access function",
        "FunctionName": "AWSConfigOpenAccessResponder",
        "Runtime": "python3.6",
        "Timeout": "3"
      }
    },
    "LambdaInvokePermission": {
      "Type": "AWS::Lambda::Permission",
      "Properties": {
        "FunctionName": {
          "Fn::GetAtt": [
            "AWSConfigOpenAccessResponder",
            "Arn"
          ]
        },
        "Action": "lambda:InvokeFunction",
        "Principal": "events.amazonaws.com",
        "SourceArn": {
          "Fn::GetAtt": [
            "ConfigOpenBucketEvent",
            "Arn"
          ]
        }
      }
    },
	"ConfigRoleRecorder": {
		"Type": "AWS::IAM::Role",
		"Properties": {
			"AssumeRolePolicyDocument": {
				"Version": "2012-10-17",
				"Statement": [{
					"Effect": "Allow",
					"Principal": {
						"Service": [
							"config.amazonaws.com"
						]
					},
					"Action": [
						"sts:AssumeRole"
					]
				}]
			},
			"ManagedPolicyArns": [
				"arn:aws:iam::aws:policy/service-role/AWSConfigRole"
			],
			"Policies": [{
				"PolicyName": "root",
				"PolicyDocument": {
					"Version": "2012-10-17",
					"Statement": [{
							"Effect": "Allow",
							"Action": "s3:GetBucketAcl",
							"Resource": {
								"Fn::Join": [
									"", [
										"arn:aws:s3:::",
										{
											"Ref": "ConfigBucket"
										}
									]
								]
							}
						},
						{
							"Effect": "Allow",
							"Action": "s3:PutObject",
							"Resource": {
								"Fn::Join": [
									"", [
										"arn:aws:s3:::",
										{
											"Ref": "ConfigBucket"
										},
										"/AWSLogs/",
										{
											"Ref": "AWS::AccountId"
										},
										"/*"
									]
								]
							},
							"Condition": {
								"StringEquals": {
									"s3:x-amz-acl": "bucket-owner-full-control"
								}
							}
						},
						{
							"Effect": "Allow",
							"Action": "config:Put*",
							"Resource": "*"
						}
					]
				}
			}]
		}
	}
  }
}
