diff --git a/.dockerignore b/.dockerignore index eb0bb63..110e7c0 100644 --- a/.dockerignore +++ b/.dockerignore @@ -4,4 +4,6 @@ node_modules npm-debug.log README.md .next -.env \ No newline at end of file +.env +cloudformation +codebuild \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index c473655..dac8796 100644 --- a/Dockerfile +++ b/Dockerfile @@ -37,6 +37,8 @@ RUN yarn build FROM node:14-alpine AS runner WORKDIR /app +RUN apk add curl + RUN addgroup -g 1001 -S nodejs RUN adduser -S nextjs -u 1001 diff --git a/cloudformation/application.template b/cloudformation/application.template new file mode 100644 index 0000000..088e3f7 --- /dev/null +++ b/cloudformation/application.template @@ -0,0 +1,374 @@ +## +## CloudFormation template for application services and tasks +## Author: JoaoLippi +## Since: 2021-12-09 +## +## This template provides resources for an application, including tasks, services and docker repositories. +## It also creates a Rule in the Load Balancer's Listener, that needs to be updated manually afterwards +## + +AWSTemplateFormatVersion: 2010-09-09 +Resources: + # ECR - Docker repository + ApplicationRepository: + Type: 'AWS::ECR::Repository' + Properties: + RepositoryName: !Sub '${AWS::StackName}' + + # Load Balancer - needs manual update + ApplicationTargetGroup: + Type: 'AWS::ElasticLoadBalancingV2::TargetGroup' + Properties: + HealthCheckEnabled: true + HealthCheckIntervalSeconds: 30 + HealthCheckPath: !Ref HealthCheckPath + HealthCheckPort: traffic-port + HealthCheckProtocol: HTTP + HealthCheckTimeoutSeconds: 5 + HealthyThresholdCount: 5 + UnhealthyThresholdCount: 2 + Matcher: + HttpCode: 200 + Name: !Sub '${AWS::StackName}-tg' + Port: 80 + Protocol: HTTP + TargetType: instance + VpcId: !Ref VPC + ApplicationListenerRule: + Type: 'AWS::ElasticLoadBalancingV2::ListenerRule' + Properties: + Actions: + - Type: forward + TargetGroupArn: !Ref ApplicationTargetGroup + Conditions: + - Field: source-ip + SourceIpConfig: + Values: + - '0.0.0.0/0' + ListenerArn: !Ref ListenerArn + Priority: !Ref RulePriority + + # ECS - Cluster task and service settings + ApplicationTask: + Type: 'AWS::ECS::TaskDefinition' + DependsOn: ApplicationRepository + Properties: + ContainerDefinitions: + # Application container + - Cpu: 0 + DockerLabels: + stack: !Sub '${AWS::StackName}' + Essential: true + HealthCheck: + Command: + - CMD-SHELL + - !Sub 'curl -f http://localhost:3000${HealthCheckPath} || exit 1' + Interval: 10 + Retries: 3 + StartPeriod: 30 + Timeout: 60 + Image: !Sub '${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${ApplicationRepository}:latest' + LogConfiguration: + LogDriver: awslogs + Options: + awslogs-group: !Sub '/ecs/${AWS::StackName}-task' + awslogs-region: !Ref AWS::Region + awslogs-create-group: 'true' + awslogs-stream-prefix: ecs + awslogs-datetime-format: '%Y-%m-%d %H:%M:%S' + Memory: !Ref HardMemory + MemoryReservation: !Ref SoftMemory + Name: !Sub '${AWS::StackName}-container' + PortMappings: + - HostPort: 0 + ContainerPort: 3000 + Protocol: tcp + Secrets: !If + - UseSSM + - - Name: DB_URL + ValueFrom: !Sub '${DbName}-url' + - Name: DB_NAME + ValueFrom: !Sub '${DbName}-name' + - Name: DB_USER + ValueFrom: !Sub '${DbName}-user' + - Name: DB_PASS + ValueFrom: !Sub '${DbName}-pass' + - [] + ExecutionRoleArn: !GetAtt TaskRole.Arn + Family: !Sub '${AWS::StackName}-task' + RequiresCompatibilities: + - EC2 + ApplicationService: + Type: 'AWS::ECS::Service' + DependsOn: ApplicationListenerRule + Properties: + Cluster: !Ref ClusterName + DeploymentConfiguration: + MaximumPercent: !Ref MaxHealth + MinimumHealthyPercent: !Ref MinHealth + DeploymentController: + Type: ECS + DesiredCount: !Ref TaskAmount + HealthCheckGracePeriodSeconds: 0 + LaunchType: EC2 + LoadBalancers: + - ContainerName: !Sub '${AWS::StackName}-container' + ContainerPort: 3000 + TargetGroupArn: !Ref ApplicationTargetGroup + Role: !Ref ServiceRoleArn + SchedulingStrategy: REPLICA + ServiceName: !Sub '${AWS::StackName}-service' + TaskDefinition: !Ref ApplicationTask + + # ECS Task Roles and Policies + TaskSsmPolicy: + Type: 'AWS::IAM::ManagedPolicy' + Properties: + Description: Allow services to get parameters from SSM + ManagedPolicyName: !Sub 'ECS-${AWS::StackName}-GetParametersPolicy' + PolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Action: + - 'ssm:GetParameters' + - 'ssm:GetParameter' + Resource: !Sub 'arn:aws:ssm:*:${AWS::AccountId}:parameter/*' + + TaskCloudWatchPolicy: + Type: 'AWS::IAM::ManagedPolicy' + Properties: + Description: Allow services to create and manage CloudWatch logs + ManagedPolicyName: !Sub 'ECS-${AWS::StackName}-CloudWatchPolicy' + PolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Action: 'logs:PutLogEvents' + Resource: 'arn:aws:logs:*:*:log-group:*:log-stream:*' + - Effect: Allow + Action: + - 'logs:CreateLogStream' + - 'logs:DescribeLogStreams' + Resource: 'arn:aws:logs:*:*:log-group:*' + - Effect: Allow + Action: 'logs:CreateLogGroup' + Resource: '*' + TaskRole: + Type: 'AWS::IAM::Role' + Properties: + AssumeRolePolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Principal: + Service: + - ecs-tasks.amazonaws.com + Action: + - 'sts:AssumeRole' + Description: Role for resources to manage ECS services + ManagedPolicyArns: + - 'arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryPowerUser' + - !Ref TaskCloudWatchPolicy + - !Ref TaskSsmPolicy + RoleName: !Sub '${AWS::StackName}-task-role' + + # CodeBuild role + CodeBuildDeployPolicy: + Type: 'AWS::IAM::ManagedPolicy' + Properties: + Description: Allow services to manage ECS and ECR deployment + ManagedPolicyName: !Sub 'ECS-${AWS::StackName}-DeployPolicy' + PolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Action: 'ecr:GetAuthorizationToken' + Resource: '*' + - Effect: Allow + Action: + - 'ecr:InitiateLayerUpload' + - 'ecr:UploadLayerPart' + - 'ecr:CompleteLayerUpload' + - 'ecr:BatchCheckLayerAvailability' + - 'ecr:PutImage' + Resource: !GetAtt ApplicationRepository.Arn + - Effect: Allow + Action: 'ecs:UpdateService' + Resource: !Sub 'arn:aws:ecs:${AWS::Region}:${AWS::AccountId}:service/${ClusterName}/${AWS::StackName}-service' + CodeBuildRole: + Type: 'AWS::IAM::Role' + Properties: + AssumeRolePolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Principal: + Service: + - codebuild.amazonaws.com + Action: + - 'sts:AssumeRole' + Description: Role for resources to manage ECS services + ManagedPolicyArns: !If + - HasCodeBuildPolicy + - - !Ref CodeBuildPolicy + - !Ref CodeBuildDeployPolicy + - - !Ref CodeBuildDeployPolicy + RoleName: !Sub '${AWS::StackName}-codebuild-role' + +Parameters: + VPC: + Type: AWS::EC2::VPC::Id + Description: ID of the VPC to be used by this stack + ServiceRoleArn: + Type: String + Description: ARN for ECS Service execution role + ClusterName: + Type: String + Description: Name of ECS cluster + LoadBalancer: + Type: String + Description: ARN for Application Load Balancer + ListenerArn: + Type: String + Description: ARN for Load Balancer Listener + RulePriority: + Type: Number + MaxValue: 5000 + MinValue: 1 + Default: 1 + Description: Load Balancer listener rule priority for this target group + HardMemory: + Type: Number + Default: 512 + Description: Hard memory cap for container, in MBs + SoftMemory: + Type: Number + Default: 256 + Description: Soft memory cap for container, in MBs + HealthCheckPath: + Type: String + Default: '/' + Description: 'Path for container health check (for example: /api/healthcheck)' + ShouldUseSSM: + Type: String + Default: false + AllowedValues: + - true + - false + Description: Checks if application should use database credentials from Parameter Store + DbName: + Type: String + Description: Name of the RDS database instance (optional, only required if using Parameter Store) + Environment: + Type: String + Default: production + AllowedValues: + - beta + - staging + - production + Description: Application environment type + TaskAmount: + Type: Number + Default: 0 + MinValue: 0 + MaxValue: 50 + Description: Amount of tasks to run for the application service + MaxHealth: + Type: Number + MinValue: 0 + Default: 200 + Description: 'Maximum amount of tasks to be running at the same time, in percentage (example: 4 task * 200% = 8 tasks max)' + MinHealth: + Type: Number + MinValue: 0 + Default: 100 + Description: 'Minimum amount of tasks to be running at the same time, in percentage (example: 4 task * 25% = 1 tasks min)' + CodeBuildPolicy: + Type: String + Default: '' + Description: 'ARN for policy that allows access to S3 CodeBuild scripts bucket' + +Metadata: + AWS::CloudFormation::Interface: + ParameterGroups: + - Label: + default: "Application Configuration" + Parameters: + - Environment + - HardMemory + - SoftMemory + - HealthCheckPath + - Label: + default: "Service Configuration" + Parameters: + - ClusterName + - TaskAmount + - MaxHealth + - MinHealth + - ServiceRoleArn + - Label: + default: "Database Configuration" + Parameters: + - ShouldUseSSM + - DbName + - Label: + default: "Load Balancer Configuration" + Parameters: + - LoadBalancer + - ListenerArn + - RulePriority + - Label: + default: "Network Configuration" + Parameters: + - VPC + - Label: + default: "CodeBuild Configuration" + Parameters: + - CodeBuildPolicy + + ParameterLabels: + ClusterName: + default: "Cluster Name" + HardMemory: + default: "Memory (Hard Cap)" + SoftMemory: + default: "Memory (Soft Cap)" + HealthCheckPath: + default: "Health Check Path" + ServiceRoleArn: + default: "Service Role ARN" + ShouldUseSSM: + default: "Use parameter store for database credentials?" + DbName: + default: "Database instance name (optional)" + LoadBalancer: + default: "Load Balancer ARN" + ListenerArn: + default: "Load Balancer Listener ARN" + RulePriority: + default: "Listener Rule Priority" + TaskAmount: + default: "Number of tasks" + MaxHealth: + default: "Maximum amount of tasks (%)" + MinHealth: + default: "Minimum amount of tasks (%)" + CodeBuildPolicy: + default: "S3 CodeBuild Scripts bucket policy ARN (optional)" + +Conditions: + UseSSM: !Equals [!Ref ShouldUseSSM, 'true'] + HasCodeBuildPolicy: !Not [!Equals [!Ref CodeBuildPolicy, '']] + +Mappings: + LogLevelMap: + beta: + level: 'debug' + awslevel: 'debug' + staging: + level: 'debug' + awslevel: 'info' + production: + level: 'info' + awslevel: 'info' \ No newline at end of file diff --git a/cloudformation/bucket.template b/cloudformation/bucket.template new file mode 100644 index 0000000..22b2e70 --- /dev/null +++ b/cloudformation/bucket.template @@ -0,0 +1,168 @@ +## +## CloudFormation template for S3 bucket for application +## Author: JoaoLippi +## Since: 2021-12-09 +## +## This templates creates an S3 bucket and a corresponding CloudFront distribution. +## Mainly used for static storage of files and images. +## +AWSTemplateFormatVersion: 2010-09-09 +Resources: + # Bucket + AppBucket: + Type: 'AWS::S3::Bucket' + Properties: + BucketName: !Ref Name + CorsConfiguration: + CorsRules: + - AllowedHeaders: + - '*' + AllowedMethods: + - 'PUT' + AllowedOrigins: + - '*' + AppBucketPolicy: + Type: 'AWS::S3::BucketPolicy' + Properties: + Bucket: !Ref AppBucket + PolicyDocument: + Version: '2012-10-17' + Statement: + - Sid: Stmt1 + Effect: 'Allow' + Principal: + AWS: !Sub 'arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity ${CloudFrontOriginAccessId}' + Action: 's3:GetObject' + Resource: !Sub 'arn:aws:s3:::${AppBucket}/*' + + # CloudFront Distribution + CloudFrontDistribution: + Type: AWS::CloudFront::Distribution + Properties: + DistributionConfig: + Comment: !Sub 'CloudFront distribution for ${AWS::StackName} application bucket' + DefaultCacheBehavior: + AllowedMethods: + - GET + - HEAD + CachedMethods: + - GET + - HEAD + MinTTL: !Ref MinTTL + MaxTTL: !Ref MaxTTL + DefaultTTL: !Ref DefaultTTL + ForwardedValues: + QueryString: false + TargetOriginId: !Sub '${AWS::StackName}-bucket-dist' + ViewerProtocolPolicy: redirect-to-https + Enabled: true + Origins: + - DomainName: !GetAtt AppBucket.RegionalDomainName + Id: !Sub '${AWS::StackName}-bucket-dist' + S3OriginConfig: + OriginAccessIdentity: !Sub 'origin-access-identity/cloudfront/${CloudFrontOriginAccessId}' + Tags: + - Key: Name + Value: !Sub 'App Bucket Distribution - ${AWS::StackName}' + CloudFrontOriginAccessId: + Type: AWS::CloudFront::CloudFrontOriginAccessIdentity + Properties: + CloudFrontOriginAccessIdentityConfig: + Comment: !Sub '${AWS::StackName}-oaid' + + # Policy for application access + ExternalAppBucketPolicy: + Type: 'AWS::IAM::ManagedPolicy' + Properties: + Description: Allow services to manage contents of S3 bucket + ManagedPolicyName: !Sub 'S3-${AWS::StackName}-AccessPolicy' + PolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Action: + - 's3:ReplicateObject' + - 's3:PutObject' + - 's3:GetObjectAcl' + - 's3:GetObject' + - 's3:DeleteObjectVersion' + - 's3:RestoreObject' + - 's3:GetObjectVersionAcl' + - 's3:ListBucket' + - 's3:DeleteObject' + - 's3:GetObjectVersion' + - 's3:ListMultipartUploadParts' + Resource: + - !Sub + - '${BucketArn}' + - BucketArn: !GetAtt AppBucket.Arn + - !Sub + - '${BucketArn}/*' + - BucketArn: !GetAtt AppBucket.Arn + - Effect: Allow + Action: + - 's3:ListAllMyBuckets' + - 's3:HeadBucket' + Resource: '*' + +Parameters: + Name: + Type: String + Description: 'Name of the bucket to be created' + + MinTTL: + Type: Number + MinValue: 0 + Default: 0 + Description: 'Minimum TTL (time-to-live) of objects in this distribution, in seconds' + + MaxTTL: + Type: Number + MinValue: 0 + Default: 15552000 + Description: 'Maximum TTL (time-to-live) of objects in this distribution, in seconds' + + DefaultTTL: + Type: Number + MinValue: 0 + Default: 7776000 + Description: 'Default TTL (time-to-live) of objects in this distribution, in seconds' + +Metadata: + AWS::CloudFormation::Interface: + ParameterGroups: + - Label: + default: "Bucket Configuration" + Parameters: + - Name + + - Label: + default: "Distribution Configuration" + Parameters: + - MinTTL + - MaxTTL + - DefaultTTL + + ParameterLabels: + MinTTL: + default: "Minimum TTL (seconds)" + MaxTTL: + default: "Maximum TTL (seconds)" + DefaultTTL: + default: "Default TTL (seconds)" + +Outputs: + CloudFrontDistUrl: + Description: URL of the CloudFront Distribution + Value: !GetAtt CloudFrontDistribution.DomainName + + BucketName: + Description: Name of the S3 bucket + Value: !Ref AppBucket + + BucketRegion: + Description: Region of the S3 bucket + Value: !Sub ${AWS::Region} + BucketAccessPolicyArn: + Description: ARN for Bucket Access Policy + Value: !Ref ExternalAppBucketPolicy diff --git a/cloudformation/cluster.template b/cloudformation/cluster.template new file mode 100644 index 0000000..9f40353 --- /dev/null +++ b/cloudformation/cluster.template @@ -0,0 +1,285 @@ +## +## CloudFormation template for ECS cluster, Load Balancer and Instance roles +## Author: JoaoLippi +## Since: 2021-12-09 +## +## This template creates an ECS cluster and a Load Balancer. +## The cluster is empty (no instances), but all the required roles for EC2 instances to be added to the cluster +## are created +## +AWSTemplateFormatVersion: 2010-09-09 +Resources: + # Cluster + Cluster: + Type: 'AWS::ECS::Cluster' + Properties: + ClusterName: !Sub '${AWS::StackName}' + ClusterSettings: + - Name: containerInsights + Value: enabled + + # Load Balancer + LoadBalancerSecurityGroup: + Type: 'AWS::EC2::SecurityGroup' + Properties: + GroupDescription: Security Group for Load Balancer + GroupName: !Sub '${AWS::StackName}-lb-sg' + SecurityGroupIngress: + - IpProtocol: tcp + FromPort: 80 + ToPort: 80 + CidrIp: 0.0.0.0/0 + - IpProtocol: tcp + FromPort: 443 + ToPort: 443 + CidrIp: 0.0.0.0/0 + VpcId: !Ref VPC + LoadBalancer: + Type: 'AWS::ElasticLoadBalancingV2::LoadBalancer' + Properties: + Name: !Sub '${AWS::StackName}-lb' + Scheme: internet-facing + SecurityGroups: + - !Ref LoadBalancerSecurityGroup + Subnets: + - !Ref PublicSubnet1Id + - !Ref PublicSubnet2Id + - !Ref PublicSubnet3Id + Type: application + Listener: + Type: 'AWS::ElasticLoadBalancingV2::Listener' + Properties: + DefaultActions: + - Type: fixed-response + FixedResponseConfig: + StatusCode: 503 + LoadBalancerArn: !Ref LoadBalancer + Port: 80 + Protocol: HTTP + + # For instances in cluster + ECSSecurityGroup: + Type: 'AWS::EC2::SecurityGroup' + Properties: + GroupDescription: Security Group for ECS instances + GroupName: !Sub '${AWS::StackName}-sg' + SecurityGroupIngress: !If + - HasAccessIp + - !If + - HasVpnSg + - - IpProtocol: -1 + SourceSecurityGroupId: !Ref LoadBalancerSecurityGroup + FromPort: 0 + ToPort: 65535 + - IpProtocol: tcp + FromPort: 22 + ToPort: 22 + CidrIp: !Sub '${IPForAccess}/32' + - IpProtocol: tcp + SourceSecurityGroupId: !Ref VpnSecurityGroup + FromPort: 22 + ToPort: 22 + - - IpProtocol: -1 + SourceSecurityGroupId: !Ref LoadBalancerSecurityGroup + FromPort: 0 + ToPort: 65535 + - IpProtocol: tcp + FromPort: 22 + ToPort: 22 + CidrIp: !Sub '${IPForAccess}/32' + - !If + - HasVpnSg + - - IpProtocol: -1 + SourceSecurityGroupId: !Ref LoadBalancerSecurityGroup + FromPort: 0 + ToPort: 65535 + - IpProtocol: tcp + SourceSecurityGroupId: !Ref VpnSecurityGroup + FromPort: 22 + ToPort: 22 + - - IpProtocol: -1 + SourceSecurityGroupId: !Ref LoadBalancerSecurityGroup + FromPort: 0 + ToPort: 65535 + VpcId: !Ref VPC + SpotFleetRole: + Type: 'AWS::IAM::Role' + Properties: + AssumeRolePolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Principal: + Service: + - spotfleet.amazonaws.com + Action: + - 'sts:AssumeRole' + Description: Role for resources to manage EC2 Spot Fleet requests + ManagedPolicyArns: + - 'arn:aws:iam::aws:policy/service-role/AmazonEC2SpotFleetTaggingRole' + RoleName: !Sub '${AWS::StackName}-spot-fleet-role' + ServiceRole: + Type: 'AWS::IAM::Role' + Properties: + AssumeRolePolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Principal: + Service: + - ecs.amazonaws.com + Action: + - 'sts:AssumeRole' + Description: Role for resources to manage ECS services + ManagedPolicyArns: + - 'arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceRole' + RoleName: !Sub '${AWS::StackName}-service-role' + InstanceRole: + Type: 'AWS::IAM::Role' + Properties: + AssumeRolePolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Principal: + Service: + - ec2.amazonaws.com + Action: + - 'sts:AssumeRole' + Description: Role for EC2 instances in an ECS cluster to access ECS + ManagedPolicyArns: !If + - HasBucketPolicy + - - arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Role + - arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess + - !Ref InstanceSesPolicy + - !Ref BucketPolicyArn + - - arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Role + - arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess + - !Ref InstanceSesPolicy + RoleName: !Sub '${AWS::StackName}-instance-role' + InstanceProfile: + Type: 'AWS::IAM::InstanceProfile' + Properties: + InstanceProfileName: !Sub '${AWS::StackName}-instance-profile' + Roles: + - !Ref InstanceRole + + # Instance policy for SES + InstanceSesPolicy: + Type: 'AWS::IAM::ManagedPolicy' + Properties: + Description: Allow services to send emails through SES + ManagedPolicyName: !Sub 'ECS-${AWS::StackName}-SendEmailPolicy' + PolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Action: + - 'ses:SendEmail' + - 'ses:SendTemplatedEmail' + - 'ses:SendRawEmail' + - 'ses:SendBulkTemplatedEmail' + Resource: 'arn:aws:ses:*:*:identity/*' + +Parameters: + VPC: + Type: AWS::EC2::VPC::Id + Description: ID of the VPC to be used by this stack + PublicSubnet1Id: + Type: AWS::EC2::Subnet::Id + Description: ID of the public subnet 1 + PublicSubnet2Id: + Type: AWS::EC2::Subnet::Id + Description: ID of the public subnet 2 + PublicSubnet3Id: + Type: AWS::EC2::Subnet::Id + Description: ID of the public subnet 3 + IPForAccess: + Type: String + Description: IP for accessing cluster EC2 instances via SSH directly + VpnSecurityGroup: + Type: String + Default: '' + Description: ID (sg-xxxxxxxx) of the security group to allow EC2 instances access through VPN + BucketPolicyArn: + Type: String + Description: 'ARN for policy that allows application to interact with S3 bucket' + +Metadata: + AWS::CloudFormation::Interface: + ParameterGroups: + - Label: + default: "Cluster Configuration" + Parameters: + - VPC + - PublicSubnet1Id + - PublicSubnet2Id + - PublicSubnet3Id + - IPForAccess + - VpnSecurityGroup + - Label: + default: 'Instance configuration' + Parameters: + - BucketPolicyArn + + ParameterLabels: + PublicSubnet1Id: + default: "Public Subnet 1" + PublicSubnet2Id: + default: "Public Subnet 2" + PublicSubnet3Id: + default: "Public Subnet 3" + IPForAccess: + default: "IP for SSH (optional)" + VpnSecurityGroup: + default: "VPN Security Group (optional)" + BucketPolicyArn: + default: "Application Bucket Policy ARN (optional)" + +Conditions: + HasAccessIp: !Not [!Equals [!Ref IPForAccess, '']] + HasVpnSg: !Not [!Equals [!Ref VpnSecurityGroup, '']] + HasBucketPolicy: !Not [!Equals [!Ref BucketPolicyArn, '']] + +Outputs: + ServiceRole: + Description: ARN of the IAM cluster service role + Value: !GetAtt + - ServiceRole + - Arn + Export: + Name: !Sub '${AWS::StackName}-service-role-arn' + SpotFleetRole: + Description: ARN of the IAM spot fleet request role + Value: !GetAtt + - SpotFleetRole + - Arn + Export: + Name: !Sub '${AWS::StackName}-spot-fleet-role-arn' + InstanceProfile: + Description: ID of the IAM instance profile for EC2 instances + Value: !GetAtt + - InstanceProfile + - Arn + Export: + Name: !Sub '${AWS::StackName}-instance-profile-arn' + Cluster: + Description: Name of the ECS cluster + Value: !Ref Cluster + Export: + Name: !Sub '${AWS::StackName}' + LoadBalancer: + Description: ID of the Application Load Balancer + Value: !Ref LoadBalancer + Export: + Name: !Sub '${AWS::StackName}-lb' + ECSSecurityGroup: + Description: ID of the Application Load Balancer + Value: !Ref ECSSecurityGroup + Export: + Name: !Sub '${AWS::StackName}-sg' + ListenerArn: + Description: ARN for Load Balancer Listener + Value: !Ref Listener + Export: + Name: !Sub '${AWS::StackName}-listener-arn' \ No newline at end of file diff --git a/cloudformation/codebuild.bucket.template b/cloudformation/codebuild.bucket.template new file mode 100644 index 0000000..e1ae316 --- /dev/null +++ b/cloudformation/codebuild.bucket.template @@ -0,0 +1,62 @@ +## +## CloudFormation template for S3 bucket for CodeBuild scripts +## Author: JoaoLippi +## Since: 2021-12-09 +## +## Creates a bucket to host CodeBuild scripts (for discord hook and git logs), and the corresponding permissions. +## The resulting permission policy should be added to the CodeBuild project +## +AWSTemplateFormatVersion: 2010-09-09 +Resources: + # Bucket + ScriptsBucket: + Type: 'AWS::S3::Bucket' + Properties: + BucketName: !Ref Name + + # Policy for services access + ExternalAppBucketPolicy: + Type: 'AWS::IAM::ManagedPolicy' + Properties: + Description: Allow services to manage contents of S3 bucket + ManagedPolicyName: !Sub 'S3-${AWS::StackName}-AccessPolicy' + PolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Action: + - 's3:GetObject' + - 's3:ListBucket' + Resource: + - !Sub + - '${BucketArn}' + - BucketArn: !GetAtt ScriptsBucket.Arn + - !Sub + - '${BucketArn}/*' + - BucketArn: !GetAtt ScriptsBucket.Arn + +Parameters: + Name: + Type: String + Description: 'Name of the bucket to be created' + +Metadata: + AWS::CloudFormation::Interface: + ParameterGroups: + - Label: + default: "Bucket Configuration" + Parameters: + - Name + +Outputs: + BucketName: + Description: Name of the S3 bucket + Value: !Ref ScriptsBucket + + BucketRegion: + Description: Region of the S3 bucket + Value: !Sub ${AWS::Region} + + BucketAccessPolicyArn: + Description: ARN for Bucket Access Policy + Value: !Ref ExternalAppBucketPolicy diff --git a/cloudformation/instance.template b/cloudformation/instance.template new file mode 100644 index 0000000..77c492e --- /dev/null +++ b/cloudformation/instance.template @@ -0,0 +1,401 @@ +## +## CloudFormation template for EC2 instances +## Author: JoaoLippi +## Since: 2021-12-09 +## +## This template creates an EC2 instance and associates with the given ECS cluster. +## The instance can either be On Demand or Spot Request +## +AWSTemplateFormatVersion: 2010-09-09 +Resources: + # If not spot fleet request, will create an auto scaling group for EC2 instances + AutoScalingLaunchConfig: + Type: AWS::AutoScaling::LaunchConfiguration + Condition: IsNotSpotRequest + Properties: + ImageId: !FindInMap + - RegionMap + - Ref: 'AWS::Region' + - AMI + InstanceType: !Ref InstanceSize + AssociatePublicIpAddress: true + IamInstanceProfile: !Ref InstanceProfileArn + KeyName: !Ref KeyPair + SecurityGroups: + - !Ref SecurityGroup + BlockDeviceMappings: + - DeviceName: /dev/xvdcz + Ebs: + VolumeSize: 22 + VolumeType: gp2 + UserData: !Base64 + 'Fn::Join': + - '' + - - | + #!/bin/bash + - !Sub > + echo ECS_CLUSTER=${ClusterName} >> + /etc/ecs/ecs.config; + - | + echo ECS_BACKEND_HOST= >> /etc/ecs/ecs.config; + - | + export PATH=/usr/local/bin:$PATH + - | + yum -y install jq + - | + easy_install pip + - | + pip install awscli + - !Sub | + aws configure set default.region ${AWS::Region} + - > + cat </etc/init/spot-instance-termination-notice-handler.conf + - > + description "Start spot instance termination + handlermonitoring script" + - | + author "Amazon Web Services" + - | + start on started ecs + - | + script + - > + echo \$\$ + >/var/run/spot-instance-termination-notice-handler.pid + - > + exec/usr/local/bin/spot-instance-termination-notice-handler.sh + - | + end script + - | + pre-start script + - > + logger "[spot-instance-termination-notice-handler.sh]: + spotinstance termination notice handler started" + - | + end script + - | + EOF + - > + cat </usr/local/bin/spot-instance-termination-notice-handler.sh + - | + #!/bin/bash + - | + while sleep 5; do + - > + if [ -z \$(curl + -Isfhttp://169.254.169.254/latest/meta-data/spot/termination-time)];then + - | + /bin/false + - | + else + - > + logger "[spot-instance-termination-notice-handler.sh]: + spotinstance termination notice detected" + - | + STATUS=DRAINING + - > + ECS_CLUSTER=\$(curl -s http://localhost:51678/v1/metadata | + jq .Cluster | tr -d \") + - > + CONTAINER_INSTANCE=\$(curl + -shttp://localhost:51678/v1/metadata | + jq.ContainerInstanceArn | tr -d \") + - > + logger + "[spot-instance-termination-notice-handler.sh]:putting + instance in state \$STATUS" + - | + /usr/local/bin/aws + - ecs update-container-instances-state --cluster \$ECS_CLUSTER + - | + -container-instances \$CONTAINER_INSTANCE --status\$STATUS + - > + logger + "[spot-instance-termination-notice-handler.sh]:putting + myself to sleep..." + - > + sleep 120 # exit loop as instance expires in 120 secs + afterterminating notification + - | + fi + - | + done + - | + EOF + - | + chmod +x + - /usr/local/bin/spot-instance-termination-notice-handler.sh + AutoScalingGroup: + Type: AWS::AutoScaling::AutoScalingGroup + Condition: IsNotSpotRequest + Properties: + VPCZoneIdentifier: + - !Ref ApplicationSubnet1 + - !Ref ApplicationSubnet2 + - !Ref ApplicationSubnet3 + LaunchConfigurationName: !Ref AutoScalingLaunchConfig + MinSize: '0' + MaxSize: !Ref Instances + DesiredCapacity: !Ref Instances + Tags: + - Key: Name + Value: !Sub "ECS Instance - ${AWS::StackName}" + PropagateAtLaunch: true + + # Otherwise, will use Spot Fleet Request for EC2 instances + SpotFleetRequest: + Type: 'AWS::EC2::SpotFleet' + Condition: IsSpotRequest + Properties: + SpotFleetRequestConfigData: + AllocationStrategy: lowestPrice + IamFleetRole: !Ref SpotFleetRequestRoleArn + TargetCapacity: !Ref Instances + TerminateInstancesWithExpiration: true + LaunchSpecifications: + - IamInstanceProfile: + Arn: !Ref InstanceProfileArn + ImageId: !FindInMap + - RegionMap + - Ref: 'AWS::Region' + - AMI + InstanceType: !Ref InstanceSize + KeyName: !Ref KeyPair + Monitoring: + Enabled: true + SecurityGroups: + - GroupId: !Ref SecurityGroup + SubnetId: !Join + - ',' + - - !Ref ApplicationSubnet1 + - !Ref ApplicationSubnet2 + - !Ref ApplicationSubnet3 + BlockDeviceMappings: + - DeviceName: /dev/xvdcz + Ebs: + VolumeSize: 22 + VolumeType: gp2 + TagSpecifications: + - ResourceType: instance + Tags: + - Key: Name + Value: !Sub "ECS Instance - ${AWS::StackName}" + UserData: !Base64 + 'Fn::Join': + - '' + - - | + #!/bin/bash + - !Sub > + echo ECS_CLUSTER=${ClusterName} >> + /etc/ecs/ecs.config; + - | + echo ECS_BACKEND_HOST= >> /etc/ecs/ecs.config; + - | + export PATH=/usr/local/bin:$PATH + - | + yum -y install jq + - | + easy_install pip + - | + pip install awscli + - !Sub | + aws configure set default.region ${AWS::Region} + - > + cat </etc/init/spot-instance-termination-notice-handler.conf + - > + description "Start spot instance termination + handlermonitoring script" + - | + author "Amazon Web Services" + - | + start on started ecs + - | + script + - > + echo \$\$ + >/var/run/spot-instance-termination-notice-handler.pid + - > + exec/usr/local/bin/spot-instance-termination-notice-handler.sh + - | + end script + - | + pre-start script + - > + logger "[spot-instance-termination-notice-handler.sh]: + spotinstance termination notice handler started" + - | + end script + - | + EOF + - > + cat </usr/local/bin/spot-instance-termination-notice-handler.sh + - | + #!/bin/bash + - | + while sleep 5; do + - > + if [ -z \$(curl + -Isfhttp://169.254.169.254/latest/meta-data/spot/termination-time)];then + - | + /bin/false + - | + else + - > + logger "[spot-instance-termination-notice-handler.sh]: + spotinstance termination notice detected" + - | + STATUS=DRAINING + - > + ECS_CLUSTER=\$(curl -s http://localhost:51678/v1/metadata | + jq .Cluster | tr -d \") + - > + CONTAINER_INSTANCE=\$(curl + -shttp://localhost:51678/v1/metadata | + jq.ContainerInstanceArn | tr -d \") + - > + logger + "[spot-instance-termination-notice-handler.sh]:putting + instance in state \$STATUS" + - | + /usr/local/bin/aws + - ecs update-container-instances-state --cluster \$ECS_CLUSTER + - | + -container-instances \$CONTAINER_INSTANCE --status\$STATUS + - > + logger + "[spot-instance-termination-notice-handler.sh]:putting + myself to sleep..." + - > + sleep 120 # exit loop as instance expires in 120 secs + afterterminating notification + - | + fi + - | + done + - | + EOF + - | + chmod +x + - /usr/local/bin/spot-instance-termination-notice-handler.sh +Parameters: + SpotRequest: + Type: String + Default: true + AllowedValues: + - true + - false + Description: Decides if instances should spot fleet request or on-demand + ClusterName: + Type: String + Description: Name of the ECS cluster to attach the instances to + Instances: + Type: Number + Description: Amount of instances to be created + SecurityGroup: + Type: AWS::EC2::SecurityGroup::Id + Description: ID of the security group to be attached to the instances + VPC: + Type: AWS::EC2::VPC::Id + Description: ID of the VPC to be used by this stack + ApplicationSubnet1: + Type: AWS::EC2::Subnet::Id + Description: ID of the first subnet to be used by this stack + ApplicationSubnet2: + Type: AWS::EC2::Subnet::Id + Description: ID of the second subnet to be used by this stack + ApplicationSubnet3: + Type: AWS::EC2::Subnet::Id + Description: ID of the third subnet to be used by this stack + KeyPair: + Type: AWS::EC2::KeyPair::KeyName + Description: Name of the key pair used to access the EC2 instance + InstanceSize: + Type: String + Description: Instance type (size) for the EC2 instance + Default: t3.medium + InstanceProfileArn: + Type: String + Description: ARN for EC2 instance profile + SpotFleetRequestRoleArn: + Type: String + Description: ARN for Spot Fleet Request Role (only required if using Spot Request) + +Metadata: + AWS::CloudFormation::Interface: + ParameterGroups: + - Label: + default: "General Configuration" + Parameters: + - SpotRequest + - ClusterName + - Instances + - Label: + default: "Instance Configuration" + Parameters: + - InstanceSize + - KeyPair + - SecurityGroup + - Label: + default: "Network Configuration" + Parameters: + - VPC + - ApplicationSubnet1 + - ApplicationSubnet2 + - ApplicationSubnet3 + - Label: + default: "Roles Configuration" + Parameters: + - InstanceProfileArn + - SpotFleetRequestRoleArn + + ParameterLabels: + SpotRequest: + default: "Is instance a Spot Request?" + ClusterName: + default: "Cluster" + Instances: + default: "Amount of Instances" + InstanceSize: + default: "Instance size (type)" + KeyPair: + default: "Key Pair" + SecurityGroup: + default: "Security Group" + ApplicationSubnet1: + default: "Application Subnet 1" + ApplicationSubnet2: + default: "Application Subnet 2" + ApplicationSubnet3: + default: "Application Subnet 3" + InstanceProfileArn: + default: "Instance Profile ARN" + SpotFleetRequestRoleArn: + default: "Spot Fleet Request Role ARN (optional)" + +Conditions: + IsSpotRequest: !Equals [!Ref SpotRequest, 'true'] + IsNotSpotRequest: !Equals [!Ref SpotRequest, 'false'] + +Mappings: + RegionMap: + us-east-1: + AMI: 'ami-0fe19057e9cb4efd8' + us-west-2: + AMI: 'ami-04e6179c63d17513d' + sa-east-1: + AMI: 'ami-035b4cb75ab88f259' + +Outputs: + SpotFleetRequestId: + Condition: IsSpotRequest + Description: Spot fleet request ID for instances + Value: !Ref SpotFleetRequest + AutoScalingGroup: + Condition: IsNotSpotRequest + Description: Auto-scaling group name for instances + Value: !Ref AutoScalingGroup + diff --git a/cloudformation/mysql.rds.template b/cloudformation/mysql.rds.template new file mode 100644 index 0000000..758a4f3 --- /dev/null +++ b/cloudformation/mysql.rds.template @@ -0,0 +1,319 @@ +## +## CloudFormation template for RDS MySQL 8.0 database +## Author: JoaoLippi +## Since: 2021-12-09 +## +## This template provides resources for an RDS database, running MySQL 8.0.26, +## and also creates parameters in the Parameter Store to be used by ECS Tasks +## +AWSTemplateFormatVersion: 2010-09-09 +Resources: + # Accessibility + DBSecurityGroup: + Type: 'AWS::EC2::SecurityGroup' + Properties: + GroupDescription: Security Group for RDS instance + GroupName: !Sub '${AWS::StackName}-sg' + SecurityGroupIngress: !If + - HasAccessIp + - !If + - HasVpnSg + - - IpProtocol: tcp + SourceSecurityGroupId: !Ref ECSSecurityGroup + FromPort: 3306 + ToPort: 3306 + - IpProtocol: tcp + FromPort: 3306 + ToPort: 3306 + CidrIp: !Sub '${IPForAccess}/32' + - IpProtocol: tcp + SourceSecurityGroupId: !Ref VpnSecurityGroup + FromPort: 3306 + ToPort: 3306 + - - IpProtocol: tcp + SourceSecurityGroupId: !Ref ECSSecurityGroup + FromPort: 3306 + ToPort: 3306 + - IpProtocol: tcp + FromPort: 3306 + ToPort: 3306 + CidrIp: !Sub '${IPForAccess}/32' + - !If + - HasVpnSg + - - IpProtocol: tcp + SourceSecurityGroupId: !Ref ECSSecurityGroup + FromPort: 3306 + ToPort: 3306 + - IpProtocol: tcp + SourceSecurityGroupId: !Ref VpnSecurityGroup + FromPort: 3306 + ToPort: 3306 + - - IpProtocol: tcp + SourceSecurityGroupId: !Ref ECSSecurityGroup + FromPort: 3306 + ToPort: 3306 + VpcId: !Ref VPC + DBSubnetGroup: + Type: 'AWS::RDS::DBSubnetGroup' + Properties: + DBSubnetGroupDescription: Subnet for RDS instance + DBSubnetGroupName: !Sub '${AWS::StackName}-subnet' + SubnetIds: + - !Ref DatabaseSubnet1 + - !Ref DatabaseSubnet2 + - !Ref DatabaseSubnet3 + + # Instance + DBInstance: + Type: 'AWS::RDS::DBInstance' + Properties: + AllocatedStorage: !Ref DBStorageSize + BackupRetentionPeriod: 7 + CopyTagsToSnapshot: true + DBInstanceClass: !Ref DBSize + DBInstanceIdentifier: !Sub '${AWS::StackName}' + DBName: !Ref DBName + DBParameterGroupName: !Ref DBParameterGroup + DBSubnetGroupName: !Ref DBSubnetGroup + EnableCloudwatchLogsExports: + - error + - general + - slowquery + Engine: mysql + EngineVersion: 8.0.26 + MasterUsername: !Ref DBMasterUser + MasterUserPassword: !Ref DBMasterPass + MonitoringInterval: !If + - MonitordingEnabled + - 10 + - 0 + MonitoringRoleArn: !If + - MonitoringEnabled + - !GetAtt + - DBMonitoringRole + - Arn + - !Ref 'AWS::NoValue' + MultiAZ: !Ref IsMultiAZ + Port: 3306 + PreferredMaintenanceWindow: 'mon:07:00-mon:08:00' + PubliclyAccessible: !Ref IsPublic + StorageEncrypted: !Ref IsEncrypted + StorageType: gp2 + VPCSecurityGroups: + - !Ref DBSecurityGroup + DBParameterGroup: + Type: 'AWS::RDS::DBParameterGroup' + Properties: + Description: Parameter Group for RDS MySQL 8.0 instance + Family: mysql8.0 + Parameters: + log_bin_trust_function_creators: 1 + time_zone: !Ref Timezone + general_log: 1 + slow_query_log: 1 + log_output: FILE + Tags: + - Key: Name + Value: !Sub '${AWS::StackName}-pg' + DBMonitoringRole: + Type: 'AWS::IAM::Role' + Condition: MonitoringEnabled + Properties: + AssumeRolePolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Principal: + Service: + - monitoring.rds.amazonaws.com + Action: + - 'sts:AssumeRole' + Description: Role for resources to manage RDS monitoring + ManagedPolicyArns: + - 'arn:aws:iam::aws:policy/service-role/AmazonRDSEnhancedMonitoringRole' + RoleName: !Sub '${AWS::StackName}-monitoring-role' + + # Parameter Store + DBUrlParameter: + Type: AWS::SSM::Parameter + DependsOn: DBInstance + Properties: + Description: !Sub 'URL for database from stack ${AWS::StackName}' + Name: !Sub '${AWS::StackName}-url' + Tags: + Database: !Sub '${AWS::StackName}' + Type: String + Value: !GetAtt DBInstance.Endpoint.Address + DBNameParameter: + Type: AWS::SSM::Parameter + Properties: + Description: !Sub 'Name for database from stack ${AWS::StackName}' + Name: !Sub '${AWS::StackName}-name' + Tags: + Database: !Sub '${AWS::StackName}' + Type: String + Value: !Ref DBName + DBUserParameter: + Type: AWS::SSM::Parameter + Properties: + Description: !Sub 'User for database from stack ${AWS::StackName}' + Name: !Sub '${AWS::StackName}-user' + Tags: + Database: !Sub '${AWS::StackName}' + Type: String + Value: !Ref DBMasterUser + DBPassParameter: + Type: AWS::SSM::Parameter + Properties: + Description: !Sub 'Password for database from stack ${AWS::StackName}' + Name: !Sub '${AWS::StackName}-pass' + Tags: + Database: !Sub '${AWS::StackName}' + Type: String + Value: !Ref DBMasterPass +Parameters: + DBName: + Type: String + Description: Name of the database to be created + DBMasterUser: + Type: String + Default: admin + Description: The master user name for the DB instance + DBMasterPass: + Type: String + Description: The password for the master user. The password can include any printable ASCII character except "/", """, or "@". Must contain from 8 to 41 characters + NoEcho: true + DBSize: + Type: String + Description: Instance type image (size) for this RDS instance + Default: db.t3.medium + DBStorageSize: + Type: Number + MinValue: 20 + MaxValue: 16384 + Description: Amount of storage for this database + Default: 20 + MonitoringEnabled: + Type: String + Default: true + AllowedValues: + - true + - false + Description: Enables monitoring of this RDS instance + IsPublic: + Type: String + Default: false + AllowedValues: + - true + - false + Description: Specifies if this RDS instance can be accessed through a public IP or if it's only accessible from resources within the same VPC + VPC: + Type: AWS::EC2::VPC::Id + Description: ID of the VPC to be used by this stack + DatabaseSubnet1: + Type: AWS::EC2::Subnet::Id + Description: ID for Database Subnet ID 1 + DatabaseSubnet2: + Type: AWS::EC2::Subnet::Id + Description: ID for Database Subnet ID 2 + DatabaseSubnet3: + Type: AWS::EC2::Subnet::Id + Description: ID for Database Subnet ID 3 + IsMultiAZ: + Type: String + AllowedValues: + - true + - false + Default: false + Description: Allows for Multi-Availability Zone support + IsEncrypted: + Type: String + AllowedValues: + - true + - false + Default: false + Description: 'https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Overview.Encryption.html#Overview.Encryption.Availability' + IPForAccess: + Type: String + Description: IP for accessing this RDS database + ECSSecurityGroup: + Type: AWS::EC2::SecurityGroup::Id + Description: Application Security Group ID to allow access + Timezone: + Type: String + Description: Database timezone (in compliance with IANA time zone database) + Default: America/Fortaleza + VpnSecurityGroup: + Type: String + Default: '' + Description: ID (sg-xxxxxxxx) of the security group to allow EC2 instances access through VPN + +Metadata: + AWS::CloudFormation::Interface: + ParameterGroups: + - Label: + default: "Database Configuration" + Parameters: + - DBName + - DBMasterUser + - DBMasterPass + - DBSize + - DBStorageSize + - MonitoringEnabled + - IsMultiAZ + - IsEncrypted + - Timezone + - Label: + default: "Access Configuration" + Parameters: + - VPC + - DatabaseSubnet1 + - DatabaseSubnet2 + - DatabaseSubnet3 + - ECSSecurityGroup + - VpnSecurityGroup + - IsPublic + - IPForAccess + ParameterLabels: + DBName: + default: "Database Name" + DBMasterUser: + default: "Database Master User" + DBMasterPass: + default: "Database Master Password" + DBSize: + default: "Database Instance Size (type)" + DBStorageSize: + default: "Database Storage Size (in GB)" + MonitoringEnabled: + default: "Enable monitoring?" + DatabaseSubnet1: + default: "Database Subnet 1" + DatabaseSubnet2: + default: "Database Subnet 2" + DatabaseSubnet3: + default: "Database Subnet 3" + IsMultiAZ: + default: "Is Multi-Availability Zone?" + IsEncrypted: + default: "Is data encrypted at rest? (does not support db.t2.micro, check link for availability)" + ECSSecurityGroup: + default: "ECS Instances Security Group" + IPForAccess: + default: "IP for Access (optional)" + VpnSecurityGroup: + default: "VPN Security Group (optional)" + +Conditions: + MonitoringEnabled: !Equals [!Ref MonitoringEnabled, 'true'] + HasAccessIp: !Not [!Equals [!Ref IPForAccess, '']] + HasVpnSg: !Not [!Equals [!Ref VpnSecurityGroup, '']] + +Mappings: + RegionMap: + us-east-1: + AvailabilityZones: 'us-east-1a,us-east-1b,us-east-1c' + us-west-2: + AvailabilityZones: 'us-west-2a,us-west-2b,us-west-2c' + sa-east-1: + AvailabilityZones: 'sa-east-1a,sa-east-1b,sa-east-1c' \ No newline at end of file diff --git a/cloudformation/network.template b/cloudformation/network.template new file mode 100644 index 0000000..9cd1d41 --- /dev/null +++ b/cloudformation/network.template @@ -0,0 +1,286 @@ +## +## CloudFormation template for network and VPC specification +## Author: JoaoLippi +## Since: 2021-12-09 +## +## This template creates a Virtual Private Cloud (VPC) and all the required resources, like Internet Gateway and +## Routing Table. +## Provides with 6 different subnets, with 3 of those being public. +## + +AWSTemplateFormatVersion: 2010-09-09 +Resources: + # VPC and public internet gateway and routing table + Vpc: + Type: 'AWS::EC2::VPC' + Properties: + CidrBlock: !Ref VpcCidrBlock + EnableDnsHostnames: true + EnableDnsSupport: true + Tags: + - Key: Name + Value: !Sub "VPC - ${AWS::StackName}" + InternetGateway: + Type: 'AWS::EC2::InternetGateway' + Properties: + Tags: + - Key: Name + Value: !Sub "Internet Gateway - ${AWS::StackName}" + VpcRouteTable: + Type: 'AWS::EC2::RouteTable' + Properties: + VpcId: !Ref Vpc + Tags: + - Key: Name + Value: !Sub "VPC Route Table - ${AWS::StackName}" + InternetRoute: + Type: 'AWS::EC2::Route' + Properties: + DestinationCidrBlock: 0.0.0.0/0 + RouteTableId: !Ref VpcRouteTable + GatewayId: !Ref InternetGateway + DependsOn: + - IgAttachment + IgAttachment: + Type: 'AWS::EC2::VPCGatewayAttachment' + Properties: + InternetGatewayId: !Ref InternetGateway + VpcId: !Ref Vpc + + # Public subnets to be used by load balancer and application (ECS) + # Each subnet is in a different availability zone + PublicSubnet1: + Type: 'AWS::EC2::Subnet' + Properties: + VpcId: !Ref Vpc + AvailabilityZone: !Select + - 0 + - !Split + - "," + - !FindInMap + - RegionMap + - Ref: AWS::Region + - AvailabilityZones + CidrBlock: !Ref PublicSubnet1IpBlock + MapPublicIpOnLaunch: true + Tags: + - Key: Name + Value: !Sub "Public Subnet 1 - ${AWS::StackName}" + PublicSubnetRTA1: + Type: 'AWS::EC2::SubnetRouteTableAssociation' + Properties: + RouteTableId: !Ref VpcRouteTable + SubnetId: !Ref PublicSubnet1 + + PublicSubnet2: + Type: 'AWS::EC2::Subnet' + Properties: + VpcId: !Ref Vpc + AvailabilityZone: !Select + - 1 + - !Split + - "," + - !FindInMap + - RegionMap + - Ref: AWS::Region + - AvailabilityZones + CidrBlock: !Ref PublicSubnet2IpBlock + MapPublicIpOnLaunch: true + Tags: + - Key: Name + Value: !Sub "Public Subnet 2 - ${AWS::StackName}" + PublicSubnetRTA2: + Type: 'AWS::EC2::SubnetRouteTableAssociation' + Properties: + RouteTableId: !Ref VpcRouteTable + SubnetId: !Ref PublicSubnet2 + + PublicSubnet3: + Type: 'AWS::EC2::Subnet' + Properties: + VpcId: !Ref Vpc + AvailabilityZone: !Select + - 2 + - !Split + - "," + - !FindInMap + - RegionMap + - Ref: AWS::Region + - AvailabilityZones + CidrBlock: !Ref PublicSubnet3IpBlock + MapPublicIpOnLaunch: true + Tags: + - Key: Name + Value: !Sub "Public Subnet 3 - ${AWS::StackName}" + PublicSubnetRTA3: + Type: 'AWS::EC2::SubnetRouteTableAssociation' + Properties: + RouteTableId: !Ref VpcRouteTable + SubnetId: !Ref PublicSubnet3 + + # Private subnets to be used by the database (RDS) + # Each subnet is in a different availability zone + DatabaseSubnet1: + Type: 'AWS::EC2::Subnet' + Properties: + VpcId: !Ref Vpc + AvailabilityZone: !Select + - 0 + - !Split + - "," + - !FindInMap + - RegionMap + - Ref: AWS::Region + - AvailabilityZones + CidrBlock: !Ref DatabaseSubnet1IpBlock + MapPublicIpOnLaunch: false + Tags: + - Key: Name + Value: !Sub "Database Subnet 1 - ${AWS::StackName}" + DatabaseSubnet2: + Type: 'AWS::EC2::Subnet' + Properties: + VpcId: !Ref Vpc + AvailabilityZone: !Select + - 1 + - !Split + - "," + - !FindInMap + - RegionMap + - Ref: AWS::Region + - AvailabilityZones + CidrBlock: !Ref DatabaseSubnet2IpBlock + MapPublicIpOnLaunch: false + Tags: + - Key: Name + Value: !Sub "Database Subnet 2 - ${AWS::StackName}" + DatabaseSubnet3: + Type: 'AWS::EC2::Subnet' + Properties: + VpcId: !Ref Vpc + AvailabilityZone: !Select + - 2 + - !Split + - "," + - !FindInMap + - RegionMap + - Ref: AWS::Region + - AvailabilityZones + CidrBlock: !Ref DatabaseSubnet3IpBlock + MapPublicIpOnLaunch: false + Tags: + - Key: Name + Value: !Sub "Database Subnet 3 - ${AWS::StackName}" + +Parameters: + VpcCidrBlock: + Type: String + Default: 10.0.0.0/16 + Description: Enter CIDR block corresponding to VPC's IPv4 range + PublicSubnet1IpBlock: + Type: String + Default: 10.0.0.0/20 + Description: Enter public subnet 1 IP block within VPC's IPv4 range + PublicSubnet2IpBlock: + Type: String + Default: 10.0.16.0/20 + Description: Enter public subnet 2 IP block within VPC's IPv4 range + PublicSubnet3IpBlock: + Type: String + Default: 10.0.32.0/20 + Description: Enter public subnet 3 IP block within VPC's IPv4 range + DatabaseSubnet1IpBlock: + Type: String + Default: 10.0.48.0/20 + Description: Enter database private subnet 1 IP block within VPC's IPv4 range + DatabaseSubnet2IpBlock: + Type: String + Default: 10.0.64.0/20 + Description: Enter database private subnet 2 IP block within VPC's IPv4 range + DatabaseSubnet3IpBlock: + Type: String + Default: 10.0.80.0/20 + Description: Enter database private subnet 3 IP block within VPC's IPv4 range + +Metadata: + AWS::CloudFormation::Interface: + ParameterGroups: + - Label: + default: "VPC Configuration" + Parameters: + - VpcCidrBlock + - Label: + default: "Public Subnet Configuration" + Parameters: + - PublicSubnet1IpBlock + - PublicSubnet2IpBlock + - PublicSubnet3IpBlock + + - Label: + default: "Database Subnet Configuration" + Parameters: + - DatabaseSubnet1IpBlock + - DatabaseSubnet2IpBlock + - DatabaseSubnet3IpBlock + + ParameterLabels: + VpcCidrBlock: + default: "VPC CIDR Block" + PublicSubnet1IpBlock: + default: "Public Subnet 1 CIDR Block" + PublicSubnet2IpBlock: + default: "Public Subnet 2 CIDR Block" + PublicSubnet3IpBlock: + default: "Public Subnet 3 CIDR Block" + DatabaseSubnet1IpBlock: + default: "Database Subnet 1 CIDR Block" + DatabaseSubnet2IpBlock: + default: "Database Subnet 2 CIDR Block" + DatabaseSubnet3IpBlock: + default: "Database Subnet 3 CIDR Block" + +Mappings: + RegionMap: + us-east-1: + AvailabilityZones: 'us-east-1a,us-east-1b,us-east-1c' + us-west-2: + AvailabilityZones: 'us-west-2a,us-west-2b,us-west-2c' + sa-east-1: + AvailabilityZones: 'sa-east-1a,sa-east-1b,sa-east-1c' + +Outputs: + VPC: + Description: ID of the VPC + Value: !Ref Vpc + Export: + Name: !Sub '${AWS::StackName}-vpc-id' + PublicSubnet1: + Description: Public subnet 1 ID + Value: !Ref PublicSubnet1 + Export: + Name: !Sub '${AWS::StackName}-pub-subnet1-id' + PublicSubnet2: + Description: Public subnet 2 ID + Value: !Ref PublicSubnet2 + Export: + Name: !Sub '${AWS::StackName}-pub-subnet2-id' + PublicSubnet3: + Description: Public subnet 3 ID + Value: !Ref PublicSubnet3 + Export: + Name: !Sub '${AWS::StackName}-pub-subnet3-id' + DatabaseSubnet1: + Description: Database subnet 1 ID + Value: !Ref DatabaseSubnet1 + Export: + Name: !Sub '${AWS::StackName}-db-subnet1-id' + DatabaseSubnet2: + Description: Database subnet 2 ID + Value: !Ref DatabaseSubnet2 + Export: + Name: !Sub '${AWS::StackName}-db-subnet2-id' + DatabaseSubnet3: + Description: Database subnet 3 ID + Value: !Ref DatabaseSubnet3 + Export: + Name: !Sub '${AWS::StackName}-db-subnet3-id' diff --git a/cloudformation/vpn.template b/cloudformation/vpn.template new file mode 100644 index 0000000..09d29b4 --- /dev/null +++ b/cloudformation/vpn.template @@ -0,0 +1,115 @@ +## +## CloudFormation template for EC2 VPN instance +## Author: JoaoLippi +## Since: 2021-12-09 +## +## This template provides an EC2 resource to be used as VPN, an instance located in the same VPC +## as the application, but assigned to a public subnet, and assigned a public IP. +## Access to this instance will still require permissions int he Security Group, and possession +## of a private key. +## + +AWSTemplateFormatVersion: 2010-09-09 +Resources: + # EC2 Instance + VpnInstance: + Type: AWS::EC2::Instance + Properties: + InstanceType: !Ref InstanceSize + ImageId: !FindInMap + - RegionMap + - Ref: 'AWS::Region' + - AMI + KeyName: !Ref KeyPair + SecurityGroupIds: + - !Ref VpnSecurityGroup + SubnetId: !Ref PublicSubnet + Tags: + - Key: Name + Value: !Sub 'VPN - ${AWS::StackName}' + + # Security Group + VpnSecurityGroup: + Type: 'AWS::EC2::SecurityGroup' + Properties: + GroupDescription: Security Group for VPN + GroupName: !Sub '${AWS::StackName}-sg' + SecurityGroupIngress: + - IpProtocol: tcp + FromPort: 22 + ToPort: 22 + CidrIp: !Sub '${IpForSsh}/32' + VpcId: !Ref VPC + +Parameters: + VPC: + Type: AWS::EC2::VPC::Id + Description: ID of the VPC to be used by this stack + PublicSubnet: + Type: AWS::EC2::Subnet::Id + Description: ID of the public subnet to be used by this VPN + KeyPair: + Type: AWS::EC2::KeyPair::KeyName + Description: Name of the key pair used to access the VPN + InstanceSize: + Type: String + Description: Instance type (size) for the EC2 instance + Default: t3.nano + IpForSsh: + Type: String + Description: IP for SSH access + Default: '127.0.0.1' + MinLength: 7 + MaxLength: 15 + AllowedPattern: '^\d{0,3}\.\d{0,3}\.\d{0,3}\.\d{0,3}$' + +Metadata: + AWS::CloudFormation::Interface: + ParameterGroups: + - Label: + default: "Instance Configuration" + Parameters: + - InstanceSize + - KeyPair + - IpForSsh + - Label: + default: "Network Configuration" + Parameters: + - VPC + - PublicSubnet + + ParameterLabels: + InstanceSize: + default: "Instance size (type)" + KeyPair: + default: "Key Pair" + PublicSubnet: + default: "Public Subnet" + IpForSsh: + default: "IP for SSH" + +Mappings: + RegionMap: + us-east-1: + AMI: 'ami-0323c3dd2da7fb37d' + us-west-2: + AMI: 'ami-0d6621c01e8c2de2c' + sa-east-1: + AMI: 'ami-003449ffb2605a74c' + +Outputs: + PublicIp: + Description: Public IP of the VPN instance for external access + Value: !GetAtt VpnInstance.PublicIp + + SSHCommand: + Description: SSH command line to access the VPN + Value: + Fn::Sub: + - 'ssh -i ${KeyPair}.pem ec2-user@${Ip}' + - KeyPair: !Ref KeyPair + Ip: !GetAtt VpnInstance.PublicIp + + SecurityGroupId: + Description: ID of the security group created for the VPN + Value: !Ref VpnSecurityGroup diff --git a/codebuild/buildspec.sh b/codebuild/buildspec.sh new file mode 100644 index 0000000..b6cdb08 --- /dev/null +++ b/codebuild/buildspec.sh @@ -0,0 +1 @@ +# TODO: UC-21 \ No newline at end of file diff --git a/codegen.yml b/codegen.yml index c46b2dc..b4dd019 100644 --- a/codegen.yml +++ b/codegen.yml @@ -1,5 +1,5 @@ overwrite: true -schema: 'http://localhost:3000/api/graphql' # GraphQL endpoint via the apollo server +schema: 'https://usecase.dev.simpli.com.br/api/graphql' # GraphQL endpoint via the apollo server documents: 'graphql/hooks/*.ts' # parse graphql operations in matching files generates: generated/graphql.tsx: # location for generated types, hooks and components diff --git a/setup/SetupNextUrql.ts b/setup/SetupNextUrql.ts index c320479..6ff0645 100644 --- a/setup/SetupNextUrql.ts +++ b/setup/SetupNextUrql.ts @@ -4,7 +4,7 @@ import fetch from 'isomorphic-unfetch' import { getToken } from 'state/AuthState' // the URL to /api/graphql -const GRAPHQL_ENDPOINT = 'http://localhost:3000/api/graphql' +const GRAPHQL_ENDPOINT = 'https://usecase.dev.simpli.com.br/api/graphql' export default function SetupNextUrql(App: any) { App.getInitialProps = async (ctx: NextUrqlAppContext) => {