cloudformation/memsub-promotions-cf.yaml (283 lines of code) (raw):

AWSTemplateFormatVersion: '2010-09-09' Description: Membership/Subscription promotions tool Parameters: Name: Description: The name given to the autoscaling group Type: String Stage: Description: Environment name Type: String AllowedValues: - PROD - CODE MaxInstances: Description: Maximum number of instances. This should be (at least) double the desired capacity. Type: Number MinInstances: Description: Minimum number of instances Type: Number VpcId: Description: ID of the VPC onto which to launch the application Type: AWS::EC2::VPC::Id PrivateVpcSubnets: Description: Public subnets to use in VPC Type: List<AWS::EC2::Subnet::Id> PublicVpcSubnets: Description: Public subnets to use for the ELB Type: List<AWS::EC2::Subnet::Id> AmiId: Description: Custom AMI to use for instances Type: String AlarmTopicARN: Description: ARN of the SNS topic which the no healthy hosts CloudWatch alarm should report to Type: String Stack: Description: Application stack Type: String Default: membership SslArn: Description: SSL certificate ARN Type: String App: Description: Application name Type: String Default: promotions-tool Mappings: StageMap: CODE: DNSName: promo.code.memsub-promotions.gutools.co.uk ReadableS3Resources: - arn:aws:s3:::gu-reader-revenue-private/membership/promotions-tool/CODE/* - arn:aws:s3:::gu-promotions-tool-dist/* - arn:aws:s3:::membership-private/google-auth-service-account-certificate.json - arn:aws:s3:::gu-zuora-catalog/PROD/* # i.e. generated by the PROD catalog service (this should be used when running in any stage) PROD: DNSName: memsub-promotions.gutools.co.uk ReadableS3Resources: - arn:aws:s3:::gu-reader-revenue-private/membership/promotions-tool/PROD/* - arn:aws:s3:::gu-promotions-tool-dist/* - arn:aws:s3:::membership-private/google-auth-service-account-certificate.json - arn:aws:s3:::gu-zuora-catalog/PROD/* Resources: ServerRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Statement: - Effect: Allow Principal: Service: - ec2.amazonaws.com Action: - sts:AssumeRole Path: / Policies: - PolicyName: root PolicyDocument: Statement: # Explicitly deny access to all S3 resources except for those defined in ReadableS3Resources # https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_evaluation-logic.html#policy-eval-denyallow - Effect: Deny Action: s3:* NotResource: !FindInMap [StageMap, !Ref Stage, ReadableS3Resources] - Effect: Allow Action: s3:GetObject Resource: !FindInMap [StageMap, !Ref Stage, ReadableS3Resources] - Effect: Allow Action: - ec2:DescribeTags - ec2:DescribeInstances - autoscaling:DescribeAutoScalingGroups - autoscaling:DescribeAutoScalingInstances Resource: '*' - Effect: Allow Action: - dynamodb:PutItem - dynamodb:BatchWriteItem - dynamodb:DescribeTable - dynamodb:GetItem - dynamodb:BatchGetItem - dynamodb:UpdateItem - dynamodb:DeleteItem - dynamodb:Scan - dynamodb:Query Resource: - arn:aws:dynamodb:*:*:table/MembershipSub-Promotions-PROD - arn:aws:dynamodb:*:*:table/MembershipSub-Campaigns-PROD - arn:aws:dynamodb:*:*:table/MembershipSub-Promotions-CODE - arn:aws:dynamodb:*:*:table/MembershipSub-Campaigns-CODE - Effect: Allow Action: - cloudwatch:* - logs:* Resource: '*' - PolicyName: SSMTunnel PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - ssm:UpdateInstanceInformation - ssmmessages:CreateControlChannel - ssmmessages:CreateDataChannel - ssmmessages:OpenControlChannel - ssmmessages:OpenDataChannel Resource: '*' ManagedPolicyArns: - !Sub arn:aws:iam::${AWS::AccountId}:policy/guardian-ec2-role-for-ssm InstanceProfile: Type: AWS::IAM::InstanceProfile Properties: Path: / Roles: - !Ref 'ServerRole' LoadBalancer: Type: AWS::ElasticLoadBalancing::LoadBalancer Properties: LoadBalancerName: !Join ['-', [!Ref 'App', !Ref 'Stage']] Scheme: internet-facing Listeners: - LoadBalancerPort: '443' InstancePort: '9000' Protocol: HTTPS SSLCertificateId: !Ref 'SslArn' CrossZone: 'true' HealthCheck: Target: HTTP:9000/healthcheck HealthyThreshold: '2' UnhealthyThreshold: '10' Interval: '30' Timeout: '10' Subnets: !Ref 'PublicVpcSubnets' SecurityGroups: - !Ref 'LoadBalancerSecurityGroup' ConnectionDrainingPolicy: Enabled: 'true' Timeout: '60' DNSRecord: Type: AWS::Route53::RecordSetGroup Properties: Comment: Alias to memsub-promotions app HostedZoneName: memsub-promotions.gutools.co.uk. RecordSets: - AliasTarget: DNSName: !GetAtt LoadBalancer.DNSName HostedZoneId: !GetAtt LoadBalancer.CanonicalHostedZoneNameID Name: !FindInMap [StageMap, !Ref Stage, DNSName] Type: A AutoscalingGroup: Type: AWS::AutoScaling::AutoScalingGroup Properties: AvailabilityZones: !GetAZs '' LaunchConfigurationName: !Ref 'LaunchConfig' MinSize: !Ref 'MinInstances' MaxSize: !Ref 'MaxInstances' HealthCheckType: ELB HealthCheckGracePeriod: 400 LoadBalancerNames: - !Ref 'LoadBalancer' Tags: - Key: Stage Value: !Ref 'Stage' PropagateAtLaunch: 'true' - Key: Name Value: !Join [':', [!Ref 'Stage', !Ref 'App']] PropagateAtLaunch: 'true' - Key: Stack Value: !Ref 'Stack' PropagateAtLaunch: 'true' - Key: App Value: !Ref 'App' PropagateAtLaunch: 'true' - Key: Role Value: !Ref 'App' PropagateAtLaunch: 'true' VPCZoneIdentifier: !Ref 'PrivateVpcSubnets' LaunchConfig: Type: AWS::AutoScaling::LaunchConfiguration Properties: ImageId: !Ref 'AmiId' SecurityGroups: - !Ref 'InstanceSecurityGroup' - !Ref WazuhSecurityGroup InstanceType: t4g.micro MetadataOptions: HttpTokens: required AssociatePublicIpAddress: 'False' IamInstanceProfile: !Ref 'InstanceProfile' UserData: "Fn::Base64": !Sub - | #!/bin/bash -ev CONF_DIR=/etc/promotions-tool aws s3 cp s3://gu-promotions-tool-dist/${Stack}/${Stage}/${App}/promotions-tool_1.0-SNAPSHOT_all.deb /tmp dpkg -i /tmp/promotions-tool_1.0-SNAPSHOT_all.deb mkdir -p /etc/gu aws s3 cp s3://gu-reader-revenue-private/${Stack}/${App}/${Stage}/memsub-promotions-keys.conf /etc/gu chown promotions-tool /etc/gu/memsub-promotions-keys.conf chmod 0600 /etc/gu/memsub-promotions-keys.conf echo "stage=${Stage}" >> /etc/gu/memsub-promotions-keys.conf /opt/cloudwatch-logs/configure-logs application membership ${Stage} promotions-tool /var/log/promotions-tool/promotions-tool.log - {} LoadBalancerSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: VpcId: !Ref 'VpcId' GroupDescription: Open up HTTPS ingress from internet SecurityGroupIngress: - IpProtocol: tcp FromPort: '443' ToPort: '443' CidrIp: 0.0.0.0/0 SecurityGroupEgress: - IpProtocol: tcp FromPort: '9000' ToPort: '9000' CidrIp: 0.0.0.0/0 InstanceSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: VpcId: !Ref 'VpcId' GroupDescription: Open up HTTP ingress from the load balancer SecurityGroupIngress: - IpProtocol: tcp FromPort: '9000' ToPort: '9000' SourceSecurityGroupId: !Ref 'LoadBalancerSecurityGroup' SecurityGroupEgress: - IpProtocol: tcp FromPort: '443' ToPort: '443' CidrIp: 0.0.0.0/0 WazuhSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: Allow outbound traffic from wazuh agent to manager VpcId: Ref: VpcId SecurityGroupEgress: - IpProtocol: tcp FromPort: 1514 ToPort: 1515 CidrIp: 0.0.0.0/0 alarmNoHealthyHosts: Type: AWS::CloudWatch::Alarm Properties: ActionsEnabled: 'true' AlarmDescription: There are insufficient healthy hosts ComparisonOperator: LessThanThreshold EvaluationPeriods: '1' MetricName: HealthyHostCount Namespace: AWS/ELB Period: '60' Statistic: Average Threshold: !Ref 'MinInstances' AlarmActions: - !Ref 'AlarmTopicARN' InsufficientDataActions: - !Ref 'AlarmTopicARN' Dimensions: - Name: LoadBalancerName Value: !Ref 'LoadBalancer' Outputs: LoadBalancer: Value: !GetAtt [LoadBalancer, DNSName]