diff --git a/Master-Environment.template b/Master-Environment.template new file mode 100644 index 0000000..ab92858 --- /dev/null +++ b/Master-Environment.template @@ -0,0 +1,146 @@ +{ + "AWSTemplateFormatVersion": "2010-09-09", + "Description": "Role to be assumed by CodePipeline service cross account", + "Parameters": { + "S3BucketName": { + "Description": "S3 Bucket in CICD Account, which holds the CodePipeline artifacts.", + "Type": "String" + }, + "CicdAccount": { + "Description": "AWS AccountNumber for CICD Account.", + "Type": "Number" + }, + "KmsCmkArn": { + "Description": "ARN of the KMS CMK creates in CICD account.", + "Type": "String" + }, + "Environment": { + "Description": "Environment", + "Type": "String" + } + }, + "Resources": { + "IamRoleCodeBuild": { + "Type": "AWS::IAM::Role", + "Properties": { + "RoleName": { + "Fn::Sub": "${AWS::StackName}-CodeBuildRole" + }, + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "Service": "codebuild.amazonaws.com" + }, + "Action": [ + "sts:AssumeRole" + ] + }, + { + "Effect": "Allow", + "Principal": { + "AWS": [ + { + "Ref": "CicdAccount" + } + ] + }, + "Action": [ + "sts:AssumeRole" + ] + } + ] + }, + "Path": "/", + "ManagedPolicyArns": [ + { + "Ref": "IamPolicyBaseline" + } + ] + } + }, + "IamRoleCodePipeline": { + "Type": "AWS::IAM::Role", + "Properties": { + "RoleName": { + "Fn::Sub": "${AWS::StackName}-CodePipelineRole" + }, + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "AWS": [ + { + "Ref": "CicdAccount" + } + ] + }, + "Action": [ + "sts:AssumeRole" + ] + } + ] + }, + "Path": "/", + "ManagedPolicyArns": [ + { + "Ref": "IamPolicyBaseline" + } + ] + } + }, + "IamRoleCloudFormation": { + "Type": "AWS::IAM::Role", + "Properties": { + "RoleName": { + "Fn::Sub": "${AWS::StackName}-CloudFormationRole" + }, + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "Service": [ + "cloudformation.amazonaws.com" + ] + }, + "Action": [ + "sts:AssumeRole" + ] + } + ] + }, + "Path": "/", + "ManagedPolicyArns": [ + { + "Ref": "IamPolicyBaseline" + } + ] + } + }, + "IamPolicyBaseline": { + "Type": "AWS::IAM::ManagedPolicy", + "Properties": {. + "ManagedPolicyName": { + "Fn::Sub": "${AWS::StackName}-IamPolicyBaseline" + } + "PolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "Admin", + "Effect": "Allow", + "Action": "*", + "Resource": "*" + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/Master.template b/Master.template new file mode 100644 index 0000000..f031f38 --- /dev/null +++ b/Master.template @@ -0,0 +1,1048 @@ +{ + "AWSTemplateFormatVersion": "2010-09-09", + "Description": "Creates the build account's infrastructure that allows for cross-account CodePipelines.", + "Parameters": { + "CicdAccount": { + "Description": "AWS account number for cicd.", + "Type": "String" + }, + "DevAccount": { + "Description": "AWS account number for dev.", + "Type": "Number" + }, + "ProdAccount": { + "Description": "AWS account number for production.", + "Type": "Number" + }, + "SourceCodeCommitRepoName": { + "Description": "(Optional). Source CodeCommit repository. If not specified, one will be created.", + "Type": "String" + }, + "AllEnvironmentsCreated": { + "Description": "Must be false for initial creation of stacks. Afterwards it must be switched to true when all environments have been created.", + "Type": "String", + "Default": "False", + "AllowedValues": [ + "True", + "False" + ] + } + }, + "Conditions": { + "CreateCodeCommitRepo": { + "Fn::Equals": [ + "", + { + "Ref": "SourceCodeCommitRepoName" + } + ] + }, + "NotInitialCreation": { + "Fn::Equals": [ + { + "Ref": "AllEnvironmentsCreated" + }, + "True" + ] + } + }, + "Resources": { + "CodeCommit": { + "Type": "AWS::CodeCommit::Repository", + "Condition": "CreateCodeCommitRepo", + "Properties": { + "RepositoryDescription": "Repository created by CodePipeline", + "RepositoryName": { + "Ref": "AWS::StackName" + } + } + }, + "CodeBuildGenerateCICDTemplates": { + "Type": "AWS::CodeBuild::Project", + "Properties": { + "Artifacts": { + "Type": "CODEPIPELINE", + "OverrideArtifactName": true + }, + "EncryptionKey": { + "Fn::GetAtt": [ + "KmsKey", + "Arn" + ] + }, + "Environment": { + "ComputeType": "BUILD_GENERAL1_SMALL", + "Image": "aws/codebuild/standard:1.0", + "EnvironmentVariables": [ + { + "Name": "Environment", + "Type": "PLAINTEXT", + "Value": "cicd" + }, + { + "Name": "MasterInfraStack", + "Type": "PLAINTEXT", + "Value": { + "Fn::Sub": "cicd-${AWS::StackName}-infra" + } + }, + { + "Name": "S3Bucket", + "Type": "PLAINTEXT", + "Value": { + "Ref": "S3Bucket" + } + } + ], + "PrivilegedMode": false, + "Type": "LINUX_CONTAINER" + }, + "Name": { + "Fn::Sub": "${AWS::StackName}-generate-cicd-templates" + }, + "ServiceRole": { + "Fn::GetAtt": [ + "RoleCodeBuild", + "Arn" + ] + }, + "Source": { + "Type": "CODEPIPELINE", + "BuildSpec": "buildspec/generate-cicd-templates.yml" + }, + "TimeoutInMinutes": "5" + } + }, + "CodeBuildGenerateSDLCTemplates": { + "Type": "AWS::CodeBuild::Project", + "Properties": { + "Artifacts": { + "Type": "CODEPIPELINE", + "OverrideArtifactName": true + }, + "EncryptionKey": { + "Fn::GetAtt": [ + "KmsKey", + "Arn" + ] + }, + "Environment": { + "ComputeType": "BUILD_GENERAL1_SMALL", + "Image": "aws/codebuild/standard:1.0", + "EnvironmentVariables": [ + { + "Name": "Environment", + "Type": "PLAINTEXT", + "Value": "cicd" + }, + { + "Name": "MasterInfraStack", + "Type": "PLAINTEXT", + "Value": { + "Fn::Sub": "cicd-${AWS::StackName}-infra" + } + }, + { + "Name": "S3Bucket", + "Type": "PLAINTEXT", + "Value": { + "Ref": "S3Bucket" + } + } + ], + "PrivilegedMode": false, + "Type": "LINUX_CONTAINER" + }, + "Name": { + "Fn::Sub": "${AWS::StackName}-generate-sdlc-templates" + }, + "ServiceRole": { + "Fn::GetAtt": [ + "RoleCodeBuild", + "Arn" + ] + }, + "Source": { + "Type": "CODEPIPELINE", + "BuildSpec": "buildspec/generate-sdlc-templates.yml" + }, + "TimeoutInMinutes": "5" + } + }, + "CodeBuildUpdateStacks": { + "Type": "AWS::CodeBuild::Project", + "Properties": { + "Artifacts": { + "Type": "CODEPIPELINE", + "OverrideArtifactName": true + }, + "EncryptionKey": { + "Fn::GetAtt": [ + "KmsKey", + "Arn" + ] + }, + "Environment": { + "ComputeType": "BUILD_GENERAL1_SMALL", + "Image": "aws/codebuild/standard:1.0", + "EnvironmentVariables": [ + { + "Name": "Environment", + "Type": "PLAINTEXT", + "Value": "cicd" + }, + { + "Name": "MasterInfraStack", + "Type": "PLAINTEXT", + "Value": { + "Fn::Sub": "cicd-${AWS::StackName}-infra" + } + } + ], + "PrivilegedMode": false, + "Type": "LINUX_CONTAINER" + }, + "Name": { + "Fn::Sub": "${AWS::StackName}-update-stacks" + }, + "ServiceRole": { + "Fn::GetAtt": [ + "RoleCodeBuild", + "Arn" + ] + }, + "Source": { + "Type": "CODEPIPELINE", + "BuildSpec": "buildspec/update-stacks.yml" + }, + "TimeoutInMinutes": "5" + } + }, + "PolicyDeprecated": { + "Type": "AWS::IAM::ManagedPolicy", + "DependsOn": "S3Bucket", + "Properties": { + "PolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "AccessToS3ArtifactsBucket", + "Effect": "Allow", + "Action": [ + "s3:PutObject", + "s3:GetBucketPolicy", + "s3:GetObject", + "s3:ListBucket" + ], + "Resource": [ + { + "Fn::Sub": [ + "${S3BucketArn}/*", + { + "S3BucketArn": { + "Fn::GetAtt": [ + "S3Bucket", + "Arn" + ] + } + } + ] + }, + { + "Fn::GetAtt": [ + "S3Bucket", + "Arn" + ] + } + ] + }, + { + "Fn::If": [ + "NotInitialCreation", + { + "Sid": "AllowAssumeEnvironmentRoles", + "Effect": "Allow", + "Action": "sts:AssumeRole", + "Resource": [ + { + "Fn::Sub": "arn:aws:iam::${CicdAccount}:role/cicd-${AWS::StackName}-CloudFormationRole" + }, + { + "Fn::Sub": "arn:aws:iam::${CicdAccount}:role/cicd-${AWS::StackName}-CodePipelineRole" + }, + { + "Fn::Sub": "arn:aws:iam::${CicdAccount}:role/cicd-${AWS::StackName}-CodeBuildRole" + }, + { + "Fn::Sub": "arn:aws:iam::${DevAccount}:role/dev-${AWS::StackName}-CloudFormationRole" + }, + { + "Fn::Sub": "arn:aws:iam::${DevAccount}:role/dev-${AWS::StackName}-CodePipelineRole" + }, + { + "Fn::Sub": "arn:aws:iam::${DevAccount}:role/dev-${AWS::StackName}-CodeBuildRole" + }, + { + "Fn::Sub": "arn:aws:iam::${ProdAccount}:role/prod-${AWS::StackName}-CloudFormationRole" + }, + { + "Fn::Sub": "arn:aws:iam::${ProdAccount}:role/prod-${AWS::StackName}-CodePipelineRole" + }, + { + "Fn::Sub": "arn:aws:iam::${ProdAccount}:role/prod-${AWS::StackName}-CodeBuildRole" + } + ] + }, + { + "Ref": "AWS::NoValue" + } + ] + }, + { + "Sid": "UseKmsKey", + "Effect": "Allow", + "Action": [ + "kms:DescribeKey", + "kms:GenerateDataKey*", + "kms:Encrypt", + "kms:ReEncrypt*", + "kms:Decrypt" + ], + "Resource": [ + { + "Fn::GetAtt": [ + "KmsKey", + "Arn" + ] + } + ] + }, + { + "Sid": "CodeCommitRepos", + "Effect": "Allow", + "Action": [ + "codecommit:List*", + "codecommit:Get*", + "codecommit:GitPull", + "codecommit:UploadArchive", + "codecommit:CancelUploadArchive" + ], + "Resource": "*" + }, + { + "Sid": "CreateLogs", + "Effect": "Allow", + "Action": [ + "logs:CreateLogStream", + "logs:CreateLogGroup", + "logs:PutLogEvents" + ], + "Resource": "*" + }, + { + "Sid": "DescribeCloudFormationStacks", + "Effect": "Allow", + "Action": [ + "cloudformation:Describe*", + "cloudformation:ExecuteChangeSet" + ], + "Resource": "*" + }, + { + "Sid": "RunCodeBuild", + "Effect": "Allow", + "Action": [ + "codebuild:StartBuild", + "codebuild:BatchGetBuilds" + ], + "Resource": { + "Fn::Sub": "arn:aws:codebuild:*:${AWS::AccountId}:project/cicd-${AWS::StackName}*" + } + }, + { + "Sid": "SelfUpdateStack", + "Effect": "Allow", + "Action": [ + "sts:AssumeRole", + "iam:PassRole", + "iam:GetRole", + "iam:DeletePolicyVersion", + "cloudformation:UpdateStack", + "iam:ListPolicyVersions", + "iam:CreatePolicyVersion", + "iam:ListPolicyVersions", + "codepipeline:StartPipelineExecution" + ], + "Resource": + [ + { + "Fn::Sub": "arn:aws:iam::${AWS::AccountId}:role/${AWS::StackName}-CodePipelineRole*" + }, + { + "Fn::Sub": "arn:aws:iam::${AWS::AccountId}:policy/${AWS::StackName}-PolicyCodePipeline*" + }, + { + "Fn::Sub": "arn:aws:cloudformation:*:${AWS::AccountId}:stack/${AWS::StackName}" + }, + { + "Fn::Sub": "arn:aws:cloudformation:*:${AWS::AccountId}:stack/${AWS::StackName}/*" + }, + { + "Fn::Sub": "arn:aws:codepipeline:*:${AWS::AccountId}:${AWS::StackName}" + } + ] + } + ] + } + } + }, + "PolicyBaseline": { + "Type": "AWS::IAM::ManagedPolicy", + "DependsOn": "S3Bucket", + "Properties": { + "PolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "AdminAccess", + "Effect": "Allow", + "Action": "*", + "Resource": "*" + } + ] + } + } + }, + "RoleCodePipeline": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "Service": [ + "codepipeline.amazonaws.com" + ] + }, + "Action": "sts:AssumeRole" + } + ] + }, + "ManagedPolicyArns": [ + { + "Ref": "PolicyBaseline" + } + ], + "RoleName": { + "Fn::Sub": "${AWS::StackName}-CodePipelineRole" + } + } + }, + "RoleCloudFormation": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "Service": [ + "cloudformation.amazonaws.com" + ] + }, + "Action": "sts:AssumeRole" + } + ] + }, + "ManagedPolicyArns": [ + { + "Ref": "PolicyBaseline" + } + ], + "RoleName": { + "Fn::Sub": "${AWS::StackName}-CloudFormationRole" + } + } + }, + "RoleCodeBuild": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "Service": [ + "codebuild.amazonaws.com", + "codepipeline.amazonaws.com" + ] + }, + "Action": "sts:AssumeRole" + } + ] + }, + "ManagedPolicyArns": [ + { + "Ref": "PolicyBaseline" + } + ], + "RoleName": { + "Fn::Sub": "${AWS::StackName}-CodeBuildRole" + } + } + }, + "KmsKey": { + "Type": "AWS::KMS::Key", + "Properties": { + "Description": "Key used for cross-account codepipline deployments", + "EnableKeyRotation": true, + "KeyPolicy": { + "Version": "2012-10-17", + "Id": { + "Fn::Sub": "${AWS::StackName}" + }, + "Statement": [ + { + "Sid": "Allows admin of the key", + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Sub": "arn:aws:iam::${AWS::AccountId}:root" + } + }, + "Action": [ + "kms:*" + ], + "Resource": "*" + }, + { + "Fn::If": [ + "NotInitialCreation", + { + "Sid": "Allow other accounts to use the key.", + "Effect": "Allow", + "Principal": { + "AWS": [ + { + "Fn::Sub": "arn:aws:iam::${CicdAccount}:role/cicd-${AWS::StackName}-CloudFormationRole" + }, + { + "Fn::Sub": "arn:aws:iam::${CicdAccount}:role/cicd-${AWS::StackName}-CodePipelineRole" + }, + { + "Fn::Sub": "arn:aws:iam::${CicdAccount}:role/cicd-${AWS::StackName}-CodeBuildRole" + }, + { + "Fn::Sub": "arn:aws:iam::${DevAccount}:role/dev-${AWS::StackName}-CloudFormationRole" + }, + { + "Fn::Sub": "arn:aws:iam::${DevAccount}:role/dev-${AWS::StackName}-CodePipelineRole" + }, + { + "Fn::Sub": "arn:aws:iam::${DevAccount}:role/dev-${AWS::StackName}-CodeBuildRole" + }, + { + "Fn::Sub": "arn:aws:iam::${ProdAccount}:role/prod-${AWS::StackName}-CloudFormationRole" + }, + { + "Fn::Sub": "arn:aws:iam::${ProdAccount}:role/prod-${AWS::StackName}-CodePipelineRole" + }, + { + "Fn::Sub": "arn:aws:iam::${ProdAccount}:role/prod-${AWS::StackName}-CodeBuildRole" + } + ] + }, + "Action": [ + "kms:DescribeKey", + "kms:Encrypt", + "kms:Decrypt", + "kms:ReEncrypt", + "kms:GenerateDataKey" + ], + "Resource": "*" + }, + { + "Ref": "AWS::NoValue" + } + ] + } + ] + } + } + }, + "KmsAlias": { + "Type": "AWS::KMS::Alias", + "DependsOn": "KmsKey", + "Properties": { + "AliasName": { + "Fn::Sub": "alias/${AWS::StackName}" + }, + "TargetKeyId": { + "Ref": "KmsKey" + } + } + }, + "S3Bucket": { + "Type": "AWS::S3::Bucket", + "Properties": { + "AccessControl": "Private" + } + }, + "S3BucketPolicy": { + "Type": "AWS::S3::BucketPolicy", + "Properties": { + "Bucket": { + "Ref": "S3Bucket" + }, + "PolicyDocument": { + "Version": "2012-10-17", + "Id": "SSEAndSSLPolicy", + "Statement": [ + { + "Sid": "DenyInsecureConnections", + "Effect": "Deny", + "Principal": "*", + "Action": "s3:*", + "Resource": { + "Fn::Sub": [ + "${S3BucketArn}/*", + { + "S3BucketArn": { + "Fn::GetAtt": [ + "S3Bucket", + "Arn" + ] + } + } + ] + }, + "Condition": { + "Bool": { + "aws:SecureTransport": false + } + } + }, + { + "Fn::If": [ + "NotInitialCreation", + { + "Sid": "AccountsAllowedToS3Bucket", + "Effect": "Allow", + "Principal": { + "AWS": [ + { + "Fn::Sub": "arn:aws:iam::${CicdAccount}:role/cicd-${AWS::StackName}-CloudFormationRole" + }, + { + "Fn::Sub": "arn:aws:iam::${CicdAccount}:role/cicd-${AWS::StackName}-CodePipelineRole" + }, + { + "Fn::Sub": "arn:aws:iam::${CicdAccount}:role/cicd-${AWS::StackName}-CodeBuildRole" + }, + { + "Fn::Sub": "arn:aws:iam::${DevAccount}:role/dev-${AWS::StackName}-CloudFormationRole" + }, + { + "Fn::Sub": "arn:aws:iam::${DevAccount}:role/dev-${AWS::StackName}-CodePipelineRole" + }, + { + "Fn::Sub": "arn:aws:iam::${DevAccount}:role/dev-${AWS::StackName}-CodeBuildRole" + }, + { + "Fn::Sub": "arn:aws:iam::${ProdAccount}:role/prod-${AWS::StackName}-CloudFormationRole" + }, + { + "Fn::Sub": "arn:aws:iam::${ProdAccount}:role/prod-${AWS::StackName}-CodePipelineRole" + }, + { + "Fn::Sub": "arn:aws:iam::${ProdAccount}:role/prod-${AWS::StackName}-CodeBuildRole" + }, + { + "Fn::GetAtt": [ + "RoleCodePipeline", + "Arn" + ] + } + ] + }, + "Action": [ + "s3:Get*", + "s3:Put*", + "s3:ListBucket" + ], + "Resource": [ + { + "Fn::Sub": [ + "${S3BucketArn}/*", + { + "S3BucketArn": { + "Fn::GetAtt": [ + "S3Bucket", + "Arn" + ] + } + } + ] + }, + { + "Fn::GetAtt": [ + "S3Bucket", + "Arn" + ] + } + ] + }, + { + "Ref": "AWS::NoValue" + } + ] + } + ] + } + } + }, + "CodePipeline": { + "Type": "AWS::CodePipeline::Pipeline", + "Condition": "NotInitialCreation", + "Properties": { + "ArtifactStores": [ + { + "ArtifactStore": { + "Type": "S3", + "Location": { + "Ref": "S3Bucket" + }, + "EncryptionKey": { + "Id": { + "Ref": "KmsKey" + }, + "Type": "KMS" + } + }, + "Region": { + "Ref": "AWS::Region" + } + } + ], + "RestartExecutionOnUpdate": "True", + "RoleArn": { + "Fn::GetAtt" : ["RoleCodePipeline", "Arn"] + }, + "Name": { + "Ref": "AWS::StackName" + }, + "Stages": [ + { + "Name": "Source", + "Actions": [ + { + "ActionTypeId": { + "Category": "Source", + "Owner": "AWS", + "Provider": "CodeCommit", + "Version": "1" + }, + "Configuration": { + "RepositoryName": { + "Fn::If": [ + "CreateCodeCommitRepo", + { + "Fn::GetAtt": [ + "CodeCommit", + "Name" + ] + }, + { + "Ref": "SourceCodeCommitRepoName" + } + ] + }, + "BranchName": "master" + }, + "Name": "CodeCommit", + "OutputArtifacts": [ + { + "Name": "SourceOutput" + } + ], + "RunOrder": 1 + } + ] + }, + { + "Name": "SelfStackUpdate", + "Actions": [ + { + "ActionTypeId": { + "Category": "Deploy", + "Owner": "AWS", + "Provider": "CloudFormation", + "Version": "1" + }, + "Configuration": { + "ActionMode": "CREATE_UPDATE", + "Capabilities": "CAPABILITY_NAMED_IAM,CAPABILITY_AUTO_EXPAND", + "RoleArn": { + "Fn::GetAtt" : ["RoleCloudFormation", "Arn"] + }, + "StackName": { + "Fn::Sub": "${AWS::StackName}" + }, + "TemplatePath": "SourceOutput::Master.template", + "TemplateConfiguration": "SourceOutput::cfvars/Master.template" + }, + "Name": "UpdateCloudFormation", + "InputArtifacts": [ + { + "Name": "SourceOutput" + } + ], + "RunOrder": 1, + "RoleArn": { + "Fn::GetAtt" : ["RoleCodePipeline", "Arn"] + } + } + ] + }, + { + "Name": "GenerateCICDTemplates", + "Actions": [ + { + "ActionTypeId": { + "Category": "Build", + "Owner": "AWS", + "Provider": "CodeBuild", + "Version": "1" + }, + "Configuration": { + "ProjectName": { + "Ref": "CodeBuildGenerateCICDTemplates" + } + }, + "Name": "GenerateCICDTemplates", + "InputArtifacts": [ + { + "Name": "SourceOutput" + } + ], + "RunOrder": 1, + "OutputArtifacts": [ + { + "Name": "CICDTemplates" + } + ] + } + ] + }, + { + "Name": "Cicd", + "Actions": [ + { + "ActionTypeId": { + "Category": "Deploy", + "Owner": "AWS", + "Provider": "CloudFormation", + "Version": "1" + }, + "Configuration": { + "ActionMode": "CREATE_UPDATE", + "Capabilities": "CAPABILITY_NAMED_IAM,CAPABILITY_AUTO_EXPAND", + "RoleArn": { + "Fn::Sub": "arn:aws:iam::${CicdAccount}:role/cicd-${AWS::StackName}-CloudFormationRole" + }, + "StackName": { + "Fn::Sub": "cicd-${AWS::StackName}-infra" + }, + "TemplatePath": "CICDTemplates::Scope-CICD-Parent.template", + "TemplateConfiguration": "SourceOutput::cfvars/Cicd.template", + "ParameterOverrides": { + "Fn::Sub": "{ \"S3BucketName\" : { \"Fn::GetArtifactAtt\" : [\"SourceOutput\", \"BucketName\"]}, \"DevAccount\" : \"${DevAccount}\", \"ProdAccount\" : \"${ProdAccount}\", \"MasterPipeline\" : \"${AWS::StackName}\"}" + } + }, + "Name": "DeployCloudFormationCicd", + "InputArtifacts": [ + { + "Name": "SourceOutput" + }, + { + "Name": "CICDTemplates" + } + ], + "RunOrder": 1, + "RoleArn": { + "Fn::Sub": "arn:aws:iam::${CicdAccount}:role/cicd-${AWS::StackName}-CodePipelineRole" + } + } + ] + }, + { + "Name": "GenerateSDLCTemplates", + "Actions": [ + { + "ActionTypeId": { + "Category": "Build", + "Owner": "AWS", + "Provider": "CodeBuild", + "Version": "1" + }, + "Configuration": { + "ProjectName": { + "Ref": "CodeBuildGenerateSDLCTemplates" + } + }, + "Name": "GenerateSDLCTemplates", + "InputArtifacts": [ + { + "Name": "SourceOutput" + } + ], + "RunOrder": 1, + "OutputArtifacts": [ + { + "Name": "SDLCTemplates" + } + ] + } + ] + }, + { + "Name": "Dev", + "Actions": [ + { + "ActionTypeId": { + "Category": "Deploy", + "Owner": "AWS", + "Provider": "CloudFormation", + "Version": "1" + }, + "Configuration": { + "ActionMode": "CREATE_UPDATE", + "Capabilities": "CAPABILITY_NAMED_IAM,CAPABILITY_AUTO_EXPAND", + "RoleArn": { + "Fn::Sub": "arn:aws:iam::${DevAccount}:role/dev-${AWS::StackName}-CloudFormationRole" + }, + "StackName": { + "Fn::Sub": "dev-${AWS::StackName}-infra" + }, + "TemplatePath": "SDLCTemplates::Scope-SDLC-Parent.template", + "TemplateConfiguration": "SourceOutput::cfvars/Dev.template", + "ParameterOverrides": { + "Fn::Sub": "{ \"S3BucketName\" : { \"Fn::GetArtifactAtt\" : [\"SourceOutput\", \"BucketName\"]}, \"CicdAccount\" : \"${CicdAccount}\", \"MasterPipeline\" : \"${AWS::StackName}\"}" + } + }, + "Name": "DeployCloudFormationDev", + "InputArtifacts": [ + { + "Name": "SourceOutput" + }, + { + "Name": "SDLCTemplates" + } + ], + "RunOrder": 1, + "RoleArn": { + "Fn::Sub": "arn:aws:iam::${DevAccount}:role/dev-${AWS::StackName}-CodePipelineRole" + } + } + ] + }, + { + "Name": "Prod", + "Actions": [ + { + "ActionTypeId": { + "Category": "Deploy", + "Owner": "AWS", + "Provider": "CloudFormation", + "Version": "1" + }, + "Configuration": { + "ActionMode": "CREATE_UPDATE", + "Capabilities": "CAPABILITY_NAMED_IAM,CAPABILITY_AUTO_EXPAND", + "RoleArn": { + "Fn::Sub": "arn:aws:iam::${ProdAccount}:role/prod-${AWS::StackName}-CloudFormationRole" + }, + "StackName": { + "Fn::Sub": "prod-${AWS::StackName}-infra" + }, + "TemplatePath": "SDLCTemplates::Scope-SDLC-Parent.template", + "TemplateConfiguration": "SourceOutput::cfvars/Prod.template", + "ParameterOverrides": { + "Fn::Sub": "{ \"S3BucketName\" : { \"Fn::GetArtifactAtt\" : [\"SourceOutput\", \"BucketName\"]}, \"CicdAccount\" : \"${CicdAccount}\", \"MasterPipeline\" : \"${AWS::StackName}\"}" + } + }, + "Name": "DeployCloudFormationProd", + "InputArtifacts": [ + { + "Name": "SourceOutput" + }, + { + "Name": "SDLCTemplates" + } + ], + "RunOrder": 1, + "RoleArn": { + "Fn::Sub": "arn:aws:iam::${ProdAccount}:role/prod-${AWS::StackName}-CodePipelineRole" + } + } + ] + }, + { + "Name": "SetAllEnvironmentsCreatedTrue", + "Actions": [ + { + "ActionTypeId": { + "Category": "Build", + "Owner": "AWS", + "Provider": "CodeBuild", + "Version": "1" + }, + "Configuration": { + "ProjectName": { + "Ref": "CodeBuildUpdateStacks" + } + }, + "Name": "UpdateStacks", + "InputArtifacts": [ + { + "Name": "SourceOutput" + } + ], + "RunOrder": 1 + } + ] + } + ] + } + } + }, + "Outputs": { + "KmsCmkArn": { + "Value": { + "Fn::GetAtt": [ + "KmsKey", + "Arn" + ] + }, + "Export": { + "Name": { + "Fn::Sub": "${AWS::StackName}-KmsKeyArn" + } + } + }, + "S3BucketName": { + "Value": { + "Ref": "S3Bucket" + }, + "Export": { + "Name": { + "Fn::Sub": "${AWS::StackName}-S3BucketName" + } + } + } + } +} \ No newline at end of file diff --git a/Pipelines.json b/Pipelines.json new file mode 100644 index 0000000..4488243 --- /dev/null +++ b/Pipelines.json @@ -0,0 +1,74 @@ +{ + "Network": { + "PolicyStatements": [ + "Baseline" + ], + "Pipelines": [ + { + "Name": "Vpc", + "PipelineTemplate": "Default" + } + ] + }, + "Catalog": { + "PolicyStatements": [ + "Baseline", + "IamCreateRole" + ], + "Pipelines": [ + { + "Name": "Db", + "PipelineTemplate": "Default", + "ParameterOverrides": { + "IncludeEnvCfTemplateConfigs": "True", + "CfContainsLambda": "True" + } + } + ] + }, + "Frontend": { + "PolicyStatements": [ + "Baseline", + "Frontend", + "IamCreateRole" + ], + "Pipelines": [ + { + "Name": "Infra", + "PipelineTemplate": "Default", + "ParameterOverrides": { + "IncludeEnvCfTemplateConfigs": "True" + } + } + ] + }, + "Domain": { + "PolicyStatements": [ + "Baseline", + "IamCreateRole" + ], + "Pipelines": [ + { + "Name": "Api", + "PipelineTemplate": "Default" + } + ] + }, + "Ui": { + "PolicyStatements": [ + "Baseline", + "IamCreateRole" + ], + "Pipelines": [ + { + "Name": "React", + "PipelineTemplate": "Default", + "ParameterOverrides": { + "IncludeEnvCfTemplateConfigs": "False", + "SdlcCloudFormation": "False", + "SdlcCodeBuildPre": "True" + } + } + ] + } +} \ No newline at end of file diff --git a/Scope-CICD-Child.template b/Scope-CICD-Child.template new file mode 100644 index 0000000..9b01092 --- /dev/null +++ b/Scope-CICD-Child.template @@ -0,0 +1,414 @@ +{ + "AWSTemplateFormatVersion": "2010-09-09", + "Description": "Creates the build account's infrastructure that allows for cross-account CodePipelines.", + "Parameters": { + "DevAccount": { + "Description": "AWS account number for dev.", + "Type": "Number" + }, + "ProdAccount": { + "Description": "AWS account number for production.", + "Type": "Number" + }, + "AllEnvironmentsCreated": { + "Description": "Must be false for initial creation of stacks. Afterwards it must be switched to true when all environments have been created.", + "Type": "String", + "AllowedValues": [ + "True", + "False" + ] + }, + "Scope": { + "Type": "String" + }, + "MasterPipeline": { + "Type": "String" + }, + "Environment": { + "Type": "String" + }, + "MasterS3BucketName": { + "Type": "String" + } + }, + "Conditions": { + "NotInitialCreation": { + "Fn::Equals": [{ + "Ref": "AllEnvironmentsCreated" + }, + "True" + ] + } + }, + "Resources": { + "PolicyCodePipeline": { + "Type": "AWS::IAM::ManagedPolicy", + "DependsOn": "S3Bucket", + "Properties": { + "PolicyDocument": { + "Version": "2012-10-17", + "Statement": [{ + "Sid": "AccessToS3ArtifactsBucket", + "Effect": "Allow", + "Action": [ + "s3:PutObject", + "s3:GetBucketPolicy", + "s3:GetObject", + "s3:ListBucket" + ], + "Resource": [{ + "Fn::Sub": [ + "${S3BucketArn}/*", + { + "S3BucketArn": { + "Fn::GetAtt": [ + "S3Bucket", + "Arn" + ] + } + } + ] + }, + { + "Fn::GetAtt": [ + "S3Bucket", + "Arn" + ] + } + ] + }, + { + "Fn::If": [ + "NotInitialCreation", + { + "Sid": "AllowAssumeEnvironmentRoles", + "Effect": "Allow", + "Action": "sts:AssumeRole", + "Resource": [{ + "Fn::Sub": "arn:aws:iam::${DevAccount}:role/dev-${MasterPipeline}-infra-${Scope}-CloudFormationRole" + }, + { + "Fn::Sub": "arn:aws:iam::${DevAccount}:role/dev-${MasterPipeline}-infra-${Scope}-CodePipelineRole" + }, + { + "Fn::Sub": "arn:aws:iam::${DevAccount}:role/dev-${MasterPipeline}-infra-${Scope}-CodeBuildRole" + }, + { + "Fn::Sub": "arn:aws:iam::${ProdAccount}:role/prod-${MasterPipeline}-infra-${Scope}-CloudFormationRole" + }, + { + "Fn::Sub": "arn:aws:iam::${ProdAccount}:role/prod-${MasterPipeline}-infra-${Scope}-CodePipelineRole" + }, + { + "Fn::Sub": "arn:aws:iam::${ProdAccount}:role/prod-${MasterPipeline}-infra-${Scope}-CodeBuildRole" + } + ] + }, + { + "Ref": "AWS::NoValue" + } + ] + }, + { + "Sid": "UseKmsKey", + "Effect": "Allow", + "Action": [ + "kms:DescribeKey", + "kms:GenerateDataKey*", + "kms:Encrypt", + "kms:ReEncrypt*", + "kms:Decrypt" + ], + "Resource": [{ + "Fn::GetAtt": [ + "KmsKey", + "Arn" + ] + }] + }, + { + "Sid": "CodeCommitRepos", + "Effect": "Allow", + "Action": [ + "codecommit:List*", + "codecommit:Get*", + "codecommit:GitPull", + "codecommit:UploadArchive", + "codecommit:CancelUploadArchive" + ], + "Resource": "*" + }, + { + "Sid": "CreateLogs", + "Effect": "Allow", + "Action": [ + "logs:CreateLogStream", + "logs:CreateLogGroup", + "logs:PutLogEvents" + ], + "Resource": "*" + }, + { + "Sid": "DescribeCloudFormationStacks", + "Effect": "Allow", + "Action": [ + "cloudformation:Describe*", + "cloudformation:ExecuteChangeSet" + ], + "Resource": "*" + }, + { + "Sid": "RunCodeBuild", + "Effect": "Allow", + "Action": [ + "codebuild:StartBuild", + "codebuild:BatchGetBuilds" + ], + "Resource": { + "Fn::Sub": "arn:aws:codebuild:*:${AWS::AccountId}:project/${Environment}-${MasterPipeline}-infra-${Scope}*" + } + } + ] + } + } + }, + "RoleCodePipeline": { + "Type": "AWS::IAM::Role", + "DependsOn": "PolicyCodePipeline", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [{ + "Effect": "Allow", + "Principal": { + "Service": [ + "codepipeline.amazonaws.com" + ] + }, + "Action": "sts:AssumeRole" + }] + }, + "ManagedPolicyArns": [{ + "Ref": "PolicyCodePipeline" + }], + "RoleName": { + "Fn::Sub": "${Environment}-${MasterPipeline}-infra-${Scope}-CodePipelineRole" + } + } + }, + "KmsKey": { + "Type": "AWS::KMS::Key", + "Properties": { + "Description": "Key used for cross-account codepipline deployments", + "EnableKeyRotation": true, + "KeyPolicy": { + "Version": "2012-10-17", + "Id": { + "Fn::Sub": "${Environment}-${MasterPipeline}-infra-${Scope}" + }, + "Statement": [{ + "Sid": "Allows admin of the key", + "Effect": "Allow", + "Principal": { + "AWS": [{ + "Fn::Sub": "arn:aws:iam::${AWS::AccountId}:root" + }, + { + "Fn::Sub": "arn:aws:iam::${AWS::AccountId}:role/${Environment}-${MasterPipeline}-CloudFormationRole" + } + ] + }, + "Action": [ + "kms:*" + ], + "Resource": "*" + }, + { + "Fn::If": [ + "NotInitialCreation", + { + "Sid": "Allow other accounts to use the key.", + "Effect": "Allow", + "Principal": { + "AWS": [{ + "Fn::Sub": "arn:aws:iam::${DevAccount}:role/dev-${MasterPipeline}-infra-${Scope}-CloudFormationRole" + }, + { + "Fn::Sub": "arn:aws:iam::${DevAccount}:role/dev-${MasterPipeline}-infra-${Scope}-CodePipelineRole" + }, + { + "Fn::Sub": "arn:aws:iam::${DevAccount}:role/dev-${MasterPipeline}-infra-${Scope}-CodeBuildRole" + }, + { + "Fn::Sub": "arn:aws:iam::${ProdAccount}:role/prod-${MasterPipeline}-infra-${Scope}-CloudFormationRole" + }, + { + "Fn::Sub": "arn:aws:iam::${ProdAccount}:role/prod-${MasterPipeline}-infra-${Scope}-CodePipelineRole" + }, + { + "Fn::Sub": "arn:aws:iam::${ProdAccount}:role/prod-${MasterPipeline}-infra-${Scope}-CodeBuildRole" + } + ] + }, + "Action": [ + "kms:DescribeKey", + "kms:Encrypt", + "kms:Decrypt", + "kms:ReEncrypt", + "kms:GenerateDataKey" + ], + "Resource": "*" + }, + { + "Ref": "AWS::NoValue" + } + ] + } + ] + } + } + }, + "KmsAlias": { + "Type": "AWS::KMS::Alias", + "DependsOn": "KmsKey", + "Properties": { + "AliasName": { + "Fn::Sub": "alias/${Environment}-${MasterPipeline}-infra-${Scope}" + }, + "TargetKeyId": { + "Ref": "KmsKey" + } + } + }, + "S3Bucket": { + "Type": "AWS::S3::Bucket", + "Properties": { + "AccessControl": "Private" + } + }, + "S3BucketPolicy": { + "Type": "AWS::S3::BucketPolicy", + "Properties": { + "Bucket": { + "Ref": "S3Bucket" + }, + "PolicyDocument": { + "Version": "2012-10-17", + "Id": "SSEAndSSLPolicy", + "Statement": [{ + "Sid": "DenyInsecureConnections", + "Effect": "Deny", + "Principal": "*", + "Action": "s3:*", + "Resource": { + "Fn::Sub": [ + "${S3BucketArn}/*", + { + "S3BucketArn": { + "Fn::GetAtt": [ + "S3Bucket", + "Arn" + ] + } + } + ] + }, + "Condition": { + "Bool": { + "aws:SecureTransport": false + } + } + }, + { + "Fn::If": [ + "NotInitialCreation", + { + "Sid": "AccountsAllowedToS3Bucket", + "Effect": "Allow", + "Principal": { + "AWS": [{ + "Fn::Sub": "arn:aws:iam::${DevAccount}:role/dev-${MasterPipeline}-infra-${Scope}-CloudFormationRole" + }, + { + "Fn::Sub": "arn:aws:iam::${DevAccount}:role/dev-${MasterPipeline}-infra-${Scope}-CodePipelineRole" + }, + { + "Fn::Sub": "arn:aws:iam::${DevAccount}:role/dev-${MasterPipeline}-infra-${Scope}-CodeBuildRole" + }, + { + "Fn::Sub": "arn:aws:iam::${ProdAccount}:role/prod-${MasterPipeline}-infra-${Scope}-CloudFormationRole" + }, + { + "Fn::Sub": "arn:aws:iam::${ProdAccount}:role/prod-${MasterPipeline}-infra-${Scope}-CodePipelineRole" + }, + { + "Fn::Sub": "arn:aws:iam::${ProdAccount}:role/prod-${MasterPipeline}-infra-${Scope}-CodeBuildRole" + }, + { + "Fn::GetAtt": [ + "RoleCodePipeline", + "Arn" + ] + } + ] + }, + "Action": [ + "s3:Get*", + "s3:Put*", + "s3:ListBucket" + ], + "Resource": [{ + "Fn::Sub": [ + "${S3BucketArn}/*", + { + "S3BucketArn": { + "Fn::GetAtt": [ + "S3Bucket", + "Arn" + ] + } + } + ] + }, + { + "Fn::GetAtt": [ + "S3Bucket", + "Arn" + ] + } + ] + }, + { + "Ref": "AWS::NoValue" + } + ] + } + ] + } + } + } + }, + "Outputs": { + "KmsCmkArn": { + "Value": { + "Fn::GetAtt": [ + "KmsKey", + "Arn" + ] + } + }, + "S3BucketName": { + "Value": { + "Ref": "S3Bucket" + } + }, + "CodePipelineRoleArn": { + "Value": { + "Fn::GetAtt": [ + "RoleCodePipeline", + "Arn" + ] + } + } + } +} diff --git a/Scope-CICD-Parent.template b/Scope-CICD-Parent.template new file mode 100644 index 0000000..2b5c0a1 --- /dev/null +++ b/Scope-CICD-Parent.template @@ -0,0 +1,23 @@ +{ + "AWSTemplateFormatVersion": "2010-09-09", + "Description": "Creates the account's infrastructure that allows for cross-account CodePipelines.", + "Parameters": { + "S3BucketName": { + "Type": "String" + }, + "DevAccount": { + "Type": "String" + }, + "ProdAccount": { + "Type": "String" + }, + "Environment": { + "Type": "String" + }, + "MasterPipeline": { + "Type": "String" + } + }, + "Resources": { + } +} \ No newline at end of file diff --git a/Scope-SDLC-Child.template b/Scope-SDLC-Child.template new file mode 100644 index 0000000..9b29e8d --- /dev/null +++ b/Scope-SDLC-Child.template @@ -0,0 +1,256 @@ +{ + "AWSTemplateFormatVersion": "2010-09-09", + "Description": "Role to be assumed by CodePipeline service cross account", + "Parameters": { + "S3BucketName": { + "Description": "S3 Bucket in CICD Account, which holds the CodePipeline artifacts.", + "Type": "String" + }, + "CicdAccount": { + "Description": "AWS AccountNumber for CICD Account.", + "Type": "Number" + }, + "KmsCmkArn": { + "Description": "ARN of the KMS CMK creates in CICD account.", + "Type": "String" + }, + "Environment": { + "Description": "Environment", + "Type": "String" + }, + "MasterPipeline": { + "Type": "String" + }, + "Scope": { + "Type": "String" + }, + "MasterS3BucketName": { + "Type": "String" + } + }, + "Resources": { + "CodeBuildPre": { + "Type": "AWS::CodeBuild::Project", + "Properties": { + "Artifacts": { + "Type": "CODEPIPELINE" + }, + "EncryptionKey": { + "Ref": "KmsCmkArn" + }, + "Environment": { + "ComputeType": "BUILD_GENERAL1_SMALL", + "Image": "aws/codebuild/nodejs:6.3.1", + "EnvironmentVariables": [ + { + "Name": "Environment", + "Type": "PLAINTEXT", + "Value": "dev" + } + ], + "PrivilegedMode": false, + "Type": "LINUX_CONTAINER" + }, + "Name": { + "Fn::Sub": "${Environment}-${MasterPipeline}-infra-${Scope}-CodeBuildPre" + }, + "ServiceRole": { + "Fn::GetAtt": [ + "IamRoleCodeBuild", + "Arn" + ] + }, + "Source": { + "Type": "CODEPIPELINE", + "BuildSpec": "buildspec-sdlc-pre.yml" + }, + "TimeoutInMinutes": "5" + } + }, + "CodeBuildPost": { + "Type": "AWS::CodeBuild::Project", + "Properties": { + "Artifacts": { + "Type": "CODEPIPELINE" + }, + "EncryptionKey": { + "Ref": "KmsCmkArn" + }, + "Environment": { + "ComputeType": "BUILD_GENERAL1_SMALL", + "Image": "aws/codebuild/nodejs:6.3.1", + "EnvironmentVariables": [ + { + "Name": "Environment", + "Type": "PLAINTEXT", + "Value": "dev" + } + ], + "PrivilegedMode": false, + "Type": "LINUX_CONTAINER" + }, + "Name": { + "Fn::Sub": "${Environment}-${MasterPipeline}-infra-${Scope}-CodeBuildPost" + }, + "ServiceRole": { + "Fn::GetAtt": [ + "IamRoleCodeBuild", + "Arn" + ] + }, + "Source": { + "Type": "CODEPIPELINE", + "BuildSpec": "buildspec-sdlc-pre.yml" + }, + "TimeoutInMinutes": "5" + } + }, + "IamRoleCodeBuild": { + "Type": "AWS::IAM::Role", + "Properties": { + "RoleName": { + "Fn::Sub": "${Environment}-${MasterPipeline}-infra-${Scope}-CodeBuildRole" + }, + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "Service": "codebuild.amazonaws.com" + }, + "Action": [ + "sts:AssumeRole" + ] + }, + { + "Effect": "Allow", + "Principal": { + "AWS": [ + { + "Ref": "CicdAccount" + } + ] + }, + "Action": [ + "sts:AssumeRole" + ] + } + ] + }, + "Path": "/", + "ManagedPolicyArns": [ + { + "Ref": "IamPolicyBaseline" + } + ] + } + }, + "IamRoleCodePipeline": { + "Type": "AWS::IAM::Role", + "Properties": { + "RoleName": { + "Fn::Sub": "${Environment}-${MasterPipeline}-infra-${Scope}-CodePipelineRole" + }, + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "AWS": [ + { + "Ref": "CicdAccount" + } + ] + }, + "Action": [ + "sts:AssumeRole" + ] + } + ] + }, + "Path": "/", + "ManagedPolicyArns": [ + { + "Ref": "IamPolicyBaseline" + } + ] + } + }, + "IamRoleCloudFormation": { + "Type": "AWS::IAM::Role", + "Properties": { + "RoleName": { + "Fn::Sub": "${Environment}-${MasterPipeline}-infra-${Scope}-CloudFormationRole" + }, + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "Service": [ + "cloudformation.amazonaws.com" + ] + }, + "Action": [ + "sts:AssumeRole" + ] + } + ] + }, + "Path": "/", + "ManagedPolicyArns": [ + { + "Ref": "IamPolicyBaseline" + } + ] + } + }, + "IamPolicyBaseline": { + "Type": "AWS::IAM::ManagedPolicy", + "Properties": { + "ManagedPolicyName": { + "Fn::Sub": "${Environment}-${MasterPipeline}-infra-${Scope}-IamPolicyBaseline" + }, + "PolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "CrossAccountPipelinePermissions", + "Effect": "Allow", + "Action": [ + "s3:PutObject", + "s3:GetObject", + "s3:GetObjectVersion", + "s3:GetBucketPolicy", + "s3:ListBucket", + "kms:DescribeKey", + "kms:Encrypt", + "kms:Decrypt", + "kms:ReEncrypt", + "kms:GenerateDataKey", + "iam:PassRole" + ], + "Resource": [ + { + "Fn::Sub": "arn:aws:s3:::${S3BucketName}/*" + }, + { + "Ref": "KmsCmkArn" + }, + { + "Fn::Sub": "arn:aws:iam::${AWS::AccountId}:role/${Environment}-${Scope}-*" + }, + { + "Fn::Sub": "arn:aws:iam::${AWS::AccountId}:role/${Environment}-${MasterPipeline}-infra-${Scope}-*" + } + ] + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/Scope-SDLC-Parent.template b/Scope-SDLC-Parent.template new file mode 100644 index 0000000..5494de6 --- /dev/null +++ b/Scope-SDLC-Parent.template @@ -0,0 +1,21 @@ +{ + "AWSTemplateFormatVersion": "2010-09-09", + "Description": "Creates the account's infrastructure that allows for cross-account CodePipelines.", + "Parameters": { + "S3BucketName": { + "Type": "String" + }, + "CicdAccount": { + "Description": "AWS AccountNumber for CICD Account.", + "Type": "String" + }, + "Environment": { + "Type": "String" + }, + "MasterPipeline": { + "Type": "String" + } + }, + "Resources": { + } +} \ No newline at end of file diff --git a/buildspec/.~c9_invoke_66v5L.py b/buildspec/.~c9_invoke_66v5L.py new file mode 100644 index 0000000..e0f9bca --- /dev/null +++ b/buildspec/.~c9_invoke_66v5L.py @@ -0,0 +1,140 @@ +import boto3 +import sys +import time +import os +import json + +# Filenames +file_pipelines = 'Pipelines.json' +file_sdlc_parent = 'Scope-SDLC-Parent' +file_sdlc_child = 'Scope-SDLC-Child' + +# Open Files +# Input Files +with open(file_pipelines) as pl_file: + pipelines = json.load(pl_file) +with open(file_sdlc_parent + '.template') as sp_file: + sdlc_parent = json.load(sp_file) + + +with open(file_sdlc_child + '.template') as sc_file: + sdlc_child = json.load(sc_file) + +client = boto3.client('cloudformation') + +#MASTERINFRASTACK = os.environ['MasterInfraStack'] +#ENVIRONMENT = os.environ['Environment'] + +MASTERINFRASTACK = 'cicd-master-infra' +# ENVIRONMENT = 'cicd' + +# Get master infra stack resources +resource_summaries = client.list_stack_resources( + StackName=MASTERINFRASTACK +)['StackResourceSummaries'] + +# Get cicd child stack outputs +child_stack_outputs = {} +# Loop through master infra stack resources +for rs in resource_summaries: + # Only look for CloudFormation Stacks + if rs['ResourceType'] == 'AWS::CloudFormation::Stack': + # Get Stack outputs + child_stack_outputs[rs['LogicalResourceId']] = client.describe_stacks( + StackName = rs['PhysicalResourceId'] + )['Stacks'][0]['Outputs'] + +# Loop through infra scopes within pipeline file +for key, value in pipelines.items(): + scope = key.lower() + + # Determine stack output values + s3_bucket_name = list(filter(lambda item: item['OutputKey'] == 'S3BucketName', child_stack_outputs[scope]))[0]['OutputValue'] + kms_cmk_arn = list(filter(lambda item: item['OutputKey'] == 'KmsCmkArn', child_stack_outputs[scope]))[0]['OutputValue'] + + sdlc_parent['Resources'][scope] = { + "Type" : "AWS::CloudFormation::Stack", + "Properties": { + "Parameters": { + "S3BucketName": s3_bucket_name, + "CicdAccount": { + "Ref": "CicdAccount" + }, + "KmsCmkArn": kms_cmk_arn, + "MasterPipeline": { + "Ref": "MasterPipeline" + }, + "Environment": { + "Ref": "Environment" + }, + "Scope": scope, + "MasterS3BucketName": { + "Fn::Sub": "${S3BucketName}" + } + }, + "Tags" : [ + { + "Key": "Environment", + "Value": { + "Fn::Sub": "${Environment}" + } + } + ], + "TemplateURL" : { + "Fn::Sub": "https://s3.amazonaws.com/${S3BucketName}/generated-sdlc-templates/Scope-SDLC-Child-" + scope + ".template" + } + } + } + + # Insert Scope CICD Stacks + # cicd_parent['Resources']['Pipeline' + key] = { + # "Type": "AWS::CloudFormation::Stack", + # "Condition": "NotInitialCreation", + # "Properties": { + # "Parameters": { + # "DevAccount": { + # "Ref": "DevAccount" + # }, + # "ProdAccount": { + # "Ref": "ProdAccount" + # }, + # "MasterPipeline": { + # "Ref": "MasterPipeline" + # }, + # "Environment": { + # "Ref": "Environment" + # }, + # "Scope": { + # "Fn::Sub": "${Scope}" + # }, + # "AllEnvironmentsCreated": "False" + # }, + # "Tags": [{ + # "Key": "Environment", + # "Value": { + # "Fn::Sub": "${Environment}" + # } + # }], + # "TemplateURL": { + # "Fn::Sub": "https://s3.amazonaws.com/${S3BucketName}/generated-stacks/${Scope}.template" + # } + # } + # } + + for pipeline in value['Pipelines']: + name = pipeline['Name'].lower() + # Insert Policy statements into Baseline Policy + with open(file_sdlc_child + '.template') as sc_file: + sdlc_child = json.load(sc_file) + for ps in pipeline['PolicyStatements']: + with open('policy-statements/' + str(ps) + '.template') as json_file: + data = json.load(json_file) + for + sdlc_child['Resources']['IamPolicyBaseline']['Properties']['PolicyDocument']['Statement'].append(data) + + with open('generated-sdlc-templates/' + file_sdlc_child + '-' + scope + '.template', 'w') as sc_file_output: + json.dump(sdlc_child, sc_file_output, indent=4) + +# Save files +with open('generated-sdlc-templates/' + file_sdlc_parent + '.template', 'w') as sp_file_output: + json.dump(sdlc_parent, sp_file_output, indent=4) \ No newline at end of file diff --git a/buildspec/.~c9_invoke_iNOLF.py b/buildspec/.~c9_invoke_iNOLF.py new file mode 100644 index 0000000..51b0a8b --- /dev/null +++ b/buildspec/.~c9_invoke_iNOLF.py @@ -0,0 +1,126 @@ +import boto3 +import sys +import time +import os +import json + +# Filenames +file_pipelines = 'Pipelines.json' +file_sdlc_parent = 'Scope-SDLC-Parent' +file_sdlc_child = 'Scope-SDLC-Child' + +# Open Files +# Input Files +with open(file_pipelines) as pl_file: + pipelines = json.load(pl_file) +with open(file_sdlc_parent + '.template') as sp_file: + sdlc_parent = json.load(sp_file) + + +with open(file_sdlc_child + '.template') as sc_file: + sdlc_child = json.load(sc_file) + +client = boto3.client('cloudformation') + +#MASTERINFRASTACK = os.environ['MasterInfraStack'] +#ENVIRONMENT = os.environ['Environment'] + +MASTERINFRASTACK = 'cicd-master-infra' +# ENVIRONMENT = 'cicd' + +# Get master infra stack resources +resource_summaries = client.list_stack_resources( + StackName=MASTERINFRASTACK +)['StackResourceSummaries'] + +# Get cicd child stack outputs +child_stack_outputs = {} +# Loop through master infra stack resources +for rs in resource_summaries: + # Only look for CloudFormation Stacks + if rs['ResourceType'] == 'AWS::CloudFormation::Stack': + # Get Stack outputs + child_stack_parameters[rs['LogicalResourceId']] = client.describe_stacks( + StackName = rs['PhysicalResourceId'] + )['Stacks'][0]['Outputs'] + +# Loop through infra scopes within pipeline file +for key, value in pipelines.items(): + scope = key.lower() + + # Determine AllEnvironmentsCreated value + + sdlc_parent['Resources'][scope] = { + "Type" : "AWS::CloudFormation::Stack", + "Properties": { + "Parameters": { + "S3BucketName": {}, + "CicdAccount": { + "Ref": "CicdAccount" + }, + "KmsCmkArn": {}, + "MasterPipeline": { + "Ref": "MasterPipeline" + }, + "Environment": { + "Ref": "Environment" + }, + "Scope": "network", + "MasterS3BucketName": { + "Fn::Sub": "${S3BucketName}" + } + }, + "Tags" : [ + { + "Key": "Environment", + "Value": { + "Fn::Sub": "${Environment}" + } + } + ], + "TemplateURL" : { + "Fn::Sub": "https://s3.amazonaws.com/${S3BucketName}/generated-sdlc-templates/Scope-SDLC-Child.template" + } + } + } + + # Insert Scope CICD Stacks + # cicd_parent['Resources']['Pipeline' + key] = { + # "Type": "AWS::CloudFormation::Stack", + # "Condition": "NotInitialCreation", + # "Properties": { + # "Parameters": { + # "DevAccount": { + # "Ref": "DevAccount" + # }, + # "ProdAccount": { + # "Ref": "ProdAccount" + # }, + # "MasterPipeline": { + # "Ref": "MasterPipeline" + # }, + # "Environment": { + # "Ref": "Environment" + # }, + # "Scope": { + # "Fn::Sub": "${Scope}" + # }, + # "AllEnvironmentsCreated": "False" + # }, + # "Tags": [{ + # "Key": "Environment", + # "Value": { + # "Fn::Sub": "${Environment}" + # } + # }], + # "TemplateURL": { + # "Fn::Sub": "https://s3.amazonaws.com/${S3BucketName}/generated-stacks/${Scope}.template" + # } + # } + # } + +# Save files +with open('generated-sdlc-templates/' + file_sdlc_parent + '.template', 'w') as sp_file_output: + json.dump(sdlc_parent, sp_file_output, indent=4) +with open('generated-sdlc-templates/' + file_sdlc_child + '.template', 'w') as sc_file_output: + json.dump(sdlc_child, sc_file_output, indent=4) \ No newline at end of file diff --git a/buildspec/generate-cicd-templates.py b/buildspec/generate-cicd-templates.py new file mode 100644 index 0000000..c52de2c --- /dev/null +++ b/buildspec/generate-cicd-templates.py @@ -0,0 +1,160 @@ +import boto3 +import sys +import time +import os +import json +from botocore.exceptions import ClientError + +# Filenames +file_pipelines = 'Pipelines.json' +file_cicd_parent = 'Scope-CICD-Parent' +file_cicd_child = 'Scope-CICD-Child' + +# Open Files +# Input Files +with open(file_pipelines) as pl_file: + pipelines = json.load(pl_file) +with open(file_cicd_parent + '.template') as cp_file: + cicd_parent = json.load(cp_file) + +client = boto3.client('cloudformation') + +#MASTERINFRASTACK = os.environ['MasterInfraStack'] +#ENVIRONMENT = os.environ['Environment'] + +MASTERINFRASTACK = 'cicd-master-infra' +ENVIRONMENT = 'cicd' + +# Get master infra stack resources +aec = True +try: + master_stack = client.describe_stacks( + StackName=MASTERINFRASTACK + ) +except ClientError: + aec = False + +if aec: + resource_summaries = client.list_stack_resources( + StackName=MASTERINFRASTACK + )['StackResourceSummaries'] + # Get existing child stack parameters + child_stack_parameters = {} + # Loop through master infra stack resources + for rs in resource_summaries: + # Only look for CloudFormation Stacks + if rs['ResourceType'] == 'AWS::CloudFormation::Stack': + # Get Stack parameters + child_stack_parameters[rs['LogicalResourceId']] = client.describe_stacks( + StackName = rs['PhysicalResourceId'] + )['Stacks'][0]['Parameters'] + +# Loop through infra scopes within pipeline file +for key, value in pipelines.items(): + scope = key.lower() + # Determine AllEnvironmentsCreated value + if scope not in child_stack_parameters: + aec = False + if aec: + aec = list(filter(lambda item: item['ParameterKey'] == 'AllEnvironmentsCreated', child_stack_parameters[scope]))[0]['ParameterValue'] + + # Insert Scope CICD Stack into Parent + cicd_parent['Resources'][scope] = { + "Type" : "AWS::CloudFormation::Stack", + "Properties": { + "Parameters": { + "DevAccount": { + "Ref": "DevAccount" + }, + "ProdAccount": { + "Ref": "ProdAccount" + }, + "MasterPipeline": { + "Ref": "MasterPipeline" + }, + "Environment": { + "Ref": "Environment" + }, + "Scope": scope, + "AllEnvironmentsCreated": str(aec), + "MasterS3BucketName": { + "Fn::Sub": "${S3BucketName}" + } + }, + "Tags" : [ + { + "Key": "Environment", + "Value": { + "Fn::Sub": "${Environment}" + } + } + ], + "TemplateURL" : { + "Fn::Sub": "https://s3.amazonaws.com/${S3BucketName}/generated-cicd-templates/Scope-CICD-Child-" + scope + ".template" + } + } + } + + # Open child template to insert pipelines + with open(file_cicd_child + '.template') as cc_file: + cicd_child = json.load(cc_file) + + # Loop through pipelines + for pipeline in value['Pipelines']: + name = pipeline['Name'].lower() + cicd_child['Resources']['Pipeline' + pipeline['Name']] = { + "Type": "AWS::CloudFormation::Stack", + "Condition": "NotInitialCreation", + "Properties": { + "Parameters": { + "DevAccount": { + "Ref": "DevAccount" + }, + "ProdAccount": { + "Ref": "ProdAccount" + }, + "MasterPipeline": { + "Ref": "MasterPipeline" + }, + "Scope": scope, + "SubScope": name, + "Environment": "cicd", + "S3BucketName": { + "Ref": "S3Bucket" + }, + "KmsCmkArn": { + "Fn::GetAtt": [ + "KmsKey", + "Arn" + ] + }, + "RoleArn": { + "Fn::GetAtt": [ + "RoleCodePipeline", + "Arn" + ] + } + }, + "Tags": [{ + "Key": "Environment", + "Value": { + "Fn::Sub": "${Environment}" + } + }], + "TemplateURL": { + "Fn::Sub": "https://s3.amazonaws.com/${MasterS3BucketName}/pipeline-templates/" + pipeline['PipelineTemplate'] + '.template' + } + } + } + + # Add Parameter Overrides + if 'ParameterOverrides' in pipeline: + for po, po_value in pipeline['ParameterOverrides'].items(): + cicd_child['Resources']['Pipeline' + pipeline['Name']]['Properties']['Parameters'][po] = po_value + + with open('generated-cicd-templates/' + file_cicd_child + '-' + scope + '.template', 'w') as cc_file_output: + json.dump(cicd_child, cc_file_output, indent=4) + +# Save files +with open('generated-cicd-templates/' + file_cicd_parent + '.template', 'w') as cp_file_output: + json.dump(cicd_parent, cp_file_output, indent=4) \ No newline at end of file diff --git a/buildspec/generate-cicd-templates.yml b/buildspec/generate-cicd-templates.yml new file mode 100644 index 0000000..7983f80 --- /dev/null +++ b/buildspec/generate-cicd-templates.yml @@ -0,0 +1,21 @@ +version: 0.2 + +phases: + install: + commands: + - echo Entered the install phase... + - pip install boto3 + build: + commands: + - echo Entered the build phase... + - mkdir -p generated-cicd-templates + - python buildspec/generate-cicd-templates.py + post_build: + commands: + - aws s3 sync generated-cicd-templates s3://${S3Bucket}/generated-cicd-templates/ --delete + - aws s3 sync pipeline-templates s3://${S3Bucket}/pipeline-templates/ --delete +artifacts: + files: + - '**/*' + base-directory: 'generated-cicd-templates' + discard-paths: yes \ No newline at end of file diff --git a/buildspec/generate-sdlc-templates.py b/buildspec/generate-sdlc-templates.py new file mode 100644 index 0000000..9096b26 --- /dev/null +++ b/buildspec/generate-sdlc-templates.py @@ -0,0 +1,138 @@ +import boto3 +import sys +import time +import os +import json + +# Filenames +file_pipelines = 'Pipelines.json' +file_sdlc_parent = 'Scope-SDLC-Parent' +file_sdlc_child = 'Scope-SDLC-Child' + +# Open Files +# Input Files +with open(file_pipelines) as pl_file: + pipelines = json.load(pl_file) +with open(file_sdlc_parent + '.template') as sp_file: + sdlc_parent = json.load(sp_file) + + +with open(file_sdlc_child + '.template') as sc_file: + sdlc_child = json.load(sc_file) + +client = boto3.client('cloudformation') + +#MASTERINFRASTACK = os.environ['MasterInfraStack'] +#ENVIRONMENT = os.environ['Environment'] + +MASTERINFRASTACK = 'cicd-master-infra' +# ENVIRONMENT = 'cicd' + +# Get master infra stack resources +resource_summaries = client.list_stack_resources( + StackName=MASTERINFRASTACK +)['StackResourceSummaries'] + +# Get cicd child stack outputs +child_stack_outputs = {} +# Loop through master infra stack resources +for rs in resource_summaries: + # Only look for CloudFormation Stacks + if rs['ResourceType'] == 'AWS::CloudFormation::Stack': + # Get Stack outputs + child_stack_outputs[rs['LogicalResourceId']] = client.describe_stacks( + StackName = rs['PhysicalResourceId'] + )['Stacks'][0]['Outputs'] + +# Loop through infra scopes within pipeline file +for key, value in pipelines.items(): + scope = key.lower() + + # Determine stack output values + s3_bucket_name = list(filter(lambda item: item['OutputKey'] == 'S3BucketName', child_stack_outputs[scope]))[0]['OutputValue'] + kms_cmk_arn = list(filter(lambda item: item['OutputKey'] == 'KmsCmkArn', child_stack_outputs[scope]))[0]['OutputValue'] + + sdlc_parent['Resources'][scope] = { + "Type" : "AWS::CloudFormation::Stack", + "Properties": { + "Parameters": { + "S3BucketName": s3_bucket_name, + "CicdAccount": { + "Ref": "CicdAccount" + }, + "KmsCmkArn": kms_cmk_arn, + "MasterPipeline": { + "Ref": "MasterPipeline" + }, + "Environment": { + "Ref": "Environment" + }, + "Scope": scope, + "MasterS3BucketName": { + "Fn::Sub": "${S3BucketName}" + } + }, + "Tags" : [ + { + "Key": "Environment", + "Value": { + "Fn::Sub": "${Environment}" + } + } + ], + "TemplateURL" : { + "Fn::Sub": "https://s3.amazonaws.com/${S3BucketName}/generated-sdlc-templates/Scope-SDLC-Child-" + scope + ".template" + } + } + } + + # Insert Scope CICD Stacks + # cicd_parent['Resources']['Pipeline' + key] = { + # "Type": "AWS::CloudFormation::Stack", + # "Condition": "NotInitialCreation", + # "Properties": { + # "Parameters": { + # "DevAccount": { + # "Ref": "DevAccount" + # }, + # "ProdAccount": { + # "Ref": "ProdAccount" + # }, + # "MasterPipeline": { + # "Ref": "MasterPipeline" + # }, + # "Environment": { + # "Ref": "Environment" + # }, + # "Scope": { + # "Fn::Sub": "${Scope}" + # }, + # "AllEnvironmentsCreated": "False" + # }, + # "Tags": [{ + # "Key": "Environment", + # "Value": { + # "Fn::Sub": "${Environment}" + # } + # }], + # "TemplateURL": { + # "Fn::Sub": "https://s3.amazonaws.com/${S3BucketName}/generated-stacks/${Scope}.template" + # } + # } + # } + + # Add policy statements to PolicyBaseline + with open(file_sdlc_child + '.template') as sc_file: + sdlc_child = json.load(sc_file) + for ps in value['PolicyStatements']: + with open('policy-statements/' + str(ps) + '.template') as json_file: + data = json.load(json_file) + for statement in data['Statements']: + sdlc_child['Resources']['IamPolicyBaseline']['Properties']['PolicyDocument']['Statement'].append(statement) + + with open('generated-sdlc-templates/' + file_sdlc_child + '-' + scope + '.template', 'w') as sc_file_output: + json.dump(sdlc_child, sc_file_output, indent=4) + +# Save files +with open('generated-sdlc-templates/' + file_sdlc_parent + '.template', 'w') as sp_file_output: + json.dump(sdlc_parent, sp_file_output, indent=4) \ No newline at end of file diff --git a/buildspec/generate-sdlc-templates.yml b/buildspec/generate-sdlc-templates.yml new file mode 100644 index 0000000..d0ef0c1 --- /dev/null +++ b/buildspec/generate-sdlc-templates.yml @@ -0,0 +1,20 @@ +version: 0.2 + +phases: + install: + commands: + - echo Entered the install phase... + - pip install boto3 + build: + commands: + - echo Entered the build phase... + - mkdir -p generated-sdlc-templates + - python buildspec/generate-sdlc-templates.py + post_build: + commands: + - aws s3 sync generated-sdlc-templates s3://${S3Bucket}/generated-sdlc-templates/ --delete +artifacts: + files: + - '**/*' + base-directory: 'generated-sdlc-templates' + discard-paths: yes \ No newline at end of file diff --git a/buildspec/update-stacks.py b/buildspec/update-stacks.py new file mode 100644 index 0000000..7e75633 --- /dev/null +++ b/buildspec/update-stacks.py @@ -0,0 +1,73 @@ +import boto3 +import sys +import time +import os + +client = boto3.client('cloudformation') + +MASTERINFRASTACK = os.environ['MasterInfraStack'] +ENVIRONMENT = os.environ['Environment'] + +# List master infra stack resources +resource_summaries = client.list_stack_resources( + StackName=MASTERINFRASTACK +)['StackResourceSummaries'] + +# Update Stacks +change_sets = [] +for rs in resource_summaries: + if rs['ResourceType'] == 'AWS::CloudFormation::Stack': + stack_parameters = client.describe_stacks( + StackName = rs['PhysicalResourceId'] + )['Stacks'][0]['Parameters'] + stack_parameters[:] = [d for d in stack_parameters if d.get('ParameterKey') != 'AllEnvironmentsCreated'] + stack_parameters.append( + { + 'ParameterKey': 'AllEnvironmentsCreated', + 'ParameterValue': 'True', + 'UsePreviousValue': False + } + ) + # Create Change Set + change_set_id = client.create_change_set( + StackName = rs['PhysicalResourceId'], + UsePreviousTemplate=True, + Capabilities=[ + 'CAPABILITY_IAM', + 'CAPABILITY_NAMED_IAM', + 'CAPABILITY_AUTO_EXPAND', + ], + Parameters=stack_parameters, + ChangeSetName=ENVIRONMENT + '-' + rs['LogicalResourceId'], + ChangeSetType='UPDATE' + )['Id'] + change_set_description = {} + # Wait for change set to finish creating + while change_set_description == {} or change_set_description['Status'] == 'CREATE_PENDING' or change_set_description['Status'] == 'CREATE_IN_PROGRESS': + change_set_description = client.describe_change_set( + ChangeSetName=ENVIRONMENT + '-' + rs['LogicalResourceId'], + StackName=rs['PhysicalResourceId'] + ) + # If created, execute change set + if change_set_description['Status'] != 'FAILED': + change_set_status = client.execute_change_set( + ChangeSetName=ENVIRONMENT + '-' + rs['LogicalResourceId'], + StackName=rs['PhysicalResourceId'] + ) + # Add to wait to finish list + change_sets.append( + { + 'ChangeSetId': change_set_id, + 'ChangeSetName': ENVIRONMENT + '-' + rs['LogicalResourceId'], + 'StackName': rs['PhysicalResourceId'] + } + ) + +# Wait for change sets to finish executing +for cs in change_sets: + execution_status = '' + while execution_status == '' or execution_status == 'UNAVAILABLE' or execution_status == 'AVAILABLE' or execution_status == 'EXECUTE_IN_PROGRESS': + execution_status = client.describe_change_set( + ChangeSetName = cs['ChangeSetId'] + )['ExecutionStatus'] + time.sleep(1) \ No newline at end of file diff --git a/buildspec/update-stacks.yml b/buildspec/update-stacks.yml new file mode 100644 index 0000000..19fe872 --- /dev/null +++ b/buildspec/update-stacks.yml @@ -0,0 +1,11 @@ +version: 0.2 + +phases: + install: + commands: + - echo Entered the install phase... + - pip install boto3 + build: + commands: + - echo Entered the build phase... + - python buildspec/update-stacks.py \ No newline at end of file diff --git a/cfvars/Cicd.template b/cfvars/Cicd.template new file mode 100644 index 0000000..977b85b --- /dev/null +++ b/cfvars/Cicd.template @@ -0,0 +1,5 @@ +{ + "Parameters": { + "Environment": "cicd" + } +} \ No newline at end of file diff --git a/cfvars/Dev.template b/cfvars/Dev.template new file mode 100644 index 0000000..32949bd --- /dev/null +++ b/cfvars/Dev.template @@ -0,0 +1,5 @@ +{ + "Parameters": { + "Environment": "dev" + } +} \ No newline at end of file diff --git a/cfvars/Master.template b/cfvars/Master.template new file mode 100644 index 0000000..2609604 --- /dev/null +++ b/cfvars/Master.template @@ -0,0 +1,9 @@ +{ + "Parameters": { + "CicdAccount": "01234567890", + "DevAccount": "1234567890", + "ProdAccount": "234567890", + "SourceCodeCommitRepoName": "", + "AllEnvironmentsCreated": "True" + } +} \ No newline at end of file diff --git a/cfvars/Prod.template b/cfvars/Prod.template new file mode 100644 index 0000000..809a8e0 --- /dev/null +++ b/cfvars/Prod.template @@ -0,0 +1,5 @@ +{ + "Parameters": { + "Environment": "prod" + } +} \ No newline at end of file diff --git a/pipeline-templates/Default.template b/pipeline-templates/Default.template new file mode 100644 index 0000000..6cd8470 --- /dev/null +++ b/pipeline-templates/Default.template @@ -0,0 +1,434 @@ +{ + "AWSTemplateFormatVersion":"2010-09-09", + "Description":"Creates a pipeline.", + "Parameters":{ + "MasterPipeline": { + "Type": "String" + }, + "Scope": { + "Type": "String" + }, + "SubScope": { + "Type": "String" + }, + "SourceCodeCommitRepoName":{ + "Description":"(Optional). Source CodeCommit repository. If not specified, one will be created.", + "Type":"String", + "Default": "" + }, + "IncludeEnvCfTemplateConfigs":{ + "Description":"Include CloudFormation environment config files?", + "Type":"String", + "Default":"False", + "AllowedValues":[ + "False", + "True" + ] + }, + "CfContainsLambda":{ + "Description":"Does the cloudformation template contain a Lambda function?", + "Type":"String", + "Default":"False", + "AllowedValues":[ + "False", + "True" + ] + }, + "ProdAccount": { + "Type": "String" + }, + "DevAccount": { + "Type": "String" + }, + "Environment": { + "Type": "String" + }, + "KmsCmkArn": { + "Type": "String" + }, + "S3BucketName": { + "Type": "String" + }, + "RoleArn": { + "Type": "String" + }, + "ProdManualApproval": { + "Type": "String", + "Default": "True" + }, + "SdlcCloudFormation": { + "Type": "String", + "Default": "True" + }, + "SdlcCodeBuildPre": { + "Type": "String", + "Default": "False" + } + }, + "Conditions":{ + "CreateCodeCommitRepo":{ + "Fn::Equals":[ + "", + { + "Ref":"SourceCodeCommitRepoName" + } + ] + }, + "IncludeEnvCfTemplateConfigs":{ + "Fn::Equals":[ + "True", + { + "Ref":"IncludeEnvCfTemplateConfigs" + } + ] + }, + "CfContainsLambda":{ + "Fn::Equals":[ + "True", + { + "Ref":"CfContainsLambda" + } + ] + }, + "ProdManualApproval": { + "Fn::Equals":[ + "True", + { + "Ref":"ProdManualApproval" + } + ] + }, + "SdlcCloudFormation": { + "Fn::Equals":[ + "True", + { + "Ref":"SdlcCloudFormation" + } + ] + }, + "SdlcCodeBuildPre": { + "Fn::Equals":[ + "True", + { + "Ref":"SdlcCodeBuildPre" + } + ] + } + }, + "Resources":{ + "CodeCommit":{ + "Type":"AWS::CodeCommit::Repository", + "Condition":"CreateCodeCommitRepo", + "Properties":{ + "RepositoryDescription":"Repository created by CodePipeline", + "RepositoryName":{ + "Fn::Sub":"${Scope}-${SubScope}" + } + } + }, + "CodePipeline":{ + "Type":"AWS::CodePipeline::Pipeline", + "Properties":{ + "ArtifactStore":{ + "Type":"S3", + "Location":{ + "Ref": "S3BucketName" + }, + "EncryptionKey": { + "Id":{ + "Ref": "KmsCmkArn" + }, + "Type":"KMS" + } + }, + "RestartExecutionOnUpdate":"false", + "RoleArn": { + "Ref": "RoleArn" + }, + "Name": { + "Fn::Sub":"${Scope}-${SubScope}" + }, + "Stages": [ + { + "Name":"Source", + "Actions":[ + { + "ActionTypeId":{ + "Category":"Source", + "Owner":"AWS", + "Provider":"CodeCommit", + "Version":"1" + }, + "Configuration":{ + "RepositoryName":{ + "Fn::If":[ + "CreateCodeCommitRepo", + { + "Fn::GetAtt":[ + "CodeCommit", + "Name" + ] + }, + { + "Ref":"SourceCodeCommitRepoName" + } + ] + }, + "BranchName":"master" + }, + "Name":"CodeCommit", + "OutputArtifacts":[ + { + "Name":"SourceOutput" + } + ], + "RunOrder": 1 + } + ] + }, + { + "Name":"Dev", + "Actions":[ + { + "Fn::If": [ + "SdlcCodeBuildPre", + { + "ActionTypeId": { + "Category": "Build", + "Owner": "AWS", + "Provider": "CodeBuild", + "Version": "1" + }, + "Configuration": { + "ProjectName": { + "Fn::Sub": "dev-${MasterPipeline}-infra-${Scope}-CodeBuildPre" + } + }, + "Name": "RunCodeBuild", + "InputArtifacts": [ + { + "Name": "SourceOutput" + } + ], + "RunOrder": 1, + "RoleArn": { + "Fn::Sub":"arn:aws:iam::${DevAccount}:role/dev-${MasterPipeline}-infra-${Scope}-CodeBuildRole" + } + }, + { + "Ref": "AWS::NoValue" + } + ] + }, + { + "Fn::If": [ + "SdlcCloudFormation", + { + "ActionTypeId":{ + "Category":"Deploy", + "Owner":"AWS", + "Provider":"CloudFormation", + "Version":"1" + }, + "Configuration":{ + "ActionMode":"REPLACE_ON_FAILURE", + "Capabilities":"CAPABILITY_IAM", + "RoleArn":{ + "Fn::Sub":"arn:aws:iam::${DevAccount}:role/dev-${MasterPipeline}-infra-${Scope}-CloudFormationRole" + }, + "StackName":{ + "Fn::Sub":"dev-${Scope}-${SubScope}" + }, + "TemplatePath":"SourceOutput::CloudFormation.template", + "TemplateConfiguration":{ + "Fn::If":[ + "IncludeEnvCfTemplateConfigs", + "SourceOutput::cfvars/Dev.template", + { + "Ref":"AWS::NoValue" + } + ] + }, + "ParameterOverrides":{ + "Fn::Join": [ + "", + [ + "{", + { + "Fn::If":[ + "CfContainsLambda", + "\"S3BucketName\" : { \"Fn::GetArtifactAtt\" : [\"SourceOutput\", \"BucketName\"]}, \"S3ObjectKey\" : { \"Fn::GetArtifactAtt\" : [\"SourceOutput\", \"ObjectKey\"]},", + { + "Ref":"AWS::NoValue" + } + ] + }, + { + "Fn::Sub": "\"Environment\": \"dev\"," + }, + { + "Fn::Sub": "\"MasterPipeline\": \"${MasterPipeline}\"," + }, + { + "Fn::Sub": "\"Scope\": \"${Scope}\"" + }, + "}" + ] + ] + } + }, + "Name":"DeployCloudFormationDev", + "InputArtifacts":[ + { + "Name":"SourceOutput" + } + ], + "RunOrder": 2, + "RoleArn": { + "Fn::Sub":"arn:aws:iam::${DevAccount}:role/dev-${MasterPipeline}-infra-${Scope}-CodePipelineRole" + } + }, + { + "Ref": "AWS::NoValue" + } + ] + } + ] + }, + { + "Fn::If":[ + "ProdManualApproval", + { + "Name":"ManualApproval", + "Actions":[ + { + "Name":"ManualApproval", + "ActionTypeId":{ + "Category":"Approval", + "Owner":"AWS", + "Version":"1", + "Provider":"Manual" + }, + "RunOrder": 1 + } + ] + }, + { + "Ref": "AWS::NoValue" + } + ] + }, + { + "Name":"Prod", + "Actions":[ + { + "Fn::If": [ + "SdlcCodeBuildPre", + { + "ActionTypeId": { + "Category": "Build", + "Owner": "AWS", + "Provider": "CodeBuild", + "Version": "1" + }, + "Configuration": { + "ProjectName": { + "Fn::Sub": "prod-${MasterPipeline}-infra-${Scope}-CodeBuildPre" + } + }, + "Name": "RunCodeBuild", + "InputArtifacts": [ + { + "Name": "SourceOutput" + } + ], + "RunOrder": 1, + "RoleArn": { + "Fn::Sub":"arn:aws:iam::${ProdAccount}:role/prod-${MasterPipeline}-infra-${Scope}-CodeBuildRole" + } + }, + { + "Ref": "AWS::NoValue" + } + ] + }, + { + "Fn::If": [ + "SdlcCloudFormation", + { + "ActionTypeId":{ + "Category":"Deploy", + "Owner":"AWS", + "Provider":"CloudFormation", + "Version":"1" + }, + "Configuration":{ + "ActionMode":"REPLACE_ON_FAILURE", + "Capabilities":"CAPABILITY_IAM", + "RoleArn":{ + "Fn::Sub":"arn:aws:iam::${ProdAccount}:role/prod-${MasterPipeline}-infra-${Scope}-CloudFormationRole" + }, + "StackName":{ + "Fn::Sub":"prod-${Scope}-${SubScope}" + }, + "TemplatePath":"SourceOutput::CloudFormation.template", + "TemplateConfiguration":{ + "Fn::If":[ + "IncludeEnvCfTemplateConfigs", + "SourceOutput::cfvars/Prod.template", + { + "Ref":"AWS::NoValue" + } + ] + }, + "ParameterOverrides":{ + "Fn::Join": [ + "", + [ + "{", + { + "Fn::If":[ + "CfContainsLambda", + "\"S3BucketName\" : { \"Fn::GetArtifactAtt\" : [\"SourceOutput\", \"BucketName\"]}, \"S3ObjectKey\" : { \"Fn::GetArtifactAtt\" : [\"SourceOutput\", \"ObjectKey\"]},", + { + "Ref":"AWS::NoValue" + } + ] + }, + { + "Fn::Sub": "\"Environment\": \"prod\"," + }, + { + "Fn::Sub": "\"MasterPipeline\": \"${MasterPipeline}\"," + }, + { + "Fn::Sub": "\"Scope\": \"${Scope}\"" + }, + "}" + ] + ] + } + }, + "Name": "DeployCloudFormationProd", + "InputArtifacts": [ + { + "Name":"SourceOutput" + } + ], + "RunOrder": 2, + "RoleArn": { + "Fn::Sub":"arn:aws:iam::${ProdAccount}:role/prod-${MasterPipeline}-infra-${Scope}-CodePipelineRole" + } + }, + { + "Ref": "AWS::NoValue" + } + ] + } + ] + } + ] + } + } + } +} \ No newline at end of file diff --git a/policy-statements/Baseline.template b/policy-statements/Baseline.template new file mode 100644 index 0000000..91fa9d4 --- /dev/null +++ b/policy-statements/Baseline.template @@ -0,0 +1,104 @@ +{ + "Statements": [ + { + "Sid": "AllActionsWildcard", + "Effect": "Allow", + "Action": [ + "cloudformation:*", + "s3:*", + "codecommit:*", + "iam:*", + "kms:*", + "logs:*", + "dynamodb:*", + "sns:*", + "sqs:*", + "cloudwatch:*", + "lambda:*", + "codebuild:*" + ], + "Resource": [ + { + "Fn::Sub": "arn:aws:cloudformation:*:${AWS::AccountId}:stack/${Environment}-${Scope}-*" + }, + { + "Fn::Sub": "arn:aws:cloudformation:*:${AWS::AccountId}:changeSet/${Environment}-${Scope}-*:*" + }, + { + "Fn::Sub": "arn:aws:cloudformation:*:aws:transform/*" + }, + { + "Fn::Sub": "arn:aws:s3:::${Environment}-${Scope}-*" + }, + { + "Fn::Sub": "arn:aws:codecommit:*:${AWS::AccountId}:${Environment}-${Scope}-*" + }, + { + "Fn::Sub": "arn:aws:iam::${AWS::AccountId}:policy/${Environment}-${Scope}-*" + }, + { + "Fn::Sub": "arn:aws:kms:*:${AWS::AccountId}:alias/${Environment}-${Scope}-*" + }, + { + "Fn::Sub": "arn:aws:logs:*:${AWS::AccountId}:log-group:${Environment}-${Scope}-*" + }, + { + "Fn::Sub": "arn:aws:logs:*:${AWS::AccountId}:log-group:/aws/codebuild/${Environment}-${Scope}-*" + }, + { + "Fn::Sub": "arn:aws:dynamodb:*:${AWS::AccountId}:table/${Environment}-${Scope}-*" + }, + { + "Fn::Sub": "arn:aws:sns:*:${AWS::AccountId}:${Environment}-${Scope}-*" + }, + { + "Fn::Sub": "arn:aws:sqs:*:${AWS::AccountId}:${Environment}-${Scope}-*" + }, + { + "Fn::Sub": "arn:aws:cloudwatch:*:${AWS::AccountId}:alarm:${Environment}-${Scope}-*" + }, + { + "Fn::Sub": "arn:aws:cloudwatch::${AWS::AccountId}:dashboard/${Environment}-${Scope}-*" + }, + { + "Fn::Sub": "arn:aws:lambda:*:${AWS::AccountId}:function:${Environment}-${Scope}-*" + }, + { + "Fn::Sub": "arn:aws:codebuild:*:${AWS::AccountId}:project/${Environment}-${Scope}-*" + } + ] + }, + { + "Sid": "AllResourcesWildcard", + "Effect": "Allow", + "Action": [ + "cloudformation:DescribeStacks", + "cloudformation:List*", + "cloudformation:ValidateTemplate", + "cloudformation:EstimateTemplateCost", + "cloudformation:Get*", + "codecommit:ListRepositories", + "kms:CreateKey", + "kms:CreateAlias", + "kms:ListAliases", + "route53:CreateHostedZone" + ], + "Resource": "*" + }, + { + "Sid": "LambdaEventSourceMapping", + "Effect": "Allow", + "Action": [ + "lambda:*" + ], + "Resource": "*", + "Condition": { + "StringLike": { + "lambda:FunctionArn": { + "Fn::Sub": "arn:aws:lambda:*:${AWS::AccountId}:function:${Environment}-${Scope}-*" + } + } + } + } + ] +} \ No newline at end of file diff --git a/policy-statements/Frontend.template b/policy-statements/Frontend.template new file mode 100644 index 0000000..c58f7ff --- /dev/null +++ b/policy-statements/Frontend.template @@ -0,0 +1,20 @@ +{ + "Statements": [ + { + "Sid": "Public", + "Effect": "Allow", + "Action": [ + "route53:*", + "acm:*", + "cognito-idp:*", + "cognito-identity:*", + "apigateway:*", + "cloudfront:*", + "mobileanalytics:*", + "cognito-sync:*", + "execute-api:*" + ], + "Resource": "*" + } + ] +} \ No newline at end of file diff --git a/policy-statements/IamCreateRole.template b/policy-statements/IamCreateRole.template new file mode 100644 index 0000000..ff3259c --- /dev/null +++ b/policy-statements/IamCreateRole.template @@ -0,0 +1,38 @@ +{ + "Statements": [ + { + "Sid": "IamCreateRole", + "Effect": "Allow", + "Action": [ + "iam:CreateRole", + "iam:AttachRolePolicy", + "iam:DetachRolePolicy" + ], + "Resource": [ + { + "Fn::Sub": "arn:aws:iam::${AWS::AccountId}:role/${Environment}-${Scope}-*" + } + ], + "Condition": { + "StringEquals": { + "iam:PermissionsBoundary": { + "Fn::Sub": "arn:aws:iam::${AWS::AccountId}:policy/${Environment}-${MasterPipeline}-infra-${Scope}-IamPolicyBaseline" + } + } + } + }, + { + "Sid": "IamUpdateRole", + "Effect": "Allow", + "Action": [ + "iam:GetRole", + "iam:DeleteRole" + ], + "Resource": [ + { + "Fn::Sub": "arn:aws:iam::${AWS::AccountId}:role/${Environment}-${Scope}-*" + } + ] + } + ] +} \ No newline at end of file