AWSTemplateFormatVersion: '2010-09-09' Description: This template deploys a .NET CI/CD environment via AWS Fargate (qs-1sctmf6go) Metadata: cfn-lint: config: ignore_checks: - W9006 AWS::CloudFormation::Interface: ParameterGroups: - Label: default: Quick Start parameters Parameters: - QSS3BucketName - QSS3KeyPrefix ParameterLabels: QSS3BucketName: default: "Quick Start S3 bucket name" QSS3KeyPrefix: default: "Quick Start S3 key prefix" Parameters: QSS3BucketName: AllowedPattern: '^[0-9a-zA-Z]+([0-9a-zA-Z-]*[0-9a-zA-Z])*$' ConstraintDescription: The Quick Start bucket name can include numbers, lowercase letters, uppercase letters, and hyphens (-). It cannot start or end with a hyphen (-). Default: aws-quickstart Description: 'Name of the S3 bucket for your copy of the Quick Start assets. Keep the default name unless you are customizing the template. Changing the name updates code references to point to a new Quick Start location. This name can include numbers, lowercase letters, uppercase letters, and hyphens, but do not start or end with a hyphen (-). See https://aws-quickstart.github.io/option1.html.' Type: String QSS3KeyPrefix: AllowedPattern: '^[0-9a-zA-Z-/]*$' ConstraintDescription: The Quick Start S3 key prefix can include numbers, lowercase letters, uppercase letters, hyphens (-), and forward slashes (/). Default: quickstart-dotnetcore-fargate-cicd/ Description: 'S3 key prefix that is used to simulate a directory for your copy of the Quick Start assets. Keep the default prefix unless you are customizing the template. Changing this prefix updates code references to point to a new Quick Start location. This prefix can include numbers, lowercase letters, uppercase letters, hyphens (-), and forward slashes (/). See https://docs.aws.amazon.com/AmazonS3/latest/dev/UsingMetadata.html and https://aws-quickstart.github.io/option1.html.' Type: String Mappings: SubnetConfig: VPC: CIDR: 10.10.10.0/24 PublicSubnet1: CIDR: 10.10.10.0/27 PrivateSubnet1: CIDR: 10.10.10.32/27 PublicSubnet2: CIDR: 10.10.10.64/27 PrivateSubnet2: CIDR: 10.10.10.96/27 CodeBuildImages: DotNet: ImageName: aws/codebuild/standard:5.0 ## Resources Resources: ## VPC VPC: Type: AWS::EC2::VPC Properties: EnableDnsSupport: true EnableDnsHostnames: true CidrBlock: !FindInMap - SubnetConfig - VPC - CIDR Tags: - Key: Name Value: !Sub ${AWS::StackName}-VPC - Key: Application Value: !Ref 'AWS::StackName' PublicSubnet1: Type: AWS::EC2::Subnet Properties: AvailabilityZone: !Select - 0 - Fn::GetAZs: !Ref 'AWS::Region' VpcId: !Ref 'VPC' CidrBlock: !FindInMap - SubnetConfig - PublicSubnet1 - CIDR Tags: - Key: Name Value: !Sub ${AWS::StackName}-PublicSubnet1 - Key: Application Value: !Ref 'AWS::StackName' - Key: Description Value: !Sub ${AWS::StackName} Public Resources PrivateSubnet1: Type: AWS::EC2::Subnet Properties: AvailabilityZone: !Select - 0 - Fn::GetAZs: !Ref 'AWS::Region' VpcId: !Ref 'VPC' CidrBlock: !FindInMap - SubnetConfig - PrivateSubnet1 - CIDR Tags: - Key: Name Value: !Sub ${AWS::StackName}-PrivateSubnet1 - Key: Application Value: !Ref 'AWS::StackName' - Key: Description Value: !Sub ${AWS::StackName} Private Resources PublicSubnet2: Type: AWS::EC2::Subnet Properties: AvailabilityZone: !Select - 1 - Fn::GetAZs: !Ref 'AWS::Region' VpcId: !Ref 'VPC' CidrBlock: !FindInMap - SubnetConfig - PublicSubnet2 - CIDR Tags: - Key: Name Value: !Sub ${AWS::StackName}-PublicSubnet2 - Key: Application Value: !Ref 'AWS::StackName' - Key: Description Value: !Sub ${AWS::StackName} Public Resources PrivateSubnet2: Type: AWS::EC2::Subnet Properties: AvailabilityZone: !Select - 1 - Fn::GetAZs: !Ref 'AWS::Region' VpcId: !Ref 'VPC' CidrBlock: !FindInMap - SubnetConfig - PrivateSubnet2 - CIDR Tags: - Key: Name Value: !Sub ${AWS::StackName}-PrivateSubnet2 - Key: Application Value: !Ref 'AWS::StackName' - Key: Description Value: !Sub ${AWS::StackName} Private Resources ## Gateways # Internet Gateway InternetGateway: Type: AWS::EC2::InternetGateway Properties: Tags: - Key: Name Value: !Sub ${AWS::StackName}-InternetGateway - Key: Application Value: !Ref 'AWS::StackName' GatewayToInternet: Type: AWS::EC2::VPCGatewayAttachment Properties: VpcId: !Ref 'VPC' InternetGatewayId: !Ref 'InternetGateway' # NAT Gateways NatGateway1: DependsOn: GatewayToInternet Type: AWS::EC2::NatGateway Properties: Tags: - Key: Name Value: !Sub ${AWS::StackName}-NatGateway1 - Key: Application Value: !Ref 'AWS::StackName' AllocationId: Fn::GetAtt: - EIP1 - AllocationId SubnetId: !Ref PublicSubnet1 EIP1: Type: AWS::EC2::EIP Properties: Domain: vpc NatGateway2: DependsOn: GatewayToInternet Type: AWS::EC2::NatGateway Properties: Tags: - Key: Name Value: !Sub ${AWS::StackName}-NatGateway2 - Key: Application Value: !Ref 'AWS::StackName' AllocationId: Fn::GetAtt: - EIP2 - AllocationId SubnetId: !Ref PublicSubnet2 EIP2: Type: AWS::EC2::EIP Properties: Domain: vpc ## Routes and Route Tables PublicRouteTable: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref VPC Tags: - Key: Name Value: !Sub ${AWS::StackName}-PublicRouteTable - Key: Application Value: !Ref 'AWS::StackName' PublicRoute: Type: AWS::EC2::Route DependsOn: GatewayToInternet Properties: RouteTableId: !Ref PublicRouteTable DestinationCidrBlock: 0.0.0.0/0 GatewayId: !Ref InternetGateway PrivateRouteTable1: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref VPC Tags: - Key: Name Value: !Sub ${AWS::StackName}-PrivateRouteTable1 - Key: Application Value: !Ref 'AWS::StackName' PrivateRoute1: Type: AWS::EC2::Route Properties: RouteTableId: !Ref PrivateRouteTable1 DestinationCidrBlock: 0.0.0.0/0 NatGatewayId: !Ref NatGateway1 PrivateRouteTable2: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref VPC Tags: - Key: Name Value: !Sub ${AWS::StackName}-PrivateRouteTable2 - Key: Application Value: !Ref 'AWS::StackName' PrivateRoute2: Type: AWS::EC2::Route Properties: RouteTableId: !Ref PrivateRouteTable2 DestinationCidrBlock: 0.0.0.0/0 NatGatewayId: !Ref NatGateway2 ## Subnet Route Table Associations PublicSubnet1RouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref PublicSubnet1 RouteTableId: !Ref PublicRouteTable PublicSubnet2RouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref PublicSubnet2 RouteTableId: !Ref PublicRouteTable PrivateSubnet1RouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref PrivateSubnet1 RouteTableId: !Ref PrivateRouteTable1 PrivateSubnet2RouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref PrivateSubnet2 RouteTableId: !Ref PrivateRouteTable2 ## SecurityGroups LoadBalancerSG: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: !Sub ${AWS::StackName} Load Balancer Security Group Tags: - Key: Name Value: !Sub ${AWS::StackName}-Load Balancer SG - Key: Application Value: !Ref 'AWS::StackName' VpcId: !Ref 'VPC' SecurityGroupEgress: - Description: Allow outbound HTTP with the Internet IpProtocol: "tcp" FromPort: 80 ToPort: 80 CidrIp: "0.0.0.0/0" SecurityGroupIngress: - Description: Expose HTTP to the world IpProtocol: "tcp" FromPort: 80 ToPort: 80 CidrIp: "0.0.0.0/0" FargateSG: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: !Sub ${AWS::StackName} Fargate Security Group Tags: - Key: Name Value: !Sub ${AWS::StackName}-Fargate SG - Key: Application Value: !Ref 'AWS::StackName' VpcId: !Ref 'VPC' SecurityGroupEgress: - Description: Allow outbound HTTP access to the Internet. IpProtocol: "tcp" FromPort: 0 ToPort: 65535 CidrIp: 0.0.0.0/0 SecurityGroupIngress: - Description: Allow HTTP access to the Internet. IpProtocol: "tcp" FromPort: 80 ToPort: 80 SourceSecurityGroupId: !GetAtt LoadBalancerSG.GroupId ## Load Balancer LoadBalancer: DependsOn: GatewayToInternet Type: AWS::ElasticLoadBalancingV2::LoadBalancer Properties: Scheme: internet-facing Subnets: - !Ref PublicSubnet1 - !Ref PublicSubnet2 SecurityGroups: - !Ref LoadBalancerSG Type: application Tags: - Key: Name Value: !Sub ${AWS::StackName}-LoadBalancer - Key: Application Value: !Ref 'AWS::StackName' LoadBalancerTargetGroup: Type: AWS::ElasticLoadBalancingV2::TargetGroup Properties: Port: 80 Protocol: HTTP TargetType: ip # Needed for Fargate HealthCheckTimeoutSeconds: 5 HealthCheckIntervalSeconds: 10 HealthyThresholdCount: 2 Tags: - Key: Name Value: !Sub ${AWS::StackName}-LoadBalancerTargetGroup - Key: Application Value: !Ref 'AWS::StackName' VpcId: !Ref VPC LoadBalancerListener: Type: AWS::ElasticLoadBalancingV2::Listener Properties: DefaultActions: - Type: forward TargetGroupArn: !Ref LoadBalancerTargetGroup LoadBalancerArn: !Ref LoadBalancer Port: 80 Protocol: HTTP ## S3 Bucket for Artifacts and State ArtifactStore: Type: AWS::S3::Bucket DeletionPolicy: Retain UpdateReplacePolicy: Retain Properties: BucketEncryption: ServerSideEncryptionConfiguration: - BucketKeyEnabled: false VersioningConfiguration: Status: Enabled Tags: - Key: Name Value: !Sub ${AWS::StackName}-ArtifactStoreBucket - Key: Application Value: !Ref 'AWS::StackName' ## IAM Roles # CodeBuild CodeBuildServiceRole: Type: AWS::IAM::Role Metadata: cfn-lint: config: ignore_checks: - EIAMPolicyWildcardResource Properties: Path: / AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: codebuild.amazonaws.com Action: sts:AssumeRole Policies: - PolicyName: root PolicyDocument: Version: 2012-10-17 Statement: - Resource: "*" Effect: Allow Action: - logs:CreateLogGroup - logs:CreateLogStream - logs:PutLogEvents - ecr:GetAuthorizationToken - Resource: !Sub arn:${AWS::Partition}:s3:::${ArtifactStore}/* Effect: Allow Action: - s3:GetObject - s3:PutObject - s3:GetObjectVersion - Resource: !Sub arn:${AWS::Partition}:ecr:${AWS::Region}:${AWS::AccountId}:repository/${ImageRepository} Effect: Allow Action: - ecr:GetDownloadUrlForLayer - ecr:BatchGetImage - ecr:BatchCheckLayerAvailability - ecr:PutImage - ecr:InitiateLayerUpload - ecr:UploadLayerPart - ecr:CompleteLayerUpload # CodePipeline CodePipelineServiceRole: Type: AWS::IAM::Role Properties: Path: / AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: codepipeline.amazonaws.com Action: sts:AssumeRole Policies: - PolicyName: root PolicyDocument: Version: 2012-10-17 Statement: # Allow access to CodeCommit - Resource: !GetAtt SourceRepository.Arn Effect: Allow Action: - 'codecommit:GetBranch' - 'codecommit:GetCommit' - 'codecommit:UploadArchive' - 'codecommit:GetUploadArchiveStatus' - 'codecommit:CancelUploadArchive' - Resource: !Sub arn:${AWS::Partition}:s3:::${ArtifactStore}/* Effect: Allow Action: - s3:PutObject - s3:GetObject - s3:GetObjectVersion - s3:GetBucketVersioning - Resource: [ !GetAtt CodeBuildServiceRole.Arn, !GetAtt TaskExecutionRole.Arn ] Effect: Allow Action: iam:PassRole - Resource: !GetAtt CodeBuildProject.Arn Effect: Allow Action: - codebuild:StartBuild - codebuild:BatchGetBuilds - Resource: !Sub arn:${AWS::Partition}:ecs:*:${AWS::AccountId}:service/* Effect: Allow Action: - ecs:DescribeServices - Resource: !Ref FargateService Effect: Allow Action: - ecs:UpdateService - Resource: !Sub arn:${AWS::Partition}:ecs:*:${AWS::AccountId}:task/* Effect: Allow Action: - ecs:DescribeTasks - Resource: !Sub arn:${AWS::Partition}:ecs:*:${AWS::AccountId}:container-instance/* Effect: Allow Action: - ecs:ListTasks - Resource: "*" Effect: Allow Action: - ecs:DescribeTaskDefinition - ecs:RegisterTaskDefinition # ECS/Fargate TaskExecutionRole: Type: AWS::IAM::Role Properties: Path: / AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Action: sts:AssumeRole Effect: Allow Principal: Service: ecs-tasks.amazonaws.com ManagedPolicyArns: - !Sub arn:${AWS::Partition}:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy ## Fargate Hosting ImageRepository: Type: AWS::ECR::Repository DeletionPolicy: Retain UpdateReplacePolicy: Retain ECSCluster: Type: AWS::ECS::Cluster Properties: ClusterName: !Ref AWS::StackName LogGroup: Type: AWS::Logs::LogGroup Properties: LogGroupName: !Sub /ecs/${AWS::StackName} RetentionInDays: 30 FargateService: Type: AWS::ECS::Service DependsOn: [ LoadBalancerListener ] Properties: Cluster: !Ref ECSCluster DesiredCount: 2 TaskDefinition: !Ref TaskDefinition LaunchType: FARGATE NetworkConfiguration: AwsvpcConfiguration: AssignPublicIp: ENABLED SecurityGroups: - !Ref FargateSG Subnets: [!Ref PrivateSubnet1, !Ref PrivateSubnet2] LoadBalancers: - ContainerName: dotnet-app ContainerPort: 80 TargetGroupArn: !Ref LoadBalancerTargetGroup TaskDefinition: Type: AWS::ECS::TaskDefinition Properties: Family: !Sub ${AWS::StackName}-dotnet-app RequiresCompatibilities: [ FARGATE ] Memory: '512' Cpu: '256' NetworkMode: awsvpc ExecutionRoleArn: !GetAtt TaskExecutionRole.Arn ContainerDefinitions: - Name: dotnet-app Image: amazon/amazon-ecs-sample Essential: true Memory: 256 MountPoints: - SourceVolume: my-volume ContainerPath: /var/www/my-volume PortMappings: - ContainerPort: 80 LogConfiguration: LogDriver: awslogs Options: awslogs-region: !Ref AWS::Region awslogs-group: !Ref LogGroup awslogs-stream-prefix: !Ref AWS::StackName Volumes: - Name: my-volume ## CI/CD # CodeCommit and initial population SourceRepository: Type: AWS::CodeCommit::Repository Properties: RepositoryName: !Sub ${AWS::StackName}-repo RepositoryDescription: Hosts the code used for the Fargate application. Code: BranchName: main S3: Bucket: !Ref QSS3BucketName Key: !Sub ${QSS3KeyPrefix}fargate-cicd.zip # CodePipeline Pipeline: Type: AWS::CodePipeline::Pipeline Properties: RoleArn: !GetAtt CodePipelineServiceRole.Arn ArtifactStore: Type: S3 Location: !Ref ArtifactStore Stages: - Name: Source Actions: - InputArtifacts: [] Name: Source ActionTypeId: Category: Source Owner: AWS Version: '1' Provider: CodeCommit Configuration: BranchName: main RepositoryName: !GetAtt SourceRepository.Name OutputArtifacts: - Name: SourceCode RunOrder: 1 - Name: Build Actions: - Name: Build ActionTypeId: Category: Build Owner: AWS Version: '1' Provider: CodeBuild Configuration: ProjectName: !Ref CodeBuildProject InputArtifacts: - Name: SourceCode OutputArtifacts: - Name: BuildOutput RunOrder: 1 - Name: Deploy Actions: - Name: Deploy ActionTypeId: Category: Deploy Owner: AWS Version: '1' Provider: ECS Configuration: ClusterName: !Ref ECSCluster ServiceName: !Ref FargateService FileName: images.json InputArtifacts: - Name: BuildOutput RunOrder: 1 # CodeBuild CodeBuildProject: Type: AWS::CodeBuild::Project Properties: Artifacts: Type: CODEPIPELINE Source: Type: CODEPIPELINE Environment: Type: LINUX_CONTAINER ComputeType: BUILD_GENERAL1_SMALL PrivilegedMode: true # Allows access to Docker daemon Image: !FindInMap [ CodeBuildImages, DotNet, ImageName ] EnvironmentVariables: - Name: AWS_ACCOUNT_ID Value: !Ref AWS::AccountId - Name: AWS_DEFAULT_REGION Value: !Ref AWS::Region - Name: REPOSITORY_URI Value: !Sub ${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${ImageRepository} Name: !Ref AWS::StackName ServiceRole: !Ref CodeBuildServiceRole ## Outputs Outputs: RepoUrl: Description: The URL for the CodeCommit repository. Value: !GetAtt SourceRepository.CloneUrlHttp LoadBalancerUrl: Description: The URL for the Application Load Balancer in front of the Fargate service. Value: !Sub http://${LoadBalancer.DNSName} CodePipelineName: Description: The name of the CodePipeline pipeline. Value: !Ref Pipeline CodePipelineUrl: Value: !Sub https://console.aws.amazon.com/codepipeline/home?region=${AWS::Region}#/view/${Pipeline}