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]