diff --git a/03_Parameters/github_account_name.json b/03_Parameters/github_account_name.json new file mode 100644 index 0000000..71beb58 --- /dev/null +++ b/03_Parameters/github_account_name.json @@ -0,0 +1,7 @@ +{ + "ParamGitHubAccountName": { + "Description": "The name of the GitHub account, it is the same names that you find in the URL. Organization or private account.", + "Type": "String", + "Default": "0x4447" + } +} diff --git a/03_Parameters/github_token.json b/03_Parameters/github_token.json index 5fea529..d970881 100644 --- a/03_Parameters/github_token.json +++ b/03_Parameters/github_token.json @@ -1,6 +1,6 @@ { "GitHubToken": { - "Description": "You need to create a Personal access tokens (https://github.com/settings/tokens) for CodePipeline to have access to the GitHub repo despite it being public.", + "Description": "You need to create a Personal access tokens (https://github.com/settings/tokens) for CodePipeline to have access to the GitHub repo even if they are public, and the Scope has to have: repo and admin:repo_hook", "NoEcho": true, "Type": "String" } diff --git a/07_Resources/Repos/converter/CodePipeline/_index.json b/07_Resources/Repos/converter/CodePipeline/_index.json index 60d2b67..65788c5 100644 --- a/07_Resources/Repos/converter/CodePipeline/_index.json +++ b/07_Resources/Repos/converter/CodePipeline/_index.json @@ -22,7 +22,7 @@ "Version": "1" }, "Configuration": { - "Owner": "0x4447", + "Owner": { "Ref": "ParamGitHubAccountName" }, "Repo": "0x4447-product-s3-email-lambda-converter", "Branch": { "Ref": "Stage" }, "PollForSourceChanges": false, diff --git a/07_Resources/Repos/inbound/CodePipeline/_index.json b/07_Resources/Repos/inbound/CodePipeline/_index.json index b26d7c7..cbf2db6 100644 --- a/07_Resources/Repos/inbound/CodePipeline/_index.json +++ b/07_Resources/Repos/inbound/CodePipeline/_index.json @@ -22,7 +22,7 @@ "Version": "1" }, "Configuration": { - "Owner": "0x4447", + "Owner": { "Ref": "ParamGitHubAccountName" }, "Repo": "0x4447-product-s3-email-lambda-inbound", "Branch": { "Ref": "Stage" }, "PollForSourceChanges": false, diff --git a/07_Resources/Repos/outbound/CodePipeline/_index.json b/07_Resources/Repos/outbound/CodePipeline/_index.json index 7fcd3be..47fffee 100644 --- a/07_Resources/Repos/outbound/CodePipeline/_index.json +++ b/07_Resources/Repos/outbound/CodePipeline/_index.json @@ -22,7 +22,7 @@ "Version": "1" }, "Configuration": { - "Owner": "0x4447", + "Owner": { "Ref": "ParamGitHubAccountName" }, "Repo": "0x4447-product-s3-email-lambda-outbound", "Branch": { "Ref": "Stage" }, "PollForSourceChanges": false, diff --git a/CloudFormation.json b/CloudFormation.json index f5c3788..e369e5a 100644 --- a/CloudFormation.json +++ b/CloudFormation.json @@ -3,15 +3,20 @@ "Description": "This stack will create a solution where you get infinite emails using AWS SES and S3.", "Parameters": { "EmailRestingPlace": { - "Description": "The S3 bucket name where the emails will be stored when they come through AWS SES. (This bucket will be made for you)", + "Description": "The S3 bucket name where the emails will be stored when they come through AWS SES.", "Type": "String" }, "CodePipelineBucketName": { - "Description": "The S3 bucket name where CodePipeline will store the artifacts (this is needed only by CP to work, and pass task results to the next stage) - (This bucket needs to exist already in S3)", + "Description": "The S3 bucket name where CodePipeline will store the artifacts (this is needed only by CP to work, and pass task results to the next stage).", "Type": "String" }, + "ParamGitHubAccountName": { + "Description": "The name of the GitHub account, it is the same names that you find in the URL. Organization or private account.", + "Type": "String", + "Default": "0x4447" + }, "GitHubToken": { - "Description": "You need to create a Personal access tokens (https://github.com/settings/tokens) for CodePipeline to have access to the GitHub repo despite it being public.", + "Description": "You need to create a Personal access tokens (https://github.com/settings/tokens) for CodePipeline to have access to the GitHub repo even if they are public, and the Scope has to have: repo and admin:repo_hook", "NoEcho": true, "Type": "String" }, @@ -251,10 +256,10 @@ } } }, - "PipelineOutbound": { + "PipelineConverter": { "Type": "AWS::CodePipeline::Pipeline", "Properties": { - "Name": "0x4447_s3_email_lambda_outbound", + "Name": "0x4447_s3_email_lambda_converter", "ArtifactStore": { "Location": { "Ref": "CodePipelineBucketName" @@ -263,7 +268,7 @@ }, "RoleArn": { "Fn::GetAtt": [ - "PipelineOutboundRole", + "PipelineConverterRole", "Arn" ] }, @@ -281,12 +286,14 @@ "Version": "1" }, "Configuration": { - "Owner": "0x4447", - "Repo": "0x4447-product-s3-email-lambda-outbound", + "Owner": { + "Ref": "ParamGitHubAccountName" + }, + "Repo": "0x4447-product-s3-email-lambda-converter", "Branch": { "Ref": "Stage" }, - "PollForSourceChanges": false, + "PollForSourceChanges": true, "OAuthToken": { "Ref": "GitHubToken" } @@ -312,7 +319,7 @@ ], "Configuration": { "ProjectName": { - "Ref": "CodeBuildOutbound" + "Ref": "CodeBuildConverter" } }, "ActionTypeId": { @@ -327,13 +334,13 @@ ] } }, - "PipelineOutboundPolicy": { + "PipelineConverterPolicy": { "Type": "AWS::IAM::Policy", "Properties": { "PolicyName": "default", "Roles": [ { - "Ref": "PipelineOutboundRole" + "Ref": "PipelineConverterRole" } ], "PolicyDocument": { @@ -483,10 +490,10 @@ } } }, - "PipelineOutboundRole": { + "PipelineConverterRole": { "Type": "AWS::IAM::Role", "Properties": { - "RoleName": "0x4447_s3_email_codepipeline_outbound", + "RoleName": "0x4447_s3_email_codepipeline_converter", "AssumeRolePolicyDocument": { "Version": "2012-10-17", "Statement": [ @@ -501,149 +508,13 @@ } } }, - "PipelineOutboundWebhook": { - "Type": "AWS::CodePipeline::Webhook", - "Properties": { - "Authentication": "GITHUB_HMAC", - "AuthenticationConfiguration": { - "SecretToken": { - "Ref": "GitHubToken" - } - }, - "Filters": [ - { - "JsonPath": "$.ref", - "MatchEquals": "refs/heads/{Branch}" - } - ], - "TargetPipeline": { - "Ref": "PipelineOutbound" - }, - "TargetAction": "Material", - "TargetPipelineVersion": { - "Fn::GetAtt": [ - "PipelineOutbound", - "Version" - ] - }, - "RegisterWithThirdParty": true - } - }, - "LambdaEmailOutbound": { - "Type": "AWS::Lambda::Function", - "Description": "Take the JSON and convert it in to an raw email.", - "Properties": { - "FunctionName": "0x4447-s3-email-outbound", - "Code": { - "ZipFile": "exports.handler = async (event) => {return true;};" - }, - "Handler": "index.handler", - "MemorySize": 256, - "Role": { - "Fn::GetAtt": [ - "LambdaEmailOutboundRole", - "Arn" - ] - }, - "Runtime": "nodejs8.10", - "Timeout": 60, - "Environment": { - "Variables": { - "BUCKET": { - "Ref": "EmailRestingPlace" - } - } - } - } - }, - "S3OutboundPermission": { - "Type": "AWS::Lambda::Permission", - "DependsOn": "LambdaEmailOutbound", - "Properties": { - "Action": "lambda:InvokeFunction", - "FunctionName": { - "Ref": "LambdaEmailOutbound" - }, - "Principal": "s3.amazonaws.com" - } - }, - "LambdaEmailOutboundRole": { - "Type": "AWS::IAM::Role", - "Properties": { - "RoleName": "0x4447_s3_email_lambda_outbound", - "AssumeRolePolicyDocument": { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com" - }, - "Action": "sts:AssumeRole" - } - ] - }, - "ManagedPolicyArns": [ - "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" - ] - } - }, - "LambdaEmailOutboundPolicyS3": { - "Type": "AWS::IAM::Policy", - "Properties": { - "PolicyName": "S3_access", - "Roles": [ - { - "Ref": "LambdaEmailOutboundRole" - } - ], - "PolicyDocument": { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": "s3:*", - "Resource": [ - { - "Fn::Sub": "arn:aws:s3:::${S3Email}" - }, - { - "Fn::Sub": "arn:aws:s3:::${S3Email}/*" - } - ] - } - ] - } - } - }, - "LambdaEmailOutboundPolicySES": { - "Type": "AWS::IAM::Policy", - "Properties": { - "PolicyName": "ses", - "Roles": [ - { - "Ref": "LambdaEmailOutboundRole" - } - ], - "PolicyDocument": { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": "ses:SendRawEmail", - "Resource": "*" - } - ] - } - } - }, - "CodeBuildOutbound": { + "CodeBuildConverter": { "Type": "AWS::CodeBuild::Project", "Properties": { - "Name": "0x4447_s3_email_outbound", + "Name": "0x4447_s3_email_converter", "ServiceRole": { "Fn::GetAtt": [ - "CodeBuildOutboundRole", + "CodeBuildConverterRole", "Arn" ] }, @@ -664,17 +535,17 @@ "Name": "FUNCTION_NAME", "Type": "PLAINTEXT", "Value": { - "Ref": "LambdaEmailOutbound" + "Ref": "LambdaEmailConverter" } } ] } } }, - "CodeBuildOutboundRole": { + "CodeBuildConverterRole": { "Type": "AWS::IAM::Role", "Properties": { - "RoleName": "0x4447_s3_email_codebuild_outbound", + "RoleName": "0x4447_s3_email_codebuild_converter", "AssumeRolePolicyDocument": { "Version": "2012-10-17", "Statement": [ @@ -689,12 +560,12 @@ } } }, - "CodeBuildOutboundPolicyCWL": { + "CodeBuildConverterPolicyCWL": { "Type": "AWS::IAM::Policy", "Properties": { "Roles": [ { - "Ref": "CodeBuildOutboundRole" + "Ref": "CodeBuildConverterRole" } ], "PolicyName": "cloud_watch_log_access", @@ -710,10 +581,10 @@ ], "Resource": [ { - "Fn::Sub": "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/codebuild/0x4447_s3_email_outbound" + "Fn::Sub": "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/codebuild/0x4447_s3_email_converter" }, { - "Fn::Sub": "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/codebuild/0x4447_s3_email_outbound:*" + "Fn::Sub": "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/codebuild/0x4447_s3_email_converter:*" } ] } @@ -721,12 +592,12 @@ } } }, - "CodeBuildOutboundPolicyS3": { + "CodeBuildConverterPolicyS3": { "Type": "AWS::IAM::Policy", "Properties": { "Roles": [ { - "Ref": "CodeBuildOutboundRole" + "Ref": "CodeBuildConverterRole" } ], "PolicyName": "s3_access", @@ -744,12 +615,12 @@ } } }, - "CodeBuildOutboundPolicyLambdaUpdate": { + "CodeBuildConverterPolicyLambdaUpdate": { "Type": "AWS::IAM::Policy", "Properties": { "Roles": [ { - "Ref": "CodeBuildOutboundRole" + "Ref": "CodeBuildConverterRole" } ], "PolicyName": "update_lambda_code", @@ -760,17 +631,97 @@ "Effect": "Allow", "Action": "lambda:UpdateFunctionCode", "Resource": { - "Fn::Sub": "arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${LambdaEmailOutbound}" + "Fn::Sub": "arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${LambdaEmailConverter}" } } ] } } }, - "PipelineInbound": { + "LambdaEmailConverter": { + "Type": "AWS::Lambda::Function", + "Description": "This Lambda converts raw emails files in to HTML and TEXT ones.", + "Properties": { + "FunctionName": "0x4447-s3-email-converter", + "Code": { + "ZipFile": "exports.handler = async (event) => {return true;};" + }, + "Handler": "index.handler", + "MemorySize": 256, + "Role": { + "Fn::GetAtt": [ + "LambdaEmailConverterRole", + "Arn" + ] + }, + "Runtime": "nodejs8.10", + "Timeout": 60 + } + }, + "S3ConverterPermission": { + "Type": "AWS::Lambda::Permission", + "DependsOn": "LambdaEmailConverter", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Ref": "LambdaEmailConverter" + }, + "Principal": "s3.amazonaws.com" + } + }, + "LambdaEmailConverterRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "RoleName": "0x4447_s3_email_lambda_converter", + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + }, + "Action": "sts:AssumeRole" + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "LambdaEmailConverterPolicyS3": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyName": "S3_access", + "Roles": [ + { + "Ref": "LambdaEmailConverterRole" + } + ], + "PolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": "s3:*", + "Resource": [ + { + "Fn::Sub": "arn:aws:s3:::${S3Email}" + }, + { + "Fn::Sub": "arn:aws:s3:::${S3Email}/*" + } + ] + } + ] + } + } + }, + "PipelineOutbound": { "Type": "AWS::CodePipeline::Pipeline", "Properties": { - "Name": "0x4447_s3_email_lambda_inbound", + "Name": "0x4447_s3_email_lambda_outbound", "ArtifactStore": { "Location": { "Ref": "CodePipelineBucketName" @@ -779,7 +730,7 @@ }, "RoleArn": { "Fn::GetAtt": [ - "PipelineInboundRole", + "PipelineOutboundRole", "Arn" ] }, @@ -797,12 +748,14 @@ "Version": "1" }, "Configuration": { - "Owner": "0x4447", - "Repo": "0x4447-product-s3-email-lambda-inbound", + "Owner": { + "Ref": "ParamGitHubAccountName" + }, + "Repo": "0x4447-product-s3-email-lambda-outbound", "Branch": { "Ref": "Stage" }, - "PollForSourceChanges": false, + "PollForSourceChanges": true, "OAuthToken": { "Ref": "GitHubToken" } @@ -828,7 +781,7 @@ ], "Configuration": { "ProjectName": { - "Ref": "CodeBuildInbound" + "Ref": "CodeBuildOutbound" } }, "ActionTypeId": { @@ -843,13 +796,13 @@ ] } }, - "PipelineInboundPolicy": { + "PipelineOutboundPolicy": { "Type": "AWS::IAM::Policy", "Properties": { "PolicyName": "default", "Roles": [ { - "Ref": "PipelineInboundRole" + "Ref": "PipelineOutboundRole" } ], "PolicyDocument": { @@ -999,10 +952,10 @@ } } }, - "PipelineInboundRole": { + "PipelineOutboundRole": { "Type": "AWS::IAM::Role", "Properties": { - "RoleName": "0x4447_s3_email_codepipeline_inbound", + "RoleName": "0x4447_s3_email_codepipeline_outbound", "AssumeRolePolicyDocument": { "Version": "2012-10-17", "Statement": [ @@ -1017,41 +970,13 @@ } } }, - "PipelineInboundWebhook": { - "Type": "AWS::CodePipeline::Webhook", - "Properties": { - "Authentication": "GITHUB_HMAC", - "AuthenticationConfiguration": { - "SecretToken": { - "Ref": "GitHubToken" - } - }, - "Filters": [ - { - "JsonPath": "$.ref", - "MatchEquals": "refs/heads/{Branch}" - } - ], - "TargetPipeline": { - "Ref": "PipelineInbound" - }, - "TargetAction": "Material", - "TargetPipelineVersion": { - "Fn::GetAtt": [ - "PipelineInbound", - "Version" - ] - }, - "RegisterWithThirdParty": true - } - }, - "CodeBuildInbound": { + "CodeBuildOutbound": { "Type": "AWS::CodeBuild::Project", "Properties": { - "Name": "0x4447_s3_email_inbound", + "Name": "0x4447_s3_email_outbound", "ServiceRole": { "Fn::GetAtt": [ - "CodeBuildInboundRole", + "CodeBuildOutboundRole", "Arn" ] }, @@ -1072,17 +997,17 @@ "Name": "FUNCTION_NAME", "Type": "PLAINTEXT", "Value": { - "Ref": "LambdaEmailInbound" + "Ref": "LambdaEmailOutbound" } } ] } } }, - "CodeBuildInboundRole": { + "CodeBuildOutboundRole": { "Type": "AWS::IAM::Role", "Properties": { - "RoleName": "0x4447_s3_email_codebuild_inbound", + "RoleName": "0x4447_s3_email_codebuild_outbound", "AssumeRolePolicyDocument": { "Version": "2012-10-17", "Statement": [ @@ -1097,12 +1022,12 @@ } } }, - "CodeBuildDeployPolicyCWL": { + "CodeBuildOutboundPolicyCWL": { "Type": "AWS::IAM::Policy", "Properties": { "Roles": [ { - "Ref": "CodeBuildInboundRole" + "Ref": "CodeBuildOutboundRole" } ], "PolicyName": "cloud_watch_log_access", @@ -1118,10 +1043,10 @@ ], "Resource": [ { - "Fn::Sub": "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/codebuild/0x4447_s3_email_inbound" + "Fn::Sub": "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/codebuild/0x4447_s3_email_outbound" }, { - "Fn::Sub": "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/codebuild/0x4447_s3_email_inbound:*" + "Fn::Sub": "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/codebuild/0x4447_s3_email_outbound:*" } ] } @@ -1129,12 +1054,12 @@ } } }, - "CodeBuildDeployPolicyS3": { + "CodeBuildOutboundPolicyS3": { "Type": "AWS::IAM::Policy", "Properties": { "Roles": [ { - "Ref": "CodeBuildInboundRole" + "Ref": "CodeBuildOutboundRole" } ], "PolicyName": "s3_access", @@ -1152,12 +1077,12 @@ } } }, - "CodeBuildDeployPolicyLambdaUpdate": { + "CodeBuildOutboundPolicyLambdaUpdate": { "Type": "AWS::IAM::Policy", "Properties": { "Roles": [ { - "Ref": "CodeBuildInboundRole" + "Ref": "CodeBuildOutboundRole" } ], "PolicyName": "update_lambda_code", @@ -1168,39 +1093,18 @@ "Effect": "Allow", "Action": "lambda:UpdateFunctionCode", "Resource": { - "Fn::Sub": "arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${LambdaEmailInbound}" + "Fn::Sub": "arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${LambdaEmailOutbound}" } } ] } } }, - "LambdaEmailInboundRole": { - "Type": "AWS::IAM::Role", - "Properties": { - "RoleName": "0x4447_s3_email_lambda_inbound", - "AssumeRolePolicyDocument": { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com" - }, - "Action": "sts:AssumeRole" - } - ] - }, - "ManagedPolicyArns": [ - "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" - ] - } - }, - "LambdaEmailInbound": { + "LambdaEmailOutbound": { "Type": "AWS::Lambda::Function", - "Description": "This Lambda organizes all the incoming emails based on the From and To field.", + "Description": "Take the JSON and convert it in to an raw email.", "Properties": { - "FunctionName": "0x4447-s3-email-inbound", + "FunctionName": "0x4447-s3-email-outbound", "Code": { "ZipFile": "exports.handler = async (event) => {return true;};" }, @@ -1208,7 +1112,7 @@ "MemorySize": 256, "Role": { "Fn::GetAtt": [ - "LambdaEmailInboundRole", + "LambdaEmailOutboundRole", "Arn" ] }, @@ -1223,24 +1127,45 @@ } } }, - "S3InboundPermission": { + "S3OutboundPermission": { "Type": "AWS::Lambda::Permission", - "DependsOn": "LambdaEmailInbound", + "DependsOn": "LambdaEmailOutbound", "Properties": { "Action": "lambda:InvokeFunction", "FunctionName": { - "Ref": "LambdaEmailInbound" + "Ref": "LambdaEmailOutbound" }, "Principal": "s3.amazonaws.com" } }, - "LambdaEmailInboundPolicyS3": { + "LambdaEmailOutboundRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "RoleName": "0x4447_s3_email_lambda_outbound", + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + }, + "Action": "sts:AssumeRole" + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "LambdaEmailOutboundPolicyS3": { "Type": "AWS::IAM::Policy", "Properties": { "PolicyName": "S3_access", "Roles": [ { - "Ref": "LambdaEmailInboundRole" + "Ref": "LambdaEmailOutboundRole" } ], "PolicyDocument": { @@ -1262,13 +1187,13 @@ } } }, - "LambdaEmailInboundPolicySES": { + "LambdaEmailOutboundPolicySES": { "Type": "AWS::IAM::Policy", "Properties": { - "PolicyName": "ses_access", + "PolicyName": "ses", "Roles": [ { - "Ref": "LambdaEmailInboundRole" + "Ref": "LambdaEmailOutboundRole" } ], "PolicyDocument": { @@ -1276,17 +1201,17 @@ "Statement": [ { "Effect": "Allow", - "Action": "ses:ListIdentities", + "Action": "ses:SendRawEmail", "Resource": "*" } ] } } }, - "PipelineConverter": { + "PipelineInbound": { "Type": "AWS::CodePipeline::Pipeline", "Properties": { - "Name": "0x4447_s3_email_lambda_converter", + "Name": "0x4447_s3_email_lambda_inbound", "ArtifactStore": { "Location": { "Ref": "CodePipelineBucketName" @@ -1295,7 +1220,7 @@ }, "RoleArn": { "Fn::GetAtt": [ - "PipelineConverterRole", + "PipelineInboundRole", "Arn" ] }, @@ -1313,12 +1238,14 @@ "Version": "1" }, "Configuration": { - "Owner": "0x4447", - "Repo": "0x4447-product-s3-email-lambda-converter", + "Owner": { + "Ref": "ParamGitHubAccountName" + }, + "Repo": "0x4447-product-s3-email-lambda-inbound", "Branch": { "Ref": "Stage" }, - "PollForSourceChanges": false, + "PollForSourceChanges": true, "OAuthToken": { "Ref": "GitHubToken" } @@ -1344,7 +1271,7 @@ ], "Configuration": { "ProjectName": { - "Ref": "CodeBuildConverter" + "Ref": "CodeBuildInbound" } }, "ActionTypeId": { @@ -1359,13 +1286,13 @@ ] } }, - "PipelineConverterPolicy": { + "PipelineInboundPolicy": { "Type": "AWS::IAM::Policy", "Properties": { "PolicyName": "default", "Roles": [ { - "Ref": "PipelineConverterRole" + "Ref": "PipelineInboundRole" } ], "PolicyDocument": { @@ -1515,10 +1442,10 @@ } } }, - "PipelineConverterRole": { + "PipelineInboundRole": { "Type": "AWS::IAM::Role", "Properties": { - "RoleName": "0x4447_s3_email_codepipeline_converter", + "RoleName": "0x4447_s3_email_codepipeline_inbound", "AssumeRolePolicyDocument": { "Version": "2012-10-17", "Statement": [ @@ -1533,41 +1460,13 @@ } } }, - "PipelineConverterWebhook": { - "Type": "AWS::CodePipeline::Webhook", - "Properties": { - "Authentication": "GITHUB_HMAC", - "AuthenticationConfiguration": { - "SecretToken": { - "Ref": "GitHubToken" - } - }, - "Filters": [ - { - "JsonPath": "$.ref", - "MatchEquals": "refs/heads/{Branch}" - } - ], - "TargetPipeline": { - "Ref": "PipelineConverter" - }, - "TargetAction": "Material", - "TargetPipelineVersion": { - "Fn::GetAtt": [ - "PipelineConverter", - "Version" - ] - }, - "RegisterWithThirdParty": true - } - }, - "CodeBuildConverter": { + "CodeBuildInbound": { "Type": "AWS::CodeBuild::Project", "Properties": { - "Name": "0x4447_s3_email_converter", + "Name": "0x4447_s3_email_inbound", "ServiceRole": { "Fn::GetAtt": [ - "CodeBuildConverterRole", + "CodeBuildInboundRole", "Arn" ] }, @@ -1588,17 +1487,17 @@ "Name": "FUNCTION_NAME", "Type": "PLAINTEXT", "Value": { - "Ref": "LambdaEmailConverter" + "Ref": "LambdaEmailInbound" } } ] } } }, - "CodeBuildConverterRole": { + "CodeBuildInboundRole": { "Type": "AWS::IAM::Role", "Properties": { - "RoleName": "0x4447_s3_email_codebuild_converter", + "RoleName": "0x4447_s3_email_codebuild_inbound", "AssumeRolePolicyDocument": { "Version": "2012-10-17", "Statement": [ @@ -1613,12 +1512,12 @@ } } }, - "CodeBuildConverterPolicyCWL": { + "CodeBuildDeployPolicyCWL": { "Type": "AWS::IAM::Policy", "Properties": { "Roles": [ { - "Ref": "CodeBuildConverterRole" + "Ref": "CodeBuildInboundRole" } ], "PolicyName": "cloud_watch_log_access", @@ -1634,10 +1533,10 @@ ], "Resource": [ { - "Fn::Sub": "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/codebuild/0x4447_s3_email_converter" + "Fn::Sub": "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/codebuild/0x4447_s3_email_inbound" }, { - "Fn::Sub": "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/codebuild/0x4447_s3_email_converter:*" + "Fn::Sub": "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/codebuild/0x4447_s3_email_inbound:*" } ] } @@ -1645,57 +1544,57 @@ } } }, - "CodeBuildConverterPolicyLambdaUpdate": { + "CodeBuildDeployPolicyS3": { "Type": "AWS::IAM::Policy", "Properties": { "Roles": [ { - "Ref": "CodeBuildConverterRole" + "Ref": "CodeBuildInboundRole" } ], - "PolicyName": "update_lambda_code", + "PolicyName": "s3_access", "PolicyDocument": { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", - "Action": "lambda:UpdateFunctionCode", + "Action": "s3:*", "Resource": { - "Fn::Sub": "arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${LambdaEmailConverter}" + "Fn::Sub": "arn:aws:s3:::${CodePipelineBucketName}/*" } } ] } } }, - "CodeBuildConverterPolicyS3": { + "CodeBuildDeployPolicyLambdaUpdate": { "Type": "AWS::IAM::Policy", "Properties": { "Roles": [ { - "Ref": "CodeBuildConverterRole" + "Ref": "CodeBuildInboundRole" } ], - "PolicyName": "s3_access", + "PolicyName": "update_lambda_code", "PolicyDocument": { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", - "Action": "s3:*", + "Action": "lambda:UpdateFunctionCode", "Resource": { - "Fn::Sub": "arn:aws:s3:::${CodePipelineBucketName}/*" + "Fn::Sub": "arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${LambdaEmailInbound}" } } ] } } }, - "LambdaEmailConverter": { + "LambdaEmailInbound": { "Type": "AWS::Lambda::Function", - "Description": "This Lambda converts raw emails files in to HTML and TEXT ones.", + "Description": "This Lambda organizes all the incoming emails based on the From and To field.", "Properties": { - "FunctionName": "0x4447-s3-email-converter", + "FunctionName": "0x4447-s3-email-inbound", "Code": { "ZipFile": "exports.handler = async (event) => {return true;};" }, @@ -1703,29 +1602,36 @@ "MemorySize": 256, "Role": { "Fn::GetAtt": [ - "LambdaEmailConverterRole", + "LambdaEmailInboundRole", "Arn" ] }, "Runtime": "nodejs8.10", - "Timeout": 60 + "Timeout": 60, + "Environment": { + "Variables": { + "BUCKET": { + "Ref": "EmailRestingPlace" + } + } + } } }, - "S3ConverterPermission": { + "S3InboundPermission": { "Type": "AWS::Lambda::Permission", - "DependsOn": "LambdaEmailConverter", + "DependsOn": "LambdaEmailInbound", "Properties": { "Action": "lambda:InvokeFunction", "FunctionName": { - "Ref": "LambdaEmailConverter" + "Ref": "LambdaEmailInbound" }, "Principal": "s3.amazonaws.com" } }, - "LambdaEmailConverterRole": { + "LambdaEmailInboundRole": { "Type": "AWS::IAM::Role", "Properties": { - "RoleName": "0x4447_s3_email_lambda_converter", + "RoleName": "0x4447_s3_email_lambda_inbound", "AssumeRolePolicyDocument": { "Version": "2012-10-17", "Statement": [ @@ -1743,13 +1649,13 @@ ] } }, - "LambdaEmailConverterPolicyS3": { + "LambdaEmailInboundPolicyS3": { "Type": "AWS::IAM::Policy", "Properties": { "PolicyName": "S3_access", "Roles": [ { - "Ref": "LambdaEmailConverterRole" + "Ref": "LambdaEmailInboundRole" } ], "PolicyDocument": { @@ -1770,6 +1676,27 @@ ] } } + }, + "LambdaEmailInboundPolicySES": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyName": "ses_access", + "Roles": [ + { + "Ref": "LambdaEmailInboundRole" + } + ], + "PolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": "ses:ListIdentities", + "Resource": "*" + } + ] + } + } } } } \ No newline at end of file diff --git a/README.md b/README.md index a3a084d..b0795c9 100644 --- a/README.md +++ b/README.md @@ -4,27 +4,45 @@ This stack was created out of frustration due to the fact that to this day there The result is an unmanaged email server with unlimited email addresses that also offers the benefit of easily organizing messages by adding the `+` character to the email names. The `+` is converted to a `/`, which correlates to an object path in S3. +### Endless email addresses + +Once you add and confirm your domain with SES, you can put any string you want in front of the `@`, as long as it conforms to the email address standard. This means that you'll have endless email addresses at your disposal, and you'll be able to organize your life in a way never possible before. For example, you can give each service you sign up for its own special email: + +- facebook@example.com +- instagram@example.com +- linkedin@example.com +- etc. + ### Organizing with a + -When you sign up for online services, you can organize your emails with the `+` character in this way: +With that said, you can organize your emails with the `+` character in this way: -- social+facebook@example.com -- social+instagram@example.com -- social+linkedin@example.com +- accounts+social+facebook@example.com +- accounts+social+instagram@example.com +- accounts+social+linkedin@example.com +- accounts+travel+car+hertz@example.com +- accounts+travel+air+jetblue@example.com +- accounts+money+paypal@example.com - etc. -This groups all social emails in the `social` folder. The possibilities are endless. +When dealing with clients we came up with this folder structure: -### Endless email addresses +- clients+company_name+aws+account_name@example.com +- clients+company_name+stripe@example.com +- clients+company_name+sentry@example.com +- clients+company_name+heroku@example.com +- etc. -Once you add and confirm your domain with SES, you can put any string you want in front of the `@`, as long as it conforms to the email address standard. This means that you'll have endless email addresses at your disposal, and you'll be able to organize your life in a way never possible before. For example, you can give each service you sign up for its own special email: +For all sorts of alerts we like to group them like this + +- alarms+company_name+aws+account_name+alarm_type@example.com +- alarms+company_name+sentry+alarm_type@example.com -- facebook@example.com -- instagram@example.com -- linkedin@example.com - etc. -> Basically receive and send email with some skills. +This groups all emails in the corresponding folder by replacing the `+` with a `/` character which creates a folder structure in S3. The possibilities are endless. + +> Basically, receive and send email with some skills. # DISCLAIMER! @@ -65,7 +83,7 @@ The stack is set up in a such a way that any time new code is pushed to a select Keep in mind that when you deploy, everything may not work right out of the box. -### Confirm that you own the domain +### Confirm to SES that you own the domain You have to add your domain and confirm that you own it. Follow these steps to do so: @@ -87,7 +105,7 @@ Deployment creates SES `rule sets`. This should be enabled by default on fresh A ### Attach user to the IAM Group -After the stack is deployed you get a IAM Group with the bare minimum policy to allow to access the S3 Bucket with the emails. Use this group to give access to your IAM user to S3 Email. +After the stack is deployed you'll get a IAM Group with a policy attached that will give a user using it the bare minimum to access to the S3-Bucket to read and create emails. # SES Limitations @@ -101,10 +119,10 @@ There are two major limitations with SES: **Receiving email**: 1. An email comes to SES and and it gets stored in `TMP` S3 folder. -1. S3 will trigger the Inbound Lambda Function which will organize the email based on the `to`, `from` and `date` fields. In addition to that, the Lambda will read the domain added to SES, and will use that to determine if the email should land in the `Inbox` or `Sent` folder. If the `to` fields contains the domain from SES, it goes to the `Inbox`, if not, it is assumed the email was sent out. -1. The `Inbox` or `Sent` folder triggers another Lambda function that loads the raw email, converts it to a `.html` and `.txt` file, and stores it alongside the original message. +1. S3 will trigger the Inbound Lambda Function which will organize the email based on the `to`, `from` and `date` fields. In addition to that, the Lambda will read the domain(s) added to SES, and will use that data to determine if the email should land in the `Inbox` or `Sent` folder. If the `to` fields contains the domain from SES, it goes to the `Inbox`, if not, it is assumed the email was sent out. +1. The `Inbox` or `Sent` folder triggers another Lambda function that loads the raw email, converts it to a `.html` and `.txt` file, and stores it alongside the original message, while storing any attachments in the `attachments`. -In addition to this flow, when a new email comes in, a copy of it will be saved in the `Today` folder to show you which emails are new. The S3 bucket has a Life Cycle Policy and will delete any email older than one day from the `Today` folder. This way you always know what is new. +In addition to this flow, when a new email comes in, a copy of it will be saved in the `Today` folder to show you which emails are new. The S3 bucket has a Life Cycle Policy and will delete any email older than one day from the `Today` folder. This way you always know what's new. **Sending email**: