AWSTemplateFormatVersion: 2010-09-09 Description: ECS Automation Template Parameters: GitRepo: Default: https://github.com/aws-samples/connectivity-patterns-for-amazon-vpc-lattice.git Type: String VpcName: Default: lattice-ingress-vpc Description: Logical name for the vpc lattice ingress vpc Type: String VpcCidr: AllowedPattern: "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/(1[6-9]|2[0-8]))$" Default: 192.168.1.0/24 Description: CIDR block for the VPC Type: String ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/y PublicSubnet1Cidr: AllowedPattern: "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/(1[6-9]|2[0-8]))$" Default: 192.168.1.0/27 Description: CIDR block for the Public Subnet 1 located in AZ 1 Type: String ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28 PublicSubnet2Cidr: AllowedPattern: "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/(1[6-9]|2[0-8]))$" Default: 192.168.1.32/27 Description: CIDR block for the Public Subnet 2 located in AZ 2 Type: String ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28 PublicSubnet3Cidr: AllowedPattern: "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/(1[6-9]|2[0-8]))$" Default: 192.168.1.64/27 Description: CIDR block for the Public Subnet 3 located in AZ 3 Type: String ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28 PrivateSubnet1Cidr: AllowedPattern: "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/(1[6-9]|2[0-8]))$" Default: 192.168.1.96/27 Description: CIDR block for the Private Subnet 1 located in AZ 1 Type: String ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28 PrivateSubnet2Cidr: AllowedPattern: "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/(1[6-9]|2[0-8]))$" Default: 192.168.1.128/27 Description: CIDR block for the Private Subnet 2 located in AZ 2 Type: String ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28 PrivateSubnet3Cidr: AllowedPattern: "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/(1[6-9]|2[0-8]))$" Default: 192.168.1.160/27 Description: CIDR block for the Private Subnet 3 located in AZ 3 Type: String ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28 Resources: # Pipeline # LatticeIngressECRRepo: Type: AWS::ECR::Repository DeletionPolicy: Retain LatticeIngressBucket: Type: AWS::S3::Bucket DeletionPolicy: Retain Properties: BucketName: !Sub - lattice-ingress-codepipeline-${AWS::Region}-${AWS::AccountId}-${RandomizedValue} - RandomizedValue: Fn::Select: [0, Fn::Split: [-, Fn::Select: [2, Fn::Split: [/, !Ref AWS::StackId ]]]] # Takes the first part of the random GUID in the cloudformation stacks arn. AccessControl: Private GitRepoToCodeCommitCustomResource: Type: Custom::CopyGitRepoToS3 Properties: ServiceToken: !GetAtt GitRepoToCodeCommitLambda.Arn GitRepoToCodeCommitLambdaRole: Type: AWS::IAM::Role Properties: 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 Policies: - PolicyDocument: Statement: - Effect: Allow Action: - s3:PutObject Resource: !Sub ${LatticeIngressBucket.Arn}/cloneFromGithub/* PolicyName: PutS3Object GitRepoToCodeCommitLambda: Type: AWS::Lambda::Function Properties: Description: Lambda to be triggered by Cloudformation Custom resource to copy GitHub repo to CodeCommit repo Code: ZipFile: | import boto3 import logging import os import shutil import cfnresponse s3 = boto3.client('s3') logger = logging.getLogger() logger.setLevel(logging.INFO) repo = os.environ['GitRepo'] s3Bucket = os.environ['s3Bucket'] path = '/tmp/repo' s3ObjectName = 'latticecingress' s3ObjectExtension = 'zip' s3ObjectFullName = s3ObjectName + '.' + s3ObjectExtension def lambda_handler(event, context): response_data = {} try: logger.info('Create path and change working directory to: %s' % (path)) os.mkdir(path) os.chdir(path) logger.info('Clone the repository: %s to: %s' % (repo, path)) os.system('git clone ' + repo + ' cloned-repo') shutil.rmtree('cloned-repo/.git') logger.info('Clone complete. Files in working directory:') logger.info(os.listdir(os.getcwd())) logger.info('Create Zip from repo') shutil.make_archive(s3ObjectName, s3ObjectExtension,'cloned-repo','.') logger.info('Created zip from repo. Files in working directory:') logger.info(os.listdir(os.getcwd())) logger.info('Uploading %s to S3://%s/%s' % (s3ObjectFullName, s3Bucket, 'cloneFromGithub/'+s3ObjectFullName)) s3.upload_file(os.getcwd() + '/' + s3ObjectFullName, s3Bucket, 'cloneFromGithub/'+s3ObjectFullName) logger.info('Upload Complete. Cleaning directory') shutil.rmtree(path) cfnresponse.send(event, context, cfnresponse.SUCCESS, response_data) except Exception as e: logger.error('Execution failed...') logger.error(str(e)) response_data['Data'] = str(e) cfnresponse.send(event, context, cfnresponse.FAILED, response_data) Environment: Variables: GitRepo: !Ref GitRepo s3Bucket: !Ref LatticeIngressBucket Handler: index.lambda_handler Runtime: python3.8 Layers: - !Sub arn:aws:lambda:${AWS::Region}:553035198032:layer:git-lambda2:8 # https://github.com/lambci/git-lambda-layer MemorySize: 1024 Role: !GetAtt GitRepoToCodeCommitLambdaRole.Arn CCRepo: DependsOn: - GitRepoToCodeCommitCustomResource Type: AWS::CodeCommit::Repository Properties: Code: S3: Bucket: !Ref LatticeIngressBucket Key: cloneFromGithub/latticecingress.zip RepositoryDescription: Repository for holding IPS solution Automation Code RepositoryName: !Sub ${AWS::StackName}-repository CPipelineCloudFormationRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: - cloudformation.amazonaws.com Action: sts:AssumeRole Policies: - PolicyName: cfnRolePolicy PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: '*' Resource: '*' CPipelineServiceRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Statement: - Effect: Allow Principal: Service: - codepipeline.amazonaws.com Action: - 'sts:AssumeRole' Policies: - PolicyName: CodePipelineRolePolicy PolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Action: - codecommit:CancelUploadArchive - codecommit:GetBranch - codecommit:GetCommit - codecommit:GetUploadArchiveStatus - codecommit:UploadArchive Resource: !Sub arn:aws:codecommit:${AWS::Region}:${AWS::AccountId}:${CCRepo.Name} - Effect: Allow Action: - codebuild:StartBuild - codebuild:BatchGetBuilds Resource: - !GetAtt LatticeIngressBuildProject.Arn - Effect: Allow Action: - cloudformation:CreateStack - cloudformation:DeleteStack - cloudformation:DescribeStacks - cloudformation:UpdateStack - cloudformation:CreateChangeSet - cloudformation:DeleteChangeSet - cloudformation:DescribeChangeSet - cloudformation:ExecuteChangeSet - cloudformation:SetStackPolicy - cloudformation:ValidateTemplate Resource: "*" - Effect: Allow Action: - iam:PassRole Resource: - !GetAtt CPipelineCloudFormationRole.Arn - Effect: Allow Action: - s3:* Resource: - !Sub ${LatticeIngressBucket.Arn} - !Sub ${LatticeIngressBucket.Arn}/* CPipelineCodeBuildRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: - codebuild.amazonaws.com Action: sts:AssumeRole Policies: - PolicyName: CodebuildRolePolicy PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - logs:CreateLogGroup - logs:CreateLogStream - logs:PutLogEvents Resource: '*' - Effect: Allow Action: - ecr:PutImage - ecr:PutImageTagMutability - ecr:InitiateLayerUpload - ecr:UploadLayerPart - ecr:CompleteLayerUpload - ecr:BatchCheckLayerAvailability - ecr:GetAuthorizationToken - ecr:BatchGetImage - ecr:GetDownloadUrlForLayer Resource: - !Sub ${LatticeIngressECRRepo.Arn} - Effect: Allow Action: - ecr:GetAuthorizationToken Resource: '*' - Effect: Allow Action: - ssm:GetParameter* - ssm:PutParameter Resource: !Sub arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/${AWS::StackName}/codebuild/* - Effect: Allow Action: - s3:PutObject - s3:GetObject - s3:GetObjectVersion - s3:GetBucketAcl - s3:GetBucketLocation Resource: - !Sub ${LatticeIngressBucket.Arn} - !Sub ${LatticeIngressBucket.Arn}/* LatticeIngressBuildProject: Type: AWS::CodeBuild::Project Properties: Artifacts: Type: CODEPIPELINE Environment: Type: ARM_CONTAINER Image: aws/codebuild/amazonlinux2-aarch64-standard:2.0 ComputeType: BUILD_GENERAL1_SMALL PrivilegedMode: true ImagePullCredentialsType: CODEBUILD EnvironmentVariables: - Name: AWS_REGION Type: PLAINTEXT Value: !Ref AWS::Region - Name: AWS_ACCOUNT_ID Type: PLAINTEXT Value: !Ref AWS::AccountId - Name: IMAGE_REPO_NAME Type: PLAINTEXT Value: !Sub ${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${LatticeIngressECRRepo} LogsConfig: CloudWatchLogs: Status: ENABLED GroupName: codebuild ServiceRole: !GetAtt CPipelineCodeBuildRole.Arn Source: Type: CODEPIPELINE BuildSpec: Dockerfiles/nginx/buildspec.yml CPipeline: Type: AWS::CodePipeline::Pipeline Properties: ArtifactStore: Type: S3 Location: !Ref LatticeIngressBucket DisableInboundStageTransitions: - Reason: "Disabling to permit code changes to internal stack before initial deployment" StageName: BuildStage Name: !Sub ${AWS::StackName}-pipeline RoleArn: !GetAtt CPipelineServiceRole.Arn Stages: - Name: SourceStage Actions: - Name: Source ActionTypeId: Category: Source Owner: AWS Provider: CodeCommit Version: '1' Configuration: RepositoryName: !GetAtt CCRepo.Name BranchName: main PollForSourceChanges: 'false' OutputArtifacts: - Name: SourceArtifact RunOrder: 1 - Name: BuildStage Actions: - Name: BuildNginxContainer ActionTypeId: Category: Build Owner: AWS Provider: CodeBuild Version: '1' Configuration: ProjectName: !Ref LatticeIngressBuildProject InputArtifacts: - Name: SourceArtifact Namespace: NginxBuild RunOrder: 2 - Name: CopyToS3 ActionTypeId: Category: Deploy Owner: AWS Provider: S3 Version: '1' InputArtifacts: - Name: SourceArtifact Configuration: BucketName: !Ref LatticeIngressBucket Extract: true ObjectKey: deployment RunOrder: 1 - Name: DeployApplicationStage Actions: - Name: CreateChangeSet ActionTypeId: Category: Deploy Owner: AWS Provider: CloudFormation Version: '1' InputArtifacts: - Name: SourceArtifact Configuration: ActionMode: CHANGE_SET_REPLACE Capabilities: CAPABILITY_IAM RoleArn: !GetAtt CPipelineCloudFormationRole.Arn StackName: !Sub ${AWS::StackName}-${AWS::AccountId}-ecs ChangeSetName: !Sub ${AWS::StackName}-${AWS::AccountId}-ecs-cset TemplatePath: SourceArtifact::cloudformation/ecs/cluster.yaml ParameterOverrides: !Sub | { "VpcId": "${VPC}", "VpcCidr": "${VpcCidr}", "PrivateSubnet1": "${PrivateSubnet1}", "PrivateSubnet2": "${PrivateSubnet2}", "PrivateSubnet3": "${PrivateSubnet3}", "PublicSubnet1": "${PublicSubnet1}", "PublicSubnet2": "${PublicSubnet2}", "PublicSubnet3": "${PublicSubnet3}", "NginxImage": "#{NginxBuild.image}" } RunOrder: 1 - Name: ExecuteChangeSet ActionTypeId: Category: Deploy Owner: AWS Provider: CloudFormation Version: '1' Configuration: ActionMode: CHANGE_SET_EXECUTE ChangeSetName: !Sub ${AWS::StackName}-${AWS::AccountId}-ecs-cset RoleArn: !GetAtt CPipelineCloudFormationRole.Arn StackName: !Sub ${AWS::StackName}-${AWS::AccountId}-ecs RunOrder: 2 CPipelineCloudWatchEventRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: - events.amazonaws.com Action: sts:AssumeRole Path: / Policies: - PolicyName: CWEventRolePolicy PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: codepipeline:StartPipelineExecution Resource: !Sub arn:aws:codepipeline:${AWS::Region}:${AWS::AccountId}:${AWS::StackName}-pipeline CPipelineCloudWatchEventRule: Type: AWS::Events::Rule Properties: EventPattern: source: - aws.codecommit detail-type: - 'CodeCommit Repository State Change' resources: - !Sub arn:aws:codecommit:${AWS::Region}:${AWS::AccountId}:${AWS::StackName}-repository detail: event: - referenceCreated - referenceUpdated referenceType: - branch referenceName: - main Targets: - Arn: !Sub arn:aws:codepipeline:${AWS::Region}:${AWS::AccountId}:${AWS::StackName}-pipeline RoleArn: !GetAtt CPipelineCloudWatchEventRole.Arn Id: codepipeline-AppPipeline # Security # EndpointSecurityGroup: Type: 'AWS::EC2::SecurityGroup' Properties: VpcId: !Ref VPC GroupDescription: VPC Endpoint Security group SecurityGroupIngress: - CidrIp: !Ref VpcCidr IpProtocol: '-1' FromPort: 443 ToPort: 443 Description: Ingress rule for tcp 443 SecurityGroupEgress: - CidrIp: 0.0.0.0/0 IpProtocol: '-1' FromPort: -1 ToPort: -1 Tags: - Key: Name Value: Endpoint SG # Network # VPC: Type: AWS::EC2::VPC Properties: CidrBlock: !Ref VpcCidr EnableDnsSupport: true EnableDnsHostnames: true Tags: - Key: "Name" Value: !Ref VpcName InternetGateway: Type: AWS::EC2::InternetGateway InternetGatewayAttachment: Type: AWS::EC2::VPCGatewayAttachment Properties: VpcId: !Ref VPC InternetGatewayId: !Ref InternetGateway # Public Subnets PublicSubnet1: Type: AWS::EC2::Subnet Properties: CidrBlock: !Ref PublicSubnet1Cidr AvailabilityZone: !Select [0, Fn::GetAZs: !Ref 'AWS::Region'] VpcId: !Ref VPC MapPublicIpOnLaunch: true PublicSubnet2: Type: AWS::EC2::Subnet Properties: CidrBlock: !Ref PublicSubnet2Cidr AvailabilityZone: !Select [1, Fn::GetAZs: !Ref 'AWS::Region'] VpcId: !Ref VPC MapPublicIpOnLaunch: true PublicSubnet3: Type: AWS::EC2::Subnet Properties: CidrBlock: !Ref PublicSubnet3Cidr AvailabilityZone: !Select [2, Fn::GetAZs: !Ref 'AWS::Region'] VpcId: !Ref VPC MapPublicIpOnLaunch: true PublicSubnetRouteTable: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref VPC PublicSubnetDefaultIPv4Route: DependsOn: - InternetGatewayAttachment Type: AWS::EC2::Route Properties: DestinationCidrBlock: 0.0.0.0/0 RouteTableId: !Ref PublicSubnetRouteTable GatewayId: !Ref InternetGateway PublicSubnet1RouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: RouteTableId: !Ref PublicSubnetRouteTable SubnetId: !Ref PublicSubnet1 PublicSubnet2RouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: RouteTableId: !Ref PublicSubnetRouteTable SubnetId: !Ref PublicSubnet2 PublicSubnet3RouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: RouteTableId: !Ref PublicSubnetRouteTable SubnetId: !Ref PublicSubnet3 # Private Subnets # PrivateSubnet1: Type: AWS::EC2::Subnet Properties: CidrBlock: !Ref PrivateSubnet1Cidr AvailabilityZone: !Select [0, Fn::GetAZs: !Ref 'AWS::Region'] VpcId: !Ref VPC MapPublicIpOnLaunch: false PrivateSubnet1RouteTable: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref VPC PrivateSubnet1RouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: RouteTableId: !Ref PrivateSubnet1RouteTable SubnetId: !Ref PrivateSubnet1 PrivateSubnet2: Type: AWS::EC2::Subnet Properties: CidrBlock: !Ref PrivateSubnet2Cidr AvailabilityZone: !Select [1, Fn::GetAZs: !Ref 'AWS::Region'] VpcId: !Ref VPC MapPublicIpOnLaunch: false PrivateSubnet2RouteTable: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref VPC PrivateSubnet2RouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: RouteTableId: !Ref PrivateSubnet2RouteTable SubnetId: !Ref PrivateSubnet2 PrivateSubnet3: Type: AWS::EC2::Subnet Properties: CidrBlock: !Ref PrivateSubnet3Cidr AvailabilityZone: !Select [2, Fn::GetAZs: !Ref 'AWS::Region'] VpcId: !Ref VPC MapPublicIpOnLaunch: false PrivateSubnet3RouteTable: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref VPC PrivateSubnet3RouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: RouteTableId: !Ref PrivateSubnet3RouteTable SubnetId: !Ref PrivateSubnet3 # VPC Endpoints # EcrDkrEndpoint: Type: AWS::EC2::VPCEndpoint Properties: PrivateDnsEnabled: true SecurityGroupIds: - !Ref EndpointSecurityGroup ServiceName: !Sub com.amazonaws.${AWS::Region}.ecr.dkr SubnetIds: - !Ref PrivateSubnet1 - !Ref PrivateSubnet2 - !Ref PrivateSubnet3 VpcEndpointType: Interface VpcId: !Ref VPC EcrApiEndpoint: Type: AWS::EC2::VPCEndpoint Properties: PrivateDnsEnabled: true SecurityGroupIds: - !Ref EndpointSecurityGroup ServiceName: !Sub com.amazonaws.${AWS::Region}.ecr.api SubnetIds: - !Ref PrivateSubnet1 - !Ref PrivateSubnet2 - !Ref PrivateSubnet3 VpcEndpointType: Interface VpcId: !Ref VPC CwatchLogsEndpoint: Type: AWS::EC2::VPCEndpoint Properties: PrivateDnsEnabled: true SecurityGroupIds: - !Ref EndpointSecurityGroup ServiceName: !Sub com.amazonaws.${AWS::Region}.logs SubnetIds: - !Ref PrivateSubnet1 - !Ref PrivateSubnet2 - !Ref PrivateSubnet3 VpcEndpointType: Interface VpcId: !Ref VPC S3GatewayEndpoint: Type: AWS::EC2::VPCEndpoint Properties: RouteTableIds: - !Ref PrivateSubnet1RouteTable - !Ref PrivateSubnet2RouteTable - !Ref PrivateSubnet3RouteTable ServiceName: !Sub com.amazonaws.${AWS::Region}.s3 VpcEndpointType: Gateway VpcId: !Ref VPC SsmEndpoint: Type: AWS::EC2::VPCEndpoint Properties: PrivateDnsEnabled: true SecurityGroupIds: - !Ref EndpointSecurityGroup ServiceName: !Sub com.amazonaws.${AWS::Region}.ssm SubnetIds: - !Ref PrivateSubnet1 - !Ref PrivateSubnet2 - !Ref PrivateSubnet3 VpcEndpointType: Interface VpcId: !Ref VPC SsmMessagesEndpoint: Type: AWS::EC2::VPCEndpoint Properties: PrivateDnsEnabled: true SecurityGroupIds: - !Ref EndpointSecurityGroup ServiceName: !Sub com.amazonaws.${AWS::Region}.ssmmessages SubnetIds: - !Ref PrivateSubnet1 - !Ref PrivateSubnet2 - !Ref PrivateSubnet3 VpcEndpointType: Interface VpcId: !Ref VPC Ec2MessagesEndpoint: Type: AWS::EC2::VPCEndpoint Properties: PrivateDnsEnabled: true SecurityGroupIds: - !Ref EndpointSecurityGroup ServiceName: !Sub com.amazonaws.${AWS::Region}.ec2messages SubnetIds: - !Ref PrivateSubnet1 - !Ref PrivateSubnet2 - !Ref PrivateSubnet3 VpcEndpointType: Interface VpcId: !Ref VPC